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>
895 lines
26 KiB
Markdown
895 lines
26 KiB
Markdown
---
|
|
id: "ET-LLM-003"
|
|
title: "Motor de Generación de Estrategias"
|
|
type: "Technical Specification"
|
|
status: "Done"
|
|
priority: "Alta"
|
|
epic: "OQI-007"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2025-12-05"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# ET-LLM-003: Motor de Generación de Estrategias
|
|
|
|
**É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 motor de generación de estrategias de trading personalizadas, incluyendo análisis de perfil de usuario, cálculo de position sizing y gestión de riesgo.
|
|
|
|
---
|
|
|
|
## Arquitectura del Motor
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ STRATEGY GENERATION ENGINE │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
│ │ User Profile │ │ Market Context │ │ ML Signals │ │
|
|
│ │ Analyzer │ │ Builder │ │ Integrator │ │
|
|
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
|
|
│ │ │ │ │
|
|
│ └───────────────────────┼───────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Strategy Generator │ │
|
|
│ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ Template │ │ Position │ │ Risk │ │ Backtest │ │ │
|
|
│ │ │ Selector │ │ Sizer │ │ Calculator │ │ Validator │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ↓ │
|
|
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Strategy Output Formatter │ │
|
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes del Sistema
|
|
|
|
### 1. User Profile Analyzer
|
|
|
|
```typescript
|
|
// src/modules/copilot/strategy/profile-analyzer.ts
|
|
|
|
interface UserProfile {
|
|
riskLevel: 'conservative' | 'moderate' | 'aggressive';
|
|
experience: 'beginner' | 'intermediate' | 'advanced';
|
|
capital: number;
|
|
maxPositions: number;
|
|
restrictions: string[];
|
|
tradingStyle: 'day' | 'swing' | 'position';
|
|
accountAge: number; // días
|
|
}
|
|
|
|
interface ProfileConstraints {
|
|
maxRiskPerTrade: number; // % del capital
|
|
maxPositionSize: number; // % del capital
|
|
allowedStrategies: string[];
|
|
forbiddenAssets: string[];
|
|
minExperience: number; // meses requeridos
|
|
}
|
|
|
|
export class ProfileAnalyzer {
|
|
getConstraints(profile: UserProfile): ProfileConstraints {
|
|
const constraints: ProfileConstraints = {
|
|
maxRiskPerTrade: this.calculateMaxRisk(profile),
|
|
maxPositionSize: this.calculateMaxPosition(profile),
|
|
allowedStrategies: this.getAllowedStrategies(profile),
|
|
forbiddenAssets: profile.restrictions,
|
|
minExperience: 0,
|
|
};
|
|
|
|
// Restricciones por nivel de riesgo
|
|
switch (profile.riskLevel) {
|
|
case 'conservative':
|
|
constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.02);
|
|
constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.10);
|
|
break;
|
|
case 'moderate':
|
|
constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.05);
|
|
constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.20);
|
|
break;
|
|
case 'aggressive':
|
|
constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.10);
|
|
constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.30);
|
|
break;
|
|
}
|
|
|
|
// Restricciones por experiencia
|
|
if (profile.experience === 'beginner') {
|
|
constraints.allowedStrategies = constraints.allowedStrategies.filter(
|
|
s => !['scalping', 'options', 'leveraged'].includes(s)
|
|
);
|
|
}
|
|
|
|
// Restricciones por antigüedad de cuenta
|
|
if (profile.accountAge < 180) { // 6 meses
|
|
constraints.allowedStrategies = constraints.allowedStrategies.filter(
|
|
s => s !== 'leveraged'
|
|
);
|
|
}
|
|
|
|
return constraints;
|
|
}
|
|
|
|
private calculateMaxRisk(profile: UserProfile): number {
|
|
const baseRisk = {
|
|
conservative: 0.01,
|
|
moderate: 0.03,
|
|
aggressive: 0.05,
|
|
};
|
|
return baseRisk[profile.riskLevel];
|
|
}
|
|
|
|
private calculateMaxPosition(profile: UserProfile): number {
|
|
const basePosition = {
|
|
conservative: 0.05,
|
|
moderate: 0.15,
|
|
aggressive: 0.25,
|
|
};
|
|
return basePosition[profile.riskLevel];
|
|
}
|
|
|
|
private getAllowedStrategies(profile: UserProfile): string[] {
|
|
const strategies = ['trend_following', 'mean_reversion', 'breakout'];
|
|
|
|
if (profile.experience !== 'beginner') {
|
|
strategies.push('momentum', 'swing');
|
|
}
|
|
|
|
if (profile.experience === 'advanced') {
|
|
strategies.push('scalping', 'options');
|
|
}
|
|
|
|
return strategies;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Strategy Templates
|
|
|
|
```typescript
|
|
// src/modules/copilot/strategy/templates.ts
|
|
|
|
interface StrategyTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
difficulty: 'easy' | 'intermediate' | 'advanced';
|
|
style: 'day' | 'swing' | 'position';
|
|
minExperience: 'beginner' | 'intermediate' | 'advanced';
|
|
requiredIndicators: string[];
|
|
entryConditions: EntryCondition[];
|
|
exitConditions: ExitCondition[];
|
|
riskManagement: RiskManagement;
|
|
}
|
|
|
|
export const STRATEGY_TEMPLATES: StrategyTemplate[] = [
|
|
{
|
|
id: 'sma_crossover',
|
|
name: 'SMA Crossover',
|
|
description: 'Cruce de medias móviles simples (20/50)',
|
|
difficulty: 'easy',
|
|
style: 'swing',
|
|
minExperience: 'beginner',
|
|
requiredIndicators: ['SMA'],
|
|
entryConditions: [
|
|
{
|
|
type: 'crossover',
|
|
indicator1: { name: 'SMA', period: 20 },
|
|
indicator2: { name: 'SMA', period: 50 },
|
|
direction: 'above', // SMA20 cruza por encima de SMA50
|
|
action: 'long',
|
|
},
|
|
{
|
|
type: 'crossover',
|
|
indicator1: { name: 'SMA', period: 20 },
|
|
indicator2: { name: 'SMA', period: 50 },
|
|
direction: 'below',
|
|
action: 'short',
|
|
},
|
|
],
|
|
exitConditions: [
|
|
{
|
|
type: 'stop_loss',
|
|
method: 'atr_multiple',
|
|
value: 2,
|
|
},
|
|
{
|
|
type: 'take_profit',
|
|
method: 'risk_reward',
|
|
value: 2, // 2:1 R:R
|
|
},
|
|
],
|
|
riskManagement: {
|
|
maxRiskPerTrade: 0.02,
|
|
positionSizing: 'fixed_risk',
|
|
},
|
|
},
|
|
{
|
|
id: 'rsi_oversold',
|
|
name: 'RSI Oversold Bounce',
|
|
description: 'Comprar cuando RSI está sobrevendido en tendencia alcista',
|
|
difficulty: 'easy',
|
|
style: 'swing',
|
|
minExperience: 'beginner',
|
|
requiredIndicators: ['RSI', 'SMA'],
|
|
entryConditions: [
|
|
{
|
|
type: 'threshold',
|
|
indicator: { name: 'RSI', period: 14 },
|
|
condition: 'below',
|
|
value: 30,
|
|
action: 'watch',
|
|
},
|
|
{
|
|
type: 'threshold',
|
|
indicator: { name: 'RSI', period: 14 },
|
|
condition: 'crosses_above',
|
|
value: 30,
|
|
action: 'long',
|
|
filter: {
|
|
indicator: { name: 'SMA', period: 200 },
|
|
priceAbove: true, // Precio sobre SMA200 (tendencia alcista)
|
|
},
|
|
},
|
|
],
|
|
exitConditions: [
|
|
{
|
|
type: 'threshold',
|
|
indicator: { name: 'RSI', period: 14 },
|
|
condition: 'above',
|
|
value: 70,
|
|
},
|
|
{
|
|
type: 'stop_loss',
|
|
method: 'percent',
|
|
value: 0.03, // 3%
|
|
},
|
|
],
|
|
riskManagement: {
|
|
maxRiskPerTrade: 0.02,
|
|
positionSizing: 'fixed_risk',
|
|
},
|
|
},
|
|
{
|
|
id: 'bollinger_squeeze',
|
|
name: 'Bollinger Band Squeeze',
|
|
description: 'Breakout después de contracción de volatilidad',
|
|
difficulty: 'intermediate',
|
|
style: 'swing',
|
|
minExperience: 'intermediate',
|
|
requiredIndicators: ['BB', 'ATR'],
|
|
entryConditions: [
|
|
{
|
|
type: 'squeeze',
|
|
indicator: { name: 'BB', period: 20, stddev: 2 },
|
|
condition: 'width_below',
|
|
value: 0.04, // BB width < 4%
|
|
action: 'prepare',
|
|
},
|
|
{
|
|
type: 'breakout',
|
|
indicator: { name: 'BB', period: 20, stddev: 2 },
|
|
condition: 'close_above_upper',
|
|
volumeConfirm: true,
|
|
action: 'long',
|
|
},
|
|
],
|
|
exitConditions: [
|
|
{
|
|
type: 'trailing_stop',
|
|
method: 'atr_multiple',
|
|
value: 2,
|
|
},
|
|
{
|
|
type: 'time_exit',
|
|
maxBars: 10, // Máximo 10 velas
|
|
},
|
|
],
|
|
riskManagement: {
|
|
maxRiskPerTrade: 0.03,
|
|
positionSizing: 'volatility_adjusted',
|
|
},
|
|
},
|
|
{
|
|
id: 'macd_divergence',
|
|
name: 'MACD Divergence',
|
|
description: 'Detectar divergencias entre precio y MACD',
|
|
difficulty: 'advanced',
|
|
style: 'swing',
|
|
minExperience: 'intermediate',
|
|
requiredIndicators: ['MACD', 'RSI'],
|
|
entryConditions: [
|
|
{
|
|
type: 'divergence',
|
|
indicator: { name: 'MACD' },
|
|
priceCondition: 'lower_low',
|
|
indicatorCondition: 'higher_low',
|
|
action: 'long',
|
|
confirmation: {
|
|
indicator: { name: 'MACD' },
|
|
condition: 'histogram_positive',
|
|
},
|
|
},
|
|
],
|
|
exitConditions: [
|
|
{
|
|
type: 'signal',
|
|
indicator: { name: 'MACD' },
|
|
condition: 'signal_crossover_down',
|
|
},
|
|
{
|
|
type: 'stop_loss',
|
|
method: 'swing_low',
|
|
buffer: 0.005,
|
|
},
|
|
],
|
|
riskManagement: {
|
|
maxRiskPerTrade: 0.02,
|
|
positionSizing: 'fixed_risk',
|
|
},
|
|
},
|
|
{
|
|
id: 'ml_momentum',
|
|
name: 'ML-Enhanced Momentum',
|
|
description: 'Momentum con confirmación de señales ML',
|
|
difficulty: 'intermediate',
|
|
style: 'swing',
|
|
minExperience: 'intermediate',
|
|
requiredIndicators: ['RSI', 'MACD', 'ML_SIGNAL'],
|
|
entryConditions: [
|
|
{
|
|
type: 'ml_signal',
|
|
direction: 'bullish',
|
|
minConfidence: 0.65,
|
|
action: 'watch',
|
|
},
|
|
{
|
|
type: 'momentum',
|
|
indicator: { name: 'RSI', period: 14 },
|
|
condition: 'between',
|
|
range: [40, 60],
|
|
action: 'prepare',
|
|
},
|
|
{
|
|
type: 'confirmation',
|
|
indicator: { name: 'MACD' },
|
|
condition: 'histogram_rising',
|
|
action: 'long',
|
|
},
|
|
],
|
|
exitConditions: [
|
|
{
|
|
type: 'ml_signal',
|
|
direction: 'bearish',
|
|
minConfidence: 0.60,
|
|
},
|
|
{
|
|
type: 'trailing_stop',
|
|
method: 'percent',
|
|
value: 0.05,
|
|
},
|
|
],
|
|
riskManagement: {
|
|
maxRiskPerTrade: 0.025,
|
|
positionSizing: 'kelly_fraction',
|
|
kellyFraction: 0.25,
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
### 3. Position Sizer
|
|
|
|
```typescript
|
|
// src/modules/copilot/strategy/position-sizer.ts
|
|
|
|
interface PositionSizingParams {
|
|
capital: number;
|
|
entryPrice: number;
|
|
stopLossPrice: number;
|
|
maxRiskPercent: number;
|
|
method: 'fixed_risk' | 'volatility_adjusted' | 'kelly_fraction';
|
|
volatility?: number; // ATR
|
|
winRate?: number; // Para Kelly
|
|
avgWinLoss?: number; // Para Kelly
|
|
}
|
|
|
|
interface PositionSize {
|
|
shares: number;
|
|
totalValue: number;
|
|
riskAmount: number;
|
|
percentOfCapital: number;
|
|
}
|
|
|
|
export class PositionSizer {
|
|
calculate(params: PositionSizingParams): PositionSize {
|
|
switch (params.method) {
|
|
case 'fixed_risk':
|
|
return this.fixedRisk(params);
|
|
case 'volatility_adjusted':
|
|
return this.volatilityAdjusted(params);
|
|
case 'kelly_fraction':
|
|
return this.kellyFraction(params);
|
|
default:
|
|
return this.fixedRisk(params);
|
|
}
|
|
}
|
|
|
|
private fixedRisk(params: PositionSizingParams): PositionSize {
|
|
const { capital, entryPrice, stopLossPrice, maxRiskPercent } = params;
|
|
|
|
const riskPerShare = Math.abs(entryPrice - stopLossPrice);
|
|
const maxRiskAmount = capital * maxRiskPercent;
|
|
const shares = Math.floor(maxRiskAmount / riskPerShare);
|
|
const totalValue = shares * entryPrice;
|
|
|
|
return {
|
|
shares,
|
|
totalValue,
|
|
riskAmount: shares * riskPerShare,
|
|
percentOfCapital: (totalValue / capital) * 100,
|
|
};
|
|
}
|
|
|
|
private volatilityAdjusted(params: PositionSizingParams): PositionSize {
|
|
const { capital, entryPrice, maxRiskPercent, volatility } = params;
|
|
|
|
if (!volatility) {
|
|
return this.fixedRisk(params);
|
|
}
|
|
|
|
// Ajustar posición inversamente a la volatilidad
|
|
const baseAllocation = capital * 0.10; // 10% base
|
|
const volatilityFactor = 0.02 / volatility; // Normalizar a 2% ATR
|
|
const adjustedAllocation = baseAllocation * Math.min(volatilityFactor, 2);
|
|
|
|
const shares = Math.floor(adjustedAllocation / entryPrice);
|
|
const stopLossDistance = volatility * 2;
|
|
|
|
return {
|
|
shares,
|
|
totalValue: shares * entryPrice,
|
|
riskAmount: shares * stopLossDistance,
|
|
percentOfCapital: (shares * entryPrice / capital) * 100,
|
|
};
|
|
}
|
|
|
|
private kellyFraction(params: PositionSizingParams): PositionSize {
|
|
const { capital, entryPrice, winRate, avgWinLoss, maxRiskPercent } = params;
|
|
|
|
if (!winRate || !avgWinLoss) {
|
|
return this.fixedRisk(params);
|
|
}
|
|
|
|
// Kelly Criterion: f* = (bp - q) / b
|
|
// b = ratio win/loss, p = win rate, q = 1 - p
|
|
const b = avgWinLoss;
|
|
const p = winRate;
|
|
const q = 1 - p;
|
|
|
|
let kellyPercent = (b * p - q) / b;
|
|
kellyPercent = Math.max(0, Math.min(kellyPercent, maxRiskPercent));
|
|
kellyPercent *= 0.25; // Usar solo 25% del Kelly (half-Kelly)
|
|
|
|
const allocation = capital * kellyPercent;
|
|
const shares = Math.floor(allocation / entryPrice);
|
|
|
|
return {
|
|
shares,
|
|
totalValue: shares * entryPrice,
|
|
riskAmount: allocation * 0.5, // Estimado
|
|
percentOfCapital: kellyPercent * 100,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Risk Calculator
|
|
|
|
```typescript
|
|
// src/modules/copilot/strategy/risk-calculator.ts
|
|
|
|
interface RiskMetrics {
|
|
riskAmount: number;
|
|
riskPercent: number;
|
|
rewardAmount: number;
|
|
rewardPercent: number;
|
|
riskRewardRatio: number;
|
|
breakEvenWinRate: number;
|
|
expectedValue: number;
|
|
}
|
|
|
|
interface StopLossParams {
|
|
method: 'percent' | 'atr_multiple' | 'swing_low' | 'support_level';
|
|
value: number;
|
|
entryPrice: number;
|
|
atr?: number;
|
|
swingLow?: number;
|
|
supportLevel?: number;
|
|
}
|
|
|
|
export class RiskCalculator {
|
|
calculateStopLoss(params: StopLossParams): number {
|
|
const { method, value, entryPrice, atr, swingLow, supportLevel } = params;
|
|
|
|
switch (method) {
|
|
case 'percent':
|
|
return entryPrice * (1 - value);
|
|
|
|
case 'atr_multiple':
|
|
if (!atr) throw new Error('ATR required for atr_multiple method');
|
|
return entryPrice - (atr * value);
|
|
|
|
case 'swing_low':
|
|
if (!swingLow) throw new Error('Swing low required');
|
|
return swingLow * (1 - value); // value es buffer %
|
|
|
|
case 'support_level':
|
|
if (!supportLevel) throw new Error('Support level required');
|
|
return supportLevel * (1 - value);
|
|
|
|
default:
|
|
return entryPrice * 0.95; // Default 5%
|
|
}
|
|
}
|
|
|
|
calculateTakeProfit(
|
|
entryPrice: number,
|
|
stopLoss: number,
|
|
riskRewardRatio: number,
|
|
): number {
|
|
const riskPerShare = entryPrice - stopLoss;
|
|
const rewardPerShare = riskPerShare * riskRewardRatio;
|
|
return entryPrice + rewardPerShare;
|
|
}
|
|
|
|
calculateMetrics(
|
|
entryPrice: number,
|
|
stopLoss: number,
|
|
takeProfit: number,
|
|
positionSize: number,
|
|
estimatedWinRate?: number,
|
|
): RiskMetrics {
|
|
const riskAmount = (entryPrice - stopLoss) * positionSize;
|
|
const rewardAmount = (takeProfit - entryPrice) * positionSize;
|
|
const riskRewardRatio = rewardAmount / riskAmount;
|
|
const breakEvenWinRate = 1 / (1 + riskRewardRatio);
|
|
|
|
let expectedValue = 0;
|
|
if (estimatedWinRate) {
|
|
expectedValue = (estimatedWinRate * rewardAmount) -
|
|
((1 - estimatedWinRate) * riskAmount);
|
|
}
|
|
|
|
return {
|
|
riskAmount,
|
|
riskPercent: (riskAmount / (entryPrice * positionSize)) * 100,
|
|
rewardAmount,
|
|
rewardPercent: (rewardAmount / (entryPrice * positionSize)) * 100,
|
|
riskRewardRatio,
|
|
breakEvenWinRate,
|
|
expectedValue,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Strategy Generator
|
|
|
|
```typescript
|
|
// src/modules/copilot/strategy/strategy-generator.ts
|
|
|
|
interface GenerateStrategyParams {
|
|
symbol: string;
|
|
userProfile: UserProfile;
|
|
userPlan: 'free' | 'pro' | 'premium';
|
|
marketData: MarketData;
|
|
mlSignal?: MLSignal;
|
|
preferredStyle?: 'day' | 'swing' | 'position';
|
|
}
|
|
|
|
interface GeneratedStrategy {
|
|
template: StrategyTemplate;
|
|
symbol: string;
|
|
entry: {
|
|
price: number;
|
|
condition: string;
|
|
confidence: number;
|
|
};
|
|
stopLoss: {
|
|
price: number;
|
|
percent: number;
|
|
method: string;
|
|
};
|
|
takeProfit: {
|
|
price: number;
|
|
percent: number;
|
|
riskRewardRatio: number;
|
|
};
|
|
positionSize: PositionSize;
|
|
riskMetrics: RiskMetrics;
|
|
explanation: string;
|
|
invalidationConditions: string[];
|
|
disclaimer: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class StrategyGenerator {
|
|
constructor(
|
|
private readonly profileAnalyzer: ProfileAnalyzer,
|
|
private readonly positionSizer: PositionSizer,
|
|
private readonly riskCalculator: RiskCalculator,
|
|
private readonly marketDataService: MarketDataService,
|
|
private readonly mlService: MLSignalService,
|
|
) {}
|
|
|
|
async generate(params: GenerateStrategyParams): Promise<GeneratedStrategy> {
|
|
const { symbol, userProfile, userPlan, marketData, mlSignal } = params;
|
|
|
|
// 1. Get profile constraints
|
|
const constraints = this.profileAnalyzer.getConstraints(userProfile);
|
|
|
|
// 2. Filter applicable templates
|
|
const applicableTemplates = this.filterTemplates(
|
|
STRATEGY_TEMPLATES,
|
|
constraints,
|
|
userPlan,
|
|
params.preferredStyle,
|
|
);
|
|
|
|
// 3. Score and select best template
|
|
const scoredTemplates = await this.scoreTemplates(
|
|
applicableTemplates,
|
|
marketData,
|
|
mlSignal,
|
|
);
|
|
|
|
const selectedTemplate = scoredTemplates[0].template;
|
|
|
|
// 4. Calculate entry/exit levels
|
|
const { entry, stopLoss, takeProfit } = await this.calculateLevels(
|
|
selectedTemplate,
|
|
symbol,
|
|
marketData,
|
|
);
|
|
|
|
// 5. Calculate position size
|
|
const positionSize = this.positionSizer.calculate({
|
|
capital: userProfile.capital,
|
|
entryPrice: entry.price,
|
|
stopLossPrice: stopLoss.price,
|
|
maxRiskPercent: constraints.maxRiskPerTrade,
|
|
method: selectedTemplate.riskManagement.positionSizing,
|
|
volatility: marketData.atr,
|
|
});
|
|
|
|
// 6. Calculate risk metrics
|
|
const riskMetrics = this.riskCalculator.calculateMetrics(
|
|
entry.price,
|
|
stopLoss.price,
|
|
takeProfit.price,
|
|
positionSize.shares,
|
|
mlSignal?.confidence,
|
|
);
|
|
|
|
// 7. Generate explanation
|
|
const explanation = this.generateExplanation(
|
|
selectedTemplate,
|
|
entry,
|
|
stopLoss,
|
|
takeProfit,
|
|
marketData,
|
|
mlSignal,
|
|
);
|
|
|
|
return {
|
|
template: selectedTemplate,
|
|
symbol,
|
|
entry,
|
|
stopLoss,
|
|
takeProfit,
|
|
positionSize,
|
|
riskMetrics,
|
|
explanation,
|
|
invalidationConditions: this.getInvalidationConditions(selectedTemplate),
|
|
disclaimer: this.getDisclaimer(),
|
|
};
|
|
}
|
|
|
|
private filterTemplates(
|
|
templates: StrategyTemplate[],
|
|
constraints: ProfileConstraints,
|
|
userPlan: string,
|
|
preferredStyle?: string,
|
|
): StrategyTemplate[] {
|
|
return templates.filter(t => {
|
|
// Check if strategy is allowed
|
|
if (!constraints.allowedStrategies.includes(t.id)) return false;
|
|
|
|
// Check ML requirement
|
|
if (t.requiredIndicators.includes('ML_SIGNAL') && userPlan === 'free') {
|
|
return false;
|
|
}
|
|
|
|
// Check style preference
|
|
if (preferredStyle && t.style !== preferredStyle) return false;
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
private async scoreTemplates(
|
|
templates: StrategyTemplate[],
|
|
marketData: MarketData,
|
|
mlSignal?: MLSignal,
|
|
): Promise<Array<{ template: StrategyTemplate; score: number }>> {
|
|
const scored = templates.map(template => {
|
|
let score = 50; // Base score
|
|
|
|
// Score based on market conditions
|
|
if (marketData.trend === 'bullish' && template.id.includes('momentum')) {
|
|
score += 20;
|
|
}
|
|
|
|
if (marketData.volatility > 0.03 && template.id.includes('breakout')) {
|
|
score += 15;
|
|
}
|
|
|
|
// Score based on ML alignment
|
|
if (mlSignal && template.requiredIndicators.includes('ML_SIGNAL')) {
|
|
if (mlSignal.confidence > 0.7) {
|
|
score += 25;
|
|
} else if (mlSignal.confidence > 0.5) {
|
|
score += 10;
|
|
}
|
|
}
|
|
|
|
return { template, score };
|
|
});
|
|
|
|
return scored.sort((a, b) => b.score - a.score);
|
|
}
|
|
|
|
private generateExplanation(
|
|
template: StrategyTemplate,
|
|
entry: any,
|
|
stopLoss: any,
|
|
takeProfit: any,
|
|
marketData: MarketData,
|
|
mlSignal?: MLSignal,
|
|
): string {
|
|
let explanation = `La estrategia "${template.name}" es adecuada para las condiciones actuales.\n\n`;
|
|
|
|
explanation += `**Por qué esta estrategia:** ${template.description}\n\n`;
|
|
|
|
explanation += `**Entrada en $${entry.price.toFixed(2)}:** ${entry.condition}\n\n`;
|
|
|
|
explanation += `**Stop Loss en $${stopLoss.price.toFixed(2)} (${stopLoss.percent.toFixed(1)}%):** `;
|
|
explanation += `Usando método ${stopLoss.method} para proteger el capital.\n\n`;
|
|
|
|
explanation += `**Take Profit en $${takeProfit.price.toFixed(2)} (R:R ${takeProfit.riskRewardRatio.toFixed(1)}:1):** `;
|
|
explanation += `Objetivo basado en resistencia/proyección técnica.\n\n`;
|
|
|
|
if (mlSignal) {
|
|
explanation += `**Confirmación ML:** El modelo predice movimiento ${mlSignal.prediction} `;
|
|
explanation += `con ${(mlSignal.confidence * 100).toFixed(0)}% de confianza.\n\n`;
|
|
}
|
|
|
|
return explanation;
|
|
}
|
|
|
|
private getInvalidationConditions(template: StrategyTemplate): string[] {
|
|
return [
|
|
`El precio rompe el nivel de stop loss`,
|
|
`Cambio de tendencia en timeframe mayor`,
|
|
`Volumen anormalmente bajo en breakout`,
|
|
`Noticia de alto impacto contradictoria`,
|
|
];
|
|
}
|
|
|
|
private getDisclaimer(): string {
|
|
return `⚠️ Esta sugerencia es informativa y no constituye asesoría financiera. ` +
|
|
`El trading implica riesgos significativos. Opera bajo tu propio criterio y riesgo. ` +
|
|
`Nunca inviertas más de lo que puedes permitirte perder.`;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Integración con LLM Agent
|
|
|
|
```typescript
|
|
// Tool definition para el agente
|
|
const generateStrategyTool = {
|
|
type: 'function',
|
|
function: {
|
|
name: 'generate_strategy',
|
|
description: 'Genera una estrategia de trading personalizada para un símbolo',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
symbol: {
|
|
type: 'string',
|
|
description: 'Símbolo del activo',
|
|
},
|
|
style: {
|
|
type: 'string',
|
|
enum: ['day', 'swing', 'position'],
|
|
description: 'Estilo de trading preferido (opcional)',
|
|
},
|
|
},
|
|
required: ['symbol'],
|
|
},
|
|
},
|
|
};
|
|
|
|
// Implementación del tool
|
|
async function generateStrategy(
|
|
symbol: string,
|
|
userId: string,
|
|
userPlan: string,
|
|
style?: string,
|
|
): Promise<GeneratedStrategy> {
|
|
const userProfile = await userService.getProfile(userId);
|
|
const marketData = await marketDataService.getFullContext(symbol);
|
|
const mlSignal = userPlan !== 'free'
|
|
? await mlService.getPrediction(symbol)
|
|
: undefined;
|
|
|
|
const strategy = await strategyGenerator.generate({
|
|
symbol,
|
|
userProfile,
|
|
userPlan,
|
|
marketData,
|
|
mlSignal,
|
|
preferredStyle: style,
|
|
});
|
|
|
|
return strategy;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Dependencias
|
|
|
|
### Módulos Internos
|
|
- MarketDataService (OQI-003)
|
|
- MLSignalService (OQI-006)
|
|
- UserService (OQI-001)
|
|
|
|
### Bibliotecas
|
|
- technicalindicators (cálculo de indicadores)
|
|
- decimal.js (precisión en cálculos financieros)
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [RF-LLM-003: Sugerencias de Estrategias](../requerimientos/RF-LLM-003-strategy-suggestions.md)
|
|
- [ET-LLM-002: Agente de Análisis](./ET-LLM-002-agente-analisis.md)
|
|
|
|
---
|
|
|
|
*Especificación técnica - Sistema NEXUS*
|
|
*Trading Platform*
|