Sistema de trading bot - Semanas 1-2 completadas

- Infraestructura de datos completa
- Descarga desde exchanges (CCXT)
- Procesamiento y limpieza de datos
- Almacenamiento en PostgreSQL
- Sistema anti-duplicados
- Script de descarga masiva
- Tests unitarios
- Documentación completa
This commit is contained in:
DaM
2026-01-26 22:16:27 +01:00
parent dcffd9dfad
commit ccd1fb3e42
6 changed files with 674 additions and 158 deletions

View File

@@ -3,7 +3,7 @@
Módulo para almacenamiento persistente de datos en PostgreSQL y caché en Redis
"""
import pandas as pd
from sqlalchemy import create_engine, Column, String, Float, DateTime, Integer, Index
from sqlalchemy import create_engine, Column, String, Float, DateTime, Integer, Index, text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime
@@ -29,12 +29,19 @@ class OHLCV(Base):
low = Column(Float, nullable=False)
close = Column(Float, nullable=False)
volume = Column(Float, nullable=False)
returns = Column(Float, nullable=True) # Retornos simples
log_returns = Column(Float, nullable=True) # Retornos logarítmicos
# Índices compuestos para queries rápidas
__table_args__ = (
Index('idx_symbol_timeframe_timestamp', 'symbol', 'timeframe', 'timestamp'),
Index('idx_timestamp', 'timestamp'),
# CONSTRAINT único: no permitir duplicados
# Una combinación de symbol + timeframe + timestamp debe ser única
{'sqlite_autoincrement': True}
)
# Añadir constraint único manualmente en __init__ de StorageManager
class StorageManager:
"""
@@ -69,6 +76,20 @@ class StorageManager:
# Crear tablas si no existen
Base.metadata.create_all(self.engine)
# Añadir constraint único si no existe (para evitar duplicados)
try:
with self.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE ohlcv
ADD CONSTRAINT unique_ohlcv
UNIQUE (symbol, timeframe, timestamp)
"""))
conn.commit()
log.info("Constraint único añadido a la tabla ohlcv")
except Exception as e:
# El constraint ya existe o hubo error (no crítico)
log.debug(f"Constraint único ya existe o no se pudo añadir: {e}")
# Crear sesión
Session = sessionmaker(bind=self.engine)
self.session = Session()
@@ -117,22 +138,53 @@ class StorageManager:
if df_to_save.columns[0] != 'timestamp':
df_to_save.rename(columns={df_to_save.columns[0]: 'timestamp'}, inplace=True)
# Mantener todas las columnas relevantes
allowed_columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'symbol', 'timeframe', 'returns', 'log_returns']
df_to_save = df_to_save[[col for col in allowed_columns if col in df_to_save.columns]]
# Insertar en lotes para mejor performance
records_saved = 0
records_skipped = 0
for i in range(0, len(df_to_save), batch_size):
batch = df_to_save.iloc[i:i+batch_size]
# Usar to_sql con if_exists='append' y method='multi'
batch.to_sql(
'ohlcv',
self.engine,
if_exists='append',
index=False,
method='multi'
)
records_saved += len(batch)
log.debug(f"Guardados {records_saved}/{len(df_to_save)} registros")
try:
# Usar to_sql con if_exists='append' y method='multi'
batch.to_sql(
'ohlcv',
self.engine,
if_exists='append',
index=False,
method='multi'
)
records_saved += len(batch)
log.debug(f"Guardados {records_saved}/{len(df_to_save)} registros")
except Exception as e:
# Si hay error de duplicados, intentar uno por uno
if 'unique' in str(e).lower() or 'duplicate' in str(e).lower():
log.warning(f"Duplicados detectados en batch, insertando uno por uno...")
for _, row in batch.iterrows():
try:
row.to_frame().T.to_sql(
'ohlcv',
self.engine,
if_exists='append',
index=False
)
records_saved += 1
except Exception:
# Este registro ya existe, saltarlo
records_skipped += 1
continue
else:
# Otro tipo de error, re-lanzar
raise e
if records_skipped > 0:
log.info(f"Saltados {records_skipped} registros duplicados")
log.success(f"Guardados {records_saved} registros exitosamente")
return records_saved