Complete remaining ET specs identified in INTEGRATION-PLAN: - ET-EDU-007: Video Player Advanced (554 LOC component) - ET-MT4-001: WebSocket Integration (BLOCKER - 0% implemented) - ET-ML-009: Ensemble Signal (Multi-strategy aggregation) - ET-TRD-009: Risk-Based Position Sizer (391 LOC component) - ET-TRD-010: Drawing Tools Persistence (backend + store) - ET-TRD-011: Market Bias Indicator (multi-timeframe analysis) - ET-PFM-009: Custom Charts (SVG AllocationChart + Canvas PerformanceChart) - ET-ML-008: ICT Analysis Card (expanded - 294 LOC component) All specs include: - Architecture diagrams - Complete code examples - API contracts - Implementation guides - Testing scenarios Related: TASK-2026-01-25-002-FRONTEND-COMPREHENSIVE-AUDIT Priority: P1-P3 (mixed) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
22 KiB
22 KiB
ET-TRD-011: Market Bias Indicator
Versión: 1.0.0 Fecha: 2026-01-25 Epic: OQI-003 - Trading y Charts Componente: Frontend Component + Backend Calculation Estado: 🔴 No Implementado (especificación nueva) Prioridad: P3
Metadata
| Campo | Valor |
|---|---|
| ID | ET-TRD-011 |
| Tipo | Especificación Técnica |
| Epic | OQI-003 |
| US Relacionada | US-TRD-011 (Ver Sesgo de Mercado Multi-Timeframe) |
| Componente Frontend | MarketBiasIndicator.tsx (a crear) |
| Componente Backend | /api/market-bias/:symbol (a crear) |
| Complejidad | Media (análisis multi-timeframe + cálculos técnicos) |
| Esfuerzo Estimado | 2 horas |
1. Descripción General
Market Bias Indicator es un indicador de sesgo de mercado que muestra la dirección predominante del precio a través de múltiples timeframes, utilizando una combinación de:
- Moving Averages (MA): Tendencia basada en EMAs (20, 50, 200)
- RSI: Momentum (sobrecompra/sobreventa)
- MACD: Divergencia y señales de cruce
- Price Action: Higher Highs/Lower Lows
- Volume Trend: Confirmación de volumen
Objetivo
Proporcionar una visión rápida y clara del sesgo del mercado en múltiples timeframes (1m, 5m, 15m, 1h, 4h, 1d) para ayudar a los traders a:
✅ Identificar la tendencia dominante ✅ Alinear trades con el sesgo del mercado ✅ Detectar divergencias entre timeframes ✅ Confirmar setups de entrada con bias
2. Arquitectura del Indicador
2.1 Diagrama de Flujo
┌──────────────────────────────────────────────────────────┐
│ Market Bias Indicator Architecture │
├──────────────────────────────────────────────────────────┤
│ │
│ Market Data (BTCUSD, Multiple Timeframes) │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Fetch Candles for 6 Timeframes │ │
│ │ • 1m (last 100 candles) │ │
│ │ • 5m (last 100 candles) │ │
│ │ • 15m (last 100 candles) │ │
│ │ • 1h (last 100 candles) │ │
│ │ • 4h (last 100 candles) │ │
│ │ • 1d (last 100 candles) │ │
│ └──────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────┼───────┬───────┬───────┬───────┐ │
│ │ │ │ │ │ │ │
│ v v v v v v │
│ 1m 5m 15m 1h 4h 1d │
│ │ │ │ │ │ │ │
│ v v v v v v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Calculate Technical Indicators (per TF) │ │
│ │ • EMA 20, 50, 200 │ │
│ │ • RSI (14 period) │ │
│ │ • MACD (12, 26, 9) │ │
│ │ • Price trend (HH/HL vs LH/LL) │ │
│ │ • Volume trend (avg volume vs current) │ │
│ └──────────────┬──────────────────────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Calculate Bias Score (per TF) │ │
│ │ • MA Bias: +1 if price > all MAs, -1 if < │ │
│ │ • RSI Bias: +1 if > 50, -1 if < 50 │ │
│ │ • MACD Bias: +1 if histogram > 0, -1 if < 0 │ │
│ │ • Trend Bias: +1 if HH/HL, -1 if LH/LL │ │
│ │ Total Score: Sum / 4 = [-1.0, +1.0] │ │
│ └──────────────┬──────────────────────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Aggregate Multi-Timeframe Bias │ │
│ │ • 1m: +0.75 (BULLISH) │ │
│ │ • 5m: +0.50 (BULLISH) │ │
│ │ • 15m: +0.25 (NEUTRAL) │ │
│ │ • 1h: -0.25 (NEUTRAL) │ │
│ │ • 4h: -0.50 (BEARISH) │ │
│ │ • 1d: -0.75 (BEARISH) │ │
│ │ Overall: +0.00 (NEUTRAL/CONFLICTED) │ │
│ └──────────────┬──────────────────────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Frontend Display │ │
│ │ • Visual grid (6 timeframes) │ │
│ │ • Color coding (green/red/yellow) │ │
│ │ • Alignment indicator │ │
│ │ • Recommended action │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
3. Cálculo de Bias Score
3.1 Componentes del Score (per Timeframe)
Component 1: Moving Average Bias
function calculateMABias(close: number, ema20: number, ema50: number, ema200: number): number {
let score = 0;
// Price above all MAs = strong bullish
if (close > ema20 && close > ema50 && close > ema200) {
score = 1.0;
}
// Price below all MAs = strong bearish
else if (close < ema20 && close < ema50 && close < ema200) {
score = -1.0;
}
// Price between MAs = mixed/neutral
else if (close > ema20 && close > ema50) {
score = 0.5; // Above short-term MAs
}
else if (close < ema20 && close < ema50) {
score = -0.5; // Below short-term MAs
}
else {
score = 0.0; // Choppy/neutral
}
return score;
}
Interpretation:
- +1.0 → Price above ALL MAs (strong uptrend)
- +0.5 → Price above short-term MAs (moderate uptrend)
- 0.0 → Price mixed between MAs (neutral/range)
- -0.5 → Price below short-term MAs (moderate downtrend)
- -1.0 → Price below ALL MAs (strong downtrend)
Component 2: RSI Bias
function calculateRSIBias(rsi: number): number {
if (rsi > 70) return 1.0; // Overbought = strong bullish momentum
if (rsi > 60) return 0.75; // Bullish momentum
if (rsi > 50) return 0.5; // Slight bullish bias
if (rsi === 50) return 0.0; // Neutral
if (rsi > 40) return -0.5; // Slight bearish bias
if (rsi > 30) return -0.75; // Bearish momentum
return -1.0; // Oversold = strong bearish momentum
}
Component 3: MACD Bias
function calculateMACDBias(macdLine: number, signalLine: number, histogram: number): number {
let score = 0;
// MACD above signal and histogram positive = bullish
if (macdLine > signalLine && histogram > 0) {
score = 1.0;
}
// MACD below signal and histogram negative = bearish
else if (macdLine < signalLine && histogram < 0) {
score = -1.0;
}
// MACD above signal but histogram negative = weakening bullish
else if (macdLine > signalLine && histogram < 0) {
score = 0.25;
}
// MACD below signal but histogram positive = weakening bearish
else if (macdLine < signalLine && histogram > 0) {
score = -0.25;
}
return score;
}
Component 4: Price Action Bias (Higher Highs / Lower Lows)
function calculatePriceActionBias(candles: Candle[]): number {
const last10 = candles.slice(-10);
const highs = last10.map(c => c.high);
const lows = last10.map(c => c.low);
// Count higher highs (HH)
let HH = 0;
for (let i = 1; i < highs.length; i++) {
if (highs[i] > highs[i - 1]) HH++;
}
// Count lower lows (LL)
let LL = 0;
for (let i = 1; i < lows.length; i++) {
if (lows[i] < lows[i - 1]) LL++;
}
// HH dominate = uptrend
if (HH > LL + 2) return 1.0;
if (HH > LL) return 0.5;
// LL dominate = downtrend
if (LL > HH + 2) return -1.0;
if (LL > HH) return -0.5;
// Equal = neutral/range
return 0.0;
}
3.2 Final Bias Score (per Timeframe)
function calculateBiasScore(
candles: Candle[],
ema20: number,
ema50: number,
ema200: number,
rsi: number,
macd: MACD
): BiasResult {
const close = candles[candles.length - 1].close;
const maBias = calculateMABias(close, ema20, ema50, ema200);
const rsiBias = calculateRSIBias(rsi);
const macdBias = calculateMACDBias(macd.macd, macd.signal, macd.histogram);
const paBias = calculatePriceActionBias(candles);
// Weighted average (all equal weight for simplicity)
const totalScore = (maBias + rsiBias + macdBias + paBias) / 4;
return {
score: totalScore, // -1.0 to +1.0
direction: totalScore > 0.3 ? 'BULLISH' : totalScore < -0.3 ? 'BEARISH' : 'NEUTRAL',
strength: Math.abs(totalScore) * 100, // 0-100%
components: {
ma: maBias,
rsi: rsiBias,
macd: macdBias,
priceAction: paBias
}
};
}
4. API Endpoint
4.1 Request
GET /api/market-bias/:symbol
Query params: ?timeframes=1m,5m,15m,1h,4h,1d
4.2 Response
{
"symbol": "BTCUSD",
"timestamp": "2026-01-25T10:30:15Z",
"biases": [
{
"timeframe": "1m",
"score": 0.75,
"direction": "BULLISH",
"strength": 75,
"components": {
"ma": 1.0,
"rsi": 0.75,
"macd": 1.0,
"priceAction": 0.25
},
"currentPrice": 43250.50,
"indicators": {
"ema20": 43100.00,
"ema50": 42950.00,
"ema200": 42500.00,
"rsi": 68.5,
"macd": {
"macd": 125.3,
"signal": 98.7,
"histogram": 26.6
}
}
},
{
"timeframe": "5m",
"score": 0.50,
"direction": "BULLISH",
"strength": 50,
"components": { /* ... */ }
},
{
"timeframe": "15m",
"score": 0.25,
"direction": "NEUTRAL",
"strength": 25,
"components": { /* ... */ }
},
{
"timeframe": "1h",
"score": -0.25,
"direction": "NEUTRAL",
"strength": 25,
"components": { /* ... */ }
},
{
"timeframe": "4h",
"score": -0.50,
"direction": "BEARISH",
"strength": 50,
"components": { /* ... */ }
},
{
"timeframe": "1d",
"score": -0.75,
"direction": "BEARISH",
"strength": 75,
"components": { /* ... */ }
}
],
"overall": {
"score": 0.00,
"direction": "CONFLICTED",
"alignment": "LOW", // ALIGNED / MODERATE / LOW / CONFLICTED
"recommendation": "WAIT - Higher timeframes bearish, lower bullish. Wait for alignment."
}
}
5. Frontend Component
5.1 Component Code
// apps/frontend/src/modules/trading/components/MarketBiasIndicator.tsx
import React, { useEffect, useState } from 'react';
import { TrendingUp, TrendingDown, Minus, AlertTriangle, CheckCircle2 } from 'lucide-react';
import { apiClient } from '@/lib/apiClient';
interface BiasData {
timeframe: string;
score: number;
direction: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
strength: number;
components: {
ma: number;
rsi: number;
macd: number;
priceAction: number;
};
}
interface MarketBiasResponse {
symbol: string;
biases: BiasData[];
overall: {
score: number;
direction: string;
alignment: 'ALIGNED' | 'MODERATE' | 'LOW' | 'CONFLICTED';
recommendation: string;
};
}
interface MarketBiasIndicatorProps {
symbol: string;
timeframes?: string[];
onBiasChange?: (overall: any) => void;
refreshInterval?: number; // Refresh every N seconds
}
export const MarketBiasIndicator: React.FC<MarketBiasIndicatorProps> = ({
symbol,
timeframes = ['1m', '5m', '15m', '1h', '4h', '1d'],
onBiasChange,
refreshInterval = 60 // Default 60 seconds
}) => {
const [biasData, setBiasData] = useState<MarketBiasResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchBias();
// Auto-refresh
const interval = setInterval(() => {
fetchBias();
}, refreshInterval * 1000);
return () => clearInterval(interval);
}, [symbol, refreshInterval]);
const fetchBias = async () => {
setLoading(true);
setError(null);
try {
const response = await apiClient.get(`/api/market-bias/${symbol}`, {
params: { timeframes: timeframes.join(',') }
});
setBiasData(response.data);
onBiasChange?.(response.data.overall);
} catch (err) {
console.error('Error fetching market bias:', err);
setError('Failed to load market bias');
} finally {
setLoading(false);
}
};
const getBiasColor = (direction: string) => {
switch (direction) {
case 'BULLISH': return 'text-green-400 bg-green-500/10 border-green-500/30';
case 'BEARISH': return 'text-red-400 bg-red-500/10 border-red-500/30';
case 'NEUTRAL': return 'text-yellow-400 bg-yellow-500/10 border-yellow-500/30';
default: return 'text-gray-400 bg-gray-500/10 border-gray-500/30';
}
};
const getBiasIcon = (direction: string) => {
switch (direction) {
case 'BULLISH': return <TrendingUp className="w-4 h-4" />;
case 'BEARISH': return <TrendingDown className="w-4 h-4" />;
default: return <Minus className="w-4 h-4" />;
}
};
const getAlignmentIcon = (alignment: string) => {
if (alignment === 'ALIGNED') return <CheckCircle2 className="w-5 h-5 text-green-400" />;
if (alignment === 'CONFLICTED') return <AlertTriangle className="w-5 h-5 text-red-400" />;
return <Minus className="w-5 h-5 text-yellow-400" />;
};
if (loading && !biasData) {
return (
<div className="bg-gray-800/50 rounded-xl border border-gray-700 p-4">
<div className="animate-pulse">Loading market bias...</div>
</div>
);
}
if (error) {
return (
<div className="bg-gray-800/50 rounded-xl border border-red-500/30 p-4">
<span className="text-red-400">{error}</span>
</div>
);
}
if (!biasData) return null;
return (
<div className="bg-gray-800/50 rounded-xl border border-gray-700 p-4 space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-white">Market Bias</h3>
<p className="text-xs text-gray-500">{symbol} - Multi-Timeframe Analysis</p>
</div>
{getAlignmentIcon(biasData.overall.alignment)}
</div>
{/* Overall Bias */}
<div className={`p-3 rounded-lg border ${getBiasColor(biasData.overall.direction)}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
{getBiasIcon(biasData.overall.direction)}
<span className="font-bold">{biasData.overall.direction}</span>
</div>
<span className="text-sm">{biasData.overall.alignment} Alignment</span>
</div>
<p className="text-xs opacity-80">{biasData.overall.recommendation}</p>
</div>
{/* Timeframe Grid */}
<div className="grid grid-cols-3 gap-2">
{biasData.biases.map((bias) => (
<div
key={bias.timeframe}
className={`p-2 rounded-lg border transition-all ${getBiasColor(bias.direction)}`}
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-bold">{bias.timeframe}</span>
{getBiasIcon(bias.direction)}
</div>
{/* Strength Bar */}
<div className="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden mb-1">
<div
className={`h-full ${
bias.direction === 'BULLISH' ? 'bg-green-500' :
bias.direction === 'BEARISH' ? 'bg-red-500' :
'bg-yellow-500'
}`}
style={{ width: `${bias.strength}%` }}
/>
</div>
<div className="text-xs opacity-70">{bias.strength.toFixed(0)}% strength</div>
</div>
))}
</div>
{/* Component Breakdown (Expandable) */}
<details className="text-xs">
<summary className="cursor-pointer text-gray-500 hover:text-gray-400">
View Component Breakdown
</summary>
<div className="mt-2 space-y-2">
{biasData.biases.map((bias) => (
<div key={bias.timeframe} className="p-2 bg-gray-900/50 rounded">
<div className="font-bold mb-1">{bias.timeframe}</div>
<div className="grid grid-cols-4 gap-2 text-xs">
<div>
<span className="text-gray-500">MA:</span>
<span className={bias.components.ma > 0 ? 'text-green-400' : 'text-red-400'}>
{' '}{bias.components.ma.toFixed(2)}
</span>
</div>
<div>
<span className="text-gray-500">RSI:</span>
<span className={bias.components.rsi > 0 ? 'text-green-400' : 'text-red-400'}>
{' '}{bias.components.rsi.toFixed(2)}
</span>
</div>
<div>
<span className="text-gray-500">MACD:</span>
<span className={bias.components.macd > 0 ? 'text-green-400' : 'text-red-400'}>
{' '}{bias.components.macd.toFixed(2)}
</span>
</div>
<div>
<span className="text-gray-500">PA:</span>
<span className={bias.components.priceAction > 0 ? 'text-green-400' : 'text-red-400'}>
{' '}{bias.components.priceAction.toFixed(2)}
</span>
</div>
</div>
</div>
))}
</div>
</details>
</div>
);
};
export default MarketBiasIndicator;
6. Uso e Integración
6.1 Standalone
import MarketBiasIndicator from '@/modules/trading/components/MarketBiasIndicator';
function TradingDashboard() {
return (
<div>
<MarketBiasIndicator
symbol="BTCUSD"
timeframes={['1m', '5m', '15m', '1h', '4h', '1d']}
refreshInterval={60} // Refresh every 60 seconds
onBiasChange={(overall) => {
console.log('Overall bias changed:', overall.direction);
}}
/>
</div>
);
}
6.2 Integration with Trading Alerts
function TradingPage() {
const handleBiasChange = (overall: any) => {
if (overall.alignment === 'ALIGNED') {
showNotification(`${overall.direction} bias aligned across timeframes!`);
}
};
return (
<MarketBiasIndicator
symbol="BTCUSD"
onBiasChange={handleBiasChange}
/>
);
}
7. Roadmap de Implementación
Fase 1: Backend API (1h)
- Crear endpoint
/api/market-bias/:symbol - Implementar cálculo de indicadores (EMA, RSI, MACD)
- Implementar scoring logic
- Test con múltiples símbolos
Fase 2: Frontend Component (1h)
- Crear
MarketBiasIndicator.tsx - Grid de timeframes con color coding
- Overall bias display
- Component breakdown (expandable)
- Auto-refresh logic
8. Referencias
- Componente:
apps/frontend/src/modules/trading/components/MarketBiasIndicator.tsx(a crear) - Backend:
apps/backend/src/services/marketBias.service.ts(a crear) - US:
US-TRD-011-market-bias-indicator.md
Última actualización: 2026-01-25 Responsable: Frontend Lead + ML Engineer