| id |
title |
type |
project |
version |
status |
priority |
created_date |
updated_date |
author |
| INT-DATA-003 |
Batch de Actualizacion de Activos con Priorizacion |
Integration Specification |
trading-platform |
1.0.0 |
Planificado |
Alta |
2026-01-04 |
2026-01-04 |
Orquestador Agent |
INT-DATA-003: Proceso Batch de Actualizacion de Activos con Priorizacion
Metadata
| Campo |
Valor |
| ID |
INT-DATA-003 |
| Modulo |
Data Service |
| Tipo |
Especificacion de Integracion |
| Version |
1.0.0 |
| Estado |
Planificado |
| Fecha creacion |
2026-01-04 |
| Ultima actualizacion |
2026-01-04 |
| Autor |
Orquestador Agent |
1. Resumen Ejecutivo
Este documento especifica la implementacion de un proceso batch de actualizacion de datos de activos financieros desde la API de Polygon.io/Massive.com hacia la base de datos PostgreSQL del proyecto trading-platform.
Caracteristicas Principales:
- Ejecucion cada 5 minutos - Proceso batch programado
- Priorizacion de activos - Oro (XAU), EURUSD y Bitcoin se actualizan primero
- Rate limiting - 5 llamadas API por minuto (cuenta gratuita)
- Sistema de colas - Activos no prioritarios se encolan para actualizacion diferida
2. Contexto del Requerimiento
2.1 Situacion Actual
El proyecto ya cuenta con:
PolygonClient en apps/data-service/src/providers/polygon_client.py
DataSyncScheduler en apps/data-service/src/services/scheduler.py
- Tabla
trading.symbols para catalogo de activos
- Infraestructura de sync incremental
2.2 Necesidad
Se requiere:
- Actualizar datos de activos respetando el rate limit de 5 calls/min
- Priorizar activos criticos (XAU, EURUSD, BTCUSD) en cada ciclo
- Encolar activos secundarios para actualizacion gradual
- Mantener datos frescos para el ML Engine
2.3 Restricciones
| Restriccion |
Valor |
Nota |
| Rate Limit API |
5 calls/min |
Cuenta gratuita |
| Intervalo Batch |
5 minutos |
Configurable |
| Activos Prioritarios |
3 |
XAU, EURUSD, BTCUSD |
| Tiempo Maximo Ciclo |
60 segundos |
5 calls * 12s spacing |
3. Arquitectura de la Solucion
3.1 Diagrama de Componentes
┌─────────────────────────────────────────────────────────────────────────────┐
│ BATCH ASSET UPDATE SYSTEM │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ SCHEDULER LAYER (APScheduler) │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ PriorityBatchJob │ │ QueueProcessorJob │ │ │
│ │ │ Every 5 minutes │ │ Continuous │ │ │
│ │ │ │ │ │ │ │
│ │ │ 1. XAU/USD │ │ Process queued assets │ │ │
│ │ │ 2. EUR/USD │ │ respecting rate limit │ │ │
│ │ │ 3. BTC/USD │ │ │ │ │
│ │ └──────────┬──────────┘ └──────────────┬──────────────────────┘ │ │
│ └──────────────┼───────────────────────────────┼────────────────────────┘ │
│ │ │ │
│ ┌──────────────▼───────────────────────────────▼────────────────────────┐ │
│ │ SERVICE LAYER │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ PriorityQueueService│ │ AssetUpdateService │ │ │
│ │ │ │ │ │ │ │
│ │ │ • Priority Queue │ │ • Fetch from API │ │ │
│ │ │ • FIFO for others │ │ • Update database │ │ │
│ │ │ • Deduplication │ │ • Emit events │ │ │
│ │ └──────────┬──────────┘ └──────────────┬──────────────────────┘ │ │
│ └──────────────┼───────────────────────────────┼────────────────────────┘ │
│ │ │ │
│ ┌──────────────▼───────────────────────────────▼────────────────────────┐ │
│ │ PROVIDER LAYER │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ RateLimiter │────▶│ PolygonClient │ │ │
│ │ │ 5 req/min │ │ (existing) │ │ │
│ │ └─────────────────────┘ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
└────────────────────────────────────┼────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SYSTEMS │
│ │
│ ┌────────────────────────┐ ┌───────────────────────────────────────┐ │
│ │ Polygon.io API │ │ PostgreSQL │ │
│ │ api.polygon.io │ │ trading.symbols │ │
│ │ │ │ market_data.ohlcv_5m │ │
│ │ Rate: 5 calls/min │ │ data_sources.sync_status │ │
│ └────────────────────────┘ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 Flujo de Ejecucion
Cada 5 minutos
│
▼
┌──────────────────────┐
│ Batch Job Trigger │
└──────────┬───────────┘
│
┌─────────────┴─────────────┐
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Priority Assets │ │ Enqueue Non-Priority │
│ XAU, EURUSD, BTCUSD │ │ to Update Queue │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Rate Limited Fetch │ │ Background Processor │
│ (3 calls, 12s each) │ │ (2 calls remaining) │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Update Database │ │ Process Queue Items │
│ trading.symbols │ │ (gradual sync) │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
└─────────────┬─────────────┘
│
▼
┌───────────────────────┐
│ Emit Update Events │
│ via Redis Pub/Sub │
└───────────────────────┘
4. Especificacion Tecnica
4.1 Configuracion de Activos Prioritarios
# config/priority_assets.py
PRIORITY_ASSETS = [
{
"symbol": "XAUUSD",
"polygon_ticker": "C:XAUUSD",
"asset_type": "forex", # Oro como commodity via forex
"name": "Gold Spot",
"priority": 1,
"update_frequency_seconds": 300 # 5 min
},
{
"symbol": "EURUSD",
"polygon_ticker": "C:EURUSD",
"asset_type": "forex",
"name": "Euro/US Dollar",
"priority": 2,
"update_frequency_seconds": 300
},
{
"symbol": "BTCUSD",
"polygon_ticker": "X:BTCUSD",
"asset_type": "crypto",
"name": "Bitcoin/US Dollar",
"priority": 3,
"update_frequency_seconds": 300
}
]
# Activos secundarios (se encolan)
SECONDARY_ASSETS = [
{"symbol": "ETHUSDT", "polygon_ticker": "X:ETHUSDT", "asset_type": "crypto"},
{"symbol": "GBPUSD", "polygon_ticker": "C:GBPUSD", "asset_type": "forex"},
{"symbol": "USDJPY", "polygon_ticker": "C:USDJPY", "asset_type": "forex"},
{"symbol": "XAGUSD", "polygon_ticker": "C:XAGUSD", "asset_type": "forex"}, # Plata
# ... mas activos
]
4.2 Servicio de Cola de Prioridad
# services/priority_queue_service.py
import asyncio
from datetime import datetime
from typing import Optional, List, Dict
from dataclasses import dataclass, field
from enum import Enum
import heapq
import logging
logger = logging.getLogger(__name__)
class AssetPriority(Enum):
CRITICAL = 1 # XAU, EURUSD, BTC
HIGH = 2 # Major forex pairs
MEDIUM = 3 # Other crypto
LOW = 4 # Indices, commodities
@dataclass(order=True)
class QueuedAsset:
"""Asset in the update queue"""
priority: int
enqueued_at: datetime = field(compare=False)
symbol: str = field(compare=False)
polygon_ticker: str = field(compare=False)
asset_type: str = field(compare=False)
retry_count: int = field(default=0, compare=False)
class PriorityQueueService:
"""
Manages priority queue for asset updates.
Priority levels:
1 - Critical (XAU, EURUSD, BTC) - Always updated first
2 - High - Updated when slots available
3 - Medium - Gradual updates
4 - Low - Best effort
"""
def __init__(self, max_queue_size: int = 1000):
self._queue: List[QueuedAsset] = []
self._in_queue: set = set()
self.max_queue_size = max_queue_size
self._lock = asyncio.Lock()
async def enqueue(
self,
symbol: str,
polygon_ticker: str,
asset_type: str,
priority: AssetPriority = AssetPriority.MEDIUM
) -> bool:
"""Add asset to update queue if not already present"""
async with self._lock:
if symbol in self._in_queue:
logger.debug(f"Asset {symbol} already in queue, skipping")
return False
if len(self._queue) >= self.max_queue_size:
logger.warning(f"Queue full, dropping {symbol}")
return False
item = QueuedAsset(
priority=priority.value,
enqueued_at=datetime.utcnow(),
symbol=symbol,
polygon_ticker=polygon_ticker,
asset_type=asset_type
)
heapq.heappush(self._queue, item)
self._in_queue.add(symbol)
logger.debug(f"Enqueued {symbol} with priority {priority.name}")
return True
async def dequeue(self) -> Optional[QueuedAsset]:
"""Get next asset to update (highest priority first)"""
async with self._lock:
if not self._queue:
return None
item = heapq.heappop(self._queue)
self._in_queue.discard(item.symbol)
return item
async def peek(self) -> Optional[QueuedAsset]:
"""View next item without removing"""
async with self._lock:
return self._queue[0] if self._queue else None
@property
def size(self) -> int:
return len(self._queue)
@property
def is_empty(self) -> bool:
return len(self._queue) == 0
async def get_queue_stats(self) -> Dict:
"""Get queue statistics"""
async with self._lock:
priority_counts = {p.name: 0 for p in AssetPriority}
for item in self._queue:
priority_name = AssetPriority(item.priority).name
priority_counts[priority_name] += 1
return {
"total_items": len(self._queue),
"by_priority": priority_counts,
"oldest_item_age_seconds": (
(datetime.utcnow() - self._queue[0].enqueued_at).total_seconds()
if self._queue else 0
)
}
4.3 Servicio de Actualizacion de Activos
# services/asset_update_service.py
import asyncio
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Any
import logging
from providers.polygon_client import PolygonClient, AssetType, TickerSnapshot
from config.priority_assets import PRIORITY_ASSETS, SECONDARY_ASSETS
from services.priority_queue_service import PriorityQueueService, AssetPriority
logger = logging.getLogger(__name__)
class AssetUpdateService:
"""
Service for updating asset data from Polygon API.
Handles:
- Rate limiting (5 calls/min)
- Priority-based updates
- Database synchronization
- Error handling and retries
"""
def __init__(
self,
polygon_client: PolygonClient,
db_pool,
redis_client,
priority_queue: PriorityQueueService
):
self.polygon = polygon_client
self.db = db_pool
self.redis = redis_client
self.queue = priority_queue
# Rate limiting state
self._calls_this_minute = 0
self._minute_start = datetime.utcnow()
self._rate_limit = 5
async def _wait_for_rate_limit(self):
"""Wait if rate limit reached"""
now = datetime.utcnow()
# Reset counter each minute
if (now - self._minute_start).total_seconds() >= 60:
self._calls_this_minute = 0
self._minute_start = now
# Wait if limit reached
if self._calls_this_minute >= self._rate_limit:
wait_time = 60 - (now - self._minute_start).total_seconds()
if wait_time > 0:
logger.info(f"Rate limit reached, waiting {wait_time:.1f}s")
await asyncio.sleep(wait_time)
self._calls_this_minute = 0
self._minute_start = datetime.utcnow()
self._calls_this_minute += 1
async def update_priority_assets(self) -> Dict[str, Any]:
"""
Update all priority assets (XAU, EURUSD, BTC).
Uses 3 of 5 available API calls.
"""
results = {
"updated": [],
"failed": [],
"api_calls_used": 0
}
for asset in PRIORITY_ASSETS:
try:
await self._wait_for_rate_limit()
# Fetch snapshot from Polygon
snapshot = await self._fetch_asset_snapshot(
asset["polygon_ticker"],
asset["asset_type"]
)
if snapshot:
# Update database
await self._update_asset_in_db(asset, snapshot)
# Publish update event
await self._publish_update_event(asset["symbol"], snapshot)
results["updated"].append(asset["symbol"])
else:
results["failed"].append({
"symbol": asset["symbol"],
"error": "No data returned"
})
results["api_calls_used"] += 1
except Exception as e:
logger.error(f"Error updating {asset['symbol']}: {e}")
results["failed"].append({
"symbol": asset["symbol"],
"error": str(e)
})
return results
async def _fetch_asset_snapshot(
self,
polygon_ticker: str,
asset_type: str
) -> Optional[TickerSnapshot]:
"""Fetch current snapshot from Polygon API"""
try:
if asset_type == "forex":
return await self.polygon.get_snapshot_forex(polygon_ticker)
elif asset_type == "crypto":
return await self.polygon.get_snapshot_crypto(polygon_ticker)
else:
logger.warning(f"Unknown asset type: {asset_type}")
return None
except Exception as e:
logger.error(f"Error fetching {polygon_ticker}: {e}")
return None
async def _update_asset_in_db(
self,
asset: Dict,
snapshot: TickerSnapshot
):
"""Update asset data in PostgreSQL"""
async with self.db.acquire() as conn:
# Update trading.symbols with latest price info
await conn.execute("""
UPDATE trading.symbols
SET
updated_at = NOW(),
-- Store latest prices in a JSONB column if exists
metadata = jsonb_set(
COALESCE(metadata, '{}'),
'{last_update}',
$2::jsonb
)
WHERE symbol = $1
""", asset["symbol"], {
"bid": snapshot.bid,
"ask": snapshot.ask,
"last_price": snapshot.last_price,
"daily_open": snapshot.daily_open,
"daily_high": snapshot.daily_high,
"daily_low": snapshot.daily_low,
"daily_close": snapshot.daily_close,
"timestamp": snapshot.timestamp.isoformat()
})
# Update sync status
await conn.execute("""
INSERT INTO data_sources.data_sync_status
(ticker_id, provider_id, last_sync_timestamp, sync_status, updated_at)
SELECT
s.id,
(SELECT id FROM data_sources.api_providers WHERE code = 'polygon'),
NOW(),
'success',
NOW()
FROM trading.symbols s
WHERE s.symbol = $1
ON CONFLICT (ticker_id, provider_id)
DO UPDATE SET
last_sync_timestamp = NOW(),
sync_status = 'success',
updated_at = NOW()
""", asset["symbol"])
async def _publish_update_event(
self,
symbol: str,
snapshot: TickerSnapshot
):
"""Publish update event via Redis for real-time consumers"""
channel = f"asset:update:{symbol}"
message = {
"symbol": symbol,
"bid": snapshot.bid,
"ask": snapshot.ask,
"last_price": snapshot.last_price,
"timestamp": snapshot.timestamp.isoformat()
}
await self.redis.publish(channel, str(message))
async def process_queued_asset(self) -> Optional[Dict]:
"""
Process next asset from queue.
Uses remaining API calls (2 of 5 per cycle).
"""
item = await self.queue.dequeue()
if not item:
return None
try:
await self._wait_for_rate_limit()
snapshot = await self._fetch_asset_snapshot(
item.polygon_ticker,
item.asset_type
)
if snapshot:
asset = {
"symbol": item.symbol,
"polygon_ticker": item.polygon_ticker,
"asset_type": item.asset_type
}
await self._update_asset_in_db(asset, snapshot)
await self._publish_update_event(item.symbol, snapshot)
return {"symbol": item.symbol, "status": "success"}
else:
# Re-enqueue with lower priority on failure
if item.retry_count < 3:
item.retry_count += 1
await self.queue.enqueue(
item.symbol,
item.polygon_ticker,
item.asset_type,
AssetPriority.LOW
)
return {"symbol": item.symbol, "status": "failed", "requeued": True}
except Exception as e:
logger.error(f"Error processing queued asset {item.symbol}: {e}")
return {"symbol": item.symbol, "status": "error", "error": str(e)}
async def enqueue_secondary_assets(self):
"""Enqueue all secondary assets for gradual update"""
for asset in SECONDARY_ASSETS:
await self.queue.enqueue(
symbol=asset["symbol"],
polygon_ticker=asset["polygon_ticker"],
asset_type=asset["asset_type"],
priority=AssetPriority.MEDIUM
)
logger.info(f"Enqueued {len(SECONDARY_ASSETS)} secondary assets")
4.4 Job de Batch Programado
# services/batch_scheduler.py
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
class AssetBatchScheduler:
"""
Scheduler for batch asset updates.
Schedule:
- Every 5 minutes: Update priority assets (XAU, EURUSD, BTC)
- Continuous: Process queued secondary assets
"""
def __init__(
self,
asset_update_service,
batch_interval_minutes: int = 5
):
self.update_service = asset_update_service
self.batch_interval = batch_interval_minutes
self.scheduler = AsyncIOScheduler()
self._is_running = False
async def start(self):
"""Start the batch scheduler"""
if self._is_running:
logger.warning("Scheduler already running")
return
logger.info("Starting Asset Batch Scheduler")
# Priority assets job - every 5 minutes
self.scheduler.add_job(
self._priority_batch_job,
trigger=IntervalTrigger(minutes=self.batch_interval),
id="priority_assets_batch",
name="Update Priority Assets (XAU, EURUSD, BTC)",
replace_existing=True,
max_instances=1,
next_run_time=datetime.now() # Run immediately on start
)
# Queue processor job - every 15 seconds
self.scheduler.add_job(
self._queue_processor_job,
trigger=IntervalTrigger(seconds=15),
id="queue_processor",
name="Process Queued Secondary Assets",
replace_existing=True,
max_instances=1
)
# Enqueue secondary assets job - every 30 minutes
self.scheduler.add_job(
self._enqueue_secondary_job,
trigger=IntervalTrigger(minutes=30),
id="enqueue_secondary",
name="Enqueue Secondary Assets",
replace_existing=True,
max_instances=1
)
self.scheduler.start()
self._is_running = True
logger.info(f"Scheduler started with {len(self.scheduler.get_jobs())} jobs")
async def _priority_batch_job(self):
"""Job: Update priority assets"""
logger.info("=== Priority Batch Job Started ===")
start_time = datetime.utcnow()
try:
result = await self.update_service.update_priority_assets()
elapsed = (datetime.utcnow() - start_time).total_seconds()
logger.info(
f"Priority batch completed in {elapsed:.1f}s: "
f"{len(result['updated'])} updated, "
f"{len(result['failed'])} failed, "
f"{result['api_calls_used']} API calls"
)
# Log details
for symbol in result['updated']:
logger.debug(f" [OK] {symbol}")
for fail in result['failed']:
logger.warning(f" [FAIL] {fail['symbol']}: {fail['error']}")
except Exception as e:
logger.error(f"Priority batch job failed: {e}", exc_info=True)
async def _queue_processor_job(self):
"""Job: Process queued secondary assets"""
# Only process if we have API calls available
# Since priority uses 3, we have 2 remaining per minute
try:
for _ in range(2): # Process up to 2 items per cycle
result = await self.update_service.process_queued_asset()
if not result:
break # Queue empty
if result['status'] == 'success':
logger.debug(f"Queue: Updated {result['symbol']}")
else:
logger.debug(f"Queue: Failed {result['symbol']}")
except Exception as e:
logger.error(f"Queue processor job failed: {e}")
async def _enqueue_secondary_job(self):
"""Job: Enqueue secondary assets for update"""
try:
await self.update_service.enqueue_secondary_assets()
except Exception as e:
logger.error(f"Enqueue secondary job failed: {e}")
async def stop(self):
"""Stop the scheduler"""
if self._is_running:
self.scheduler.shutdown(wait=True)
self._is_running = False
logger.info("Scheduler stopped")
def get_job_status(self) -> list:
"""Get status of all scheduled jobs"""
return [
{
"id": job.id,
"name": job.name,
"next_run": job.next_run_time.isoformat() if job.next_run_time else None,
"trigger": str(job.trigger)
}
for job in self.scheduler.get_jobs()
]
5. Configuracion
5.1 Variables de Entorno
# .env - Data Service Configuration
# Polygon.io API (Massive.com compatible)
POLYGON_API_KEY=f09bA2V7OG7bHn4HxIT6Xs45ujg_pRXk
POLYGON_BASE_URL=https://api.polygon.io
POLYGON_RATE_LIMIT=5
POLYGON_TIER=free
# Batch Configuration
BATCH_INTERVAL_MINUTES=5
PRIORITY_ASSETS_ENABLED=true
QUEUE_MAX_SIZE=1000
# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=trading_data
DB_USER=trading_user
DB_PASSWORD=trading_dev_2025
# Redis (for queue and events)
REDIS_URL=redis://localhost:6379/0
5.2 Configuracion de Produccion
# config/production.yml
batch:
interval_minutes: 5
priority_assets:
- symbol: XAUUSD
enabled: true
priority: 1
- symbol: EURUSD
enabled: true
priority: 2
- symbol: BTCUSD
enabled: true
priority: 3
rate_limiting:
calls_per_minute: 5
tier: free
upgrade_url: https://polygon.io/pricing
queue:
max_size: 1000
retry_max_attempts: 3
retry_delay_seconds: 60
6. Plan de Migracion a Cuenta de Pago
6.1 Cuando Migrar
Se recomienda migrar a cuenta de pago cuando:
- Se requieran datos en tiempo real (< 15 min delay)
- Se necesiten mas de 5 activos actualizados por minuto
- Se requiera soporte WebSocket para streaming
6.2 Planes Disponibles
| Plan |
Rate Limit |
Precio |
Recomendado Para |
| Free |
5/min |
$0 |
Desarrollo, testing |
| Starter |
Unlimited |
$47/mo |
Produccion basica |
| Developer |
Unlimited |
$99/mo |
Produccion avanzada |
| Advanced |
Unlimited |
$199/mo |
Tiempo real |
6.3 Cambios en Codigo para Plan de Pago
# config/polygon_config.py
POLYGON_CONFIGS = {
"free": {
"rate_limit": 5,
"delay_minutes": 15,
"websocket_enabled": False
},
"starter": {
"rate_limit": None, # Unlimited
"delay_minutes": 0,
"websocket_enabled": False
},
"developer": {
"rate_limit": None,
"delay_minutes": 0,
"websocket_enabled": True
}
}
7. Testing
7.1 Tests Unitarios
# tests/test_priority_queue.py
import pytest
from services.priority_queue_service import PriorityQueueService, AssetPriority
@pytest.mark.asyncio
async def test_priority_ordering():
queue = PriorityQueueService()
await queue.enqueue("ETH", "X:ETH", "crypto", AssetPriority.LOW)
await queue.enqueue("XAU", "C:XAU", "forex", AssetPriority.CRITICAL)
await queue.enqueue("GBP", "C:GBP", "forex", AssetPriority.MEDIUM)
# Should dequeue by priority
item = await queue.dequeue()
assert item.symbol == "XAU"
item = await queue.dequeue()
assert item.symbol == "GBP"
item = await queue.dequeue()
assert item.symbol == "ETH"
@pytest.mark.asyncio
async def test_deduplication():
queue = PriorityQueueService()
result1 = await queue.enqueue("XAU", "C:XAU", "forex")
result2 = await queue.enqueue("XAU", "C:XAU", "forex")
assert result1 is True
assert result2 is False
assert queue.size == 1
7.2 Tests de Integracion
# tests/test_batch_integration.py
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_priority_batch_respects_rate_limit():
"""Verify batch job uses max 3 API calls for priority assets"""
with patch('providers.polygon_client.PolygonClient') as mock_client:
mock_client.get_snapshot_forex = AsyncMock()
mock_client.get_snapshot_crypto = AsyncMock()
service = AssetUpdateService(mock_client, None, None, None)
result = await service.update_priority_assets()
# Should only make 3 calls (XAU, EURUSD, BTC)
assert result['api_calls_used'] <= 3
8. Monitoreo
8.1 Metricas a Rastrear
| Metrica |
Descripcion |
Alerta |
batch.priority.success_rate |
% de actualizaciones exitosas |
< 90% |
batch.priority.latency_ms |
Latencia promedio del batch |
> 30000 |
queue.size |
Tamano de cola de secundarios |
> 500 |
api.rate_limit_waits |
Veces que espero por rate limit |
> 10/hora |
8.2 Endpoint de Health
# api/health.py
@router.get("/health/batch")
async def batch_health(scheduler: AssetBatchScheduler):
jobs = scheduler.get_job_status()
queue_stats = await scheduler.update_service.queue.get_queue_stats()
return {
"status": "healthy",
"jobs": jobs,
"queue": queue_stats,
"last_priority_update": await get_last_priority_update(),
"api_calls_remaining": await get_api_calls_remaining()
}
9. Trazabilidad
9.1 Documentos Relacionados
9.2 Archivos de Codigo
| Archivo |
Proposito |
apps/data-service/src/config/priority_assets.py |
Configuracion de activos |
apps/data-service/src/services/priority_queue_service.py |
Cola de prioridad |
apps/data-service/src/services/asset_update_service.py |
Servicio de actualizacion |
apps/data-service/src/services/batch_scheduler.py |
Scheduler del batch |
10. Historial de Cambios
| Version |
Fecha |
Autor |
Cambios |
| 1.0.0 |
2026-01-04 |
Orquestador Agent |
Creacion inicial |
Estado de Implementacion:
| Aspecto |
Estado |
Notas |
| Documentacion |
✅ |
Este documento |
| Diseno tecnico |
✅ |
Arquitectura definida |
| Codigo Python |
⏳ |
Pendiente |
| Tests |
⏳ |
Pendiente |
| Integracion |
⏳ |
Pendiente |