Workflow de Desarrollo

Jupyter Lab Setup Optimizado

Configuración de Jupyter Lab

# scripts/setup_jupyter.py
"""
Configuración optimizada de Jupyter Lab para trading development
"""

import os
import json
from pathlib import Path

def setup_jupyter_config():
    """Configurar Jupyter Lab para trading"""
    
    # Configuración de Jupyter Lab
    jupyter_config = {
        "notebook": {
            "Completer": {
                "use_jedi": False  # Mejor autocompletado
            }
        },
        "lab": {
            "shortcuts": [
                {
                    "command": "notebook:run-cell-and-select-next",
                    "keys": ["Shift Enter"],
                    "selector": ".jp-Notebook.jp-mod-editMode"
                },
                {
                    "command": "notebook:run-cell",
                    "keys": ["Ctrl Enter"],
                    "selector": ".jp-Notebook.jp-mod-editMode"
                }
            ]
        }
    }
    
    # Extensiones recomendadas
    extensions = [
        "@jupyter-widgets/jupyterlab-manager",
        "@jupyterlab/toc",
        "@krassowski/jupyterlab-lsp",
        "jupyterlab-plotly",
        "@jupyterlab/git"
    ]
    
    print("⚙️  Configurando Jupyter Lab...")
    
    # Crear directorio de configuración
    jupyter_dir = Path.home() / ".jupyter"
    jupyter_dir.mkdir(exist_ok=True)
    
    # Guardar configuración
    config_file = jupyter_dir / "jupyter_lab_config.json"
    with open(config_file, 'w') as f:
        json.dump(jupyter_config, f, indent=2)
    
    print("✅ Configuración guardada")
    
    # Instalar extensiones
    print("📦 Instalando extensiones...")
    for ext in extensions:
        os.system(f"jupyter labextension install {ext}")
    
    print("🚀 Jupyter Lab configurado para trading!")

# Función para crear template de notebook
def create_notebook_template():
    """Crear template estándar para notebooks de trading"""
    
    template = {
        "cells": [
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "# Trading Analysis Notebook\n",
                    "\n",
                    "**Date:** {date}\n",
                    "**Strategy:** [Strategy Name]\n",
                    "**Symbol(s):** [Symbols]\n",
                    "**Timeframe:** [Timeframe]\n",
                    "\n",
                    "## Objective\n",
                    "[Describe the analysis objective]\n",
                    "\n",
                    "## Key Questions\n",
                    "1. [Question 1]\n",
                    "2. [Question 2]\n",
                    "3. [Question 3]"
                ]
            },
            {
                "cell_type": "code",
                "execution_count": None,
                "metadata": {},
                "source": [
                    "# Standard imports for trading analysis\n",
                    "import pandas as pd\n",
                    "import numpy as np\n",
                    "import matplotlib.pyplot as plt\n",
                    "import seaborn as sns\n",
                    "import yfinance as yf\n",
                    "from datetime import datetime, timedelta\n",
                    "import warnings\n",
                    "warnings.filterwarnings('ignore')\n",
                    "\n",
                    "# Custom modules\n",
                    "import sys\n",
                    "sys.path.append('../src')\n",
                    "\n",
                    "# Configuration\n",
                    "plt.style.use('dark_background')\n",
                    "pd.set_option('display.max_columns', None)\n",
                    "pd.set_option('display.width', None)\n",
                    "\n",
                    "print(\"📊 Trading Analysis Environment Ready\")"
                ]
            },
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "## Data Acquisition"
                ]
            },
            {
                "cell_type": "code",
                "execution_count": None,
                "metadata": {},
                "source": [
                    "# Data acquisition code here\n",
                    "symbol = 'AAPL'  # Replace with target symbol\n",
                    "start_date = '2024-01-01'\n",
                    "end_date = datetime.now().strftime('%Y-%m-%d')\n",
                    "\n",
                    "# Download data\n",
                    "data = yf.download(symbol, start=start_date, end=end_date)\n",
                    "print(f\"📈 Downloaded {len(data)} days of data for {symbol}\")\n",
                    "display(data.head())"
                ]
            },
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "## Exploratory Analysis"
                ]
            },
            {
                "cell_type": "code",
                "execution_count": None,
                "metadata": {},
                "source": [
                    "# Basic statistics and exploration\n",
                    "print(f\"📊 Basic Statistics for {symbol}:\")\n",
                    "print(f\"Period: {data.index[0].date()} to {data.index[-1].date()}\")\n",
                    "print(f\"Trading days: {len(data)}\")\n",
                    "print(f\"Average daily volume: {data['Volume'].mean():,.0f}\")\n",
                    "print(f\"Price range: ${data['Low'].min():.2f} - ${data['High'].max():.2f}\")\n",
                    "\n",
                    "# Calculate returns\n",
                    "data['Returns'] = data['Close'].pct_change()\n",
                    "total_return = (data['Close'].iloc[-1] / data['Close'].iloc[0]) - 1\n",
                    "print(f\"Total return: {total_return:.2%}\")"
                ]
            },
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "## Technical Analysis"
                ]
            },
            {
                "cell_type": "code",
                "execution_count": None,
                "metadata": {},
                "source": [
                    "# Technical indicators code here\n",
                    "pass"
                ]
            },
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "## Strategy Implementation"
                ]
            },
            {
                "cell_type": "code",
                "execution_count": None,
                "metadata": {},
                "source": [
                    "# Strategy code here\n",
                    "pass"
                ]
            },
            {
                "cell_type": "markdown",
                "metadata": {},
                "source": [
                    "## Results and Conclusions\n",
                    "\n",
                    "### Key Findings\n",
                    "- [Finding 1]\n",
                    "- [Finding 2]\n",
                    "- [Finding 3]\n",
                    "\n",
                    "### Next Steps\n",
                    "- [Next step 1]\n",
                    "- [Next step 2]\n",
                    "- [Next step 3]"
                ]
            }
        ],
        "metadata": {
            "kernelspec": {
                "display_name": "Python 3",
                "language": "python",
                "name": "python3"
            },
            "language_info": {
                "codemirror_mode": {
                    "name": "ipython",
                    "version": 3
                },
                "file_extension": ".py",
                "mimetype": "text/x-python",
                "name": "python",
                "nbconvert_exporter": "python",
                "pygments_lexer": "ipython3",
                "version": "3.8.5"
            }
        },
        "nbformat": 4,
        "nbformat_minor": 4
    }
    
    return template

# Función para generar notebook desde template
def generate_notebook(strategy_name, symbol=None):
    """Generar notebook desde template"""
    from datetime import datetime
    
    template = create_notebook_template()
    
    # Personalizar template
    date_str = datetime.now().strftime('%Y-%m-%d')
    template['cells'][0]['source'][0] = template['cells'][0]['source'][0].format(date=date_str)
    
    if strategy_name:
        template['cells'][0]['source'][0] = template['cells'][0]['source'][0].replace(
            '[Strategy Name]', strategy_name
        )
    
    if symbol:
        template['cells'][0]['source'][0] = template['cells'][0]['source'][0].replace(
            '[Symbols]', symbol
        )
        template['cells'][3]['source'][0] = template['cells'][3]['source'][0].replace(
            "'AAPL'", f"'{symbol}'"
        )
    
    # Guardar notebook
    filename = f"notebooks/exploration/{strategy_name.lower().replace(' ', '_')}_{date_str}.ipynb"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    
    with open(filename, 'w') as f:
        json.dump(template, f, indent=2)
    
    print(f"📓 Notebook creado: {filename}")
    return filename

if __name__ == "__main__":
    setup_jupyter_config()
    print("\n📓 Creando notebook de ejemplo...")
    generate_notebook("Gap and Go Analysis", "TSLA")

Git Workflow para Trading

Git Configuration

# scripts/setup_git.sh
#!/bin/bash

echo "🔧 Configurando Git para proyecto de trading..."

# Configuración básica
git config --global user.name "Tu Nombre"
git config --global user.email "tu@email.com"
git config --global init.defaultBranch main

# Configuraciones útiles para desarrollo
git config --global pull.rebase false
git config --global core.autocrlf input
git config --global core.safecrlf true

# Aliases útiles
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual '!gitk'
git config --global alias.lg "log --oneline --decorate --all --graph"

echo "✅ Git configurado exitosamente"

Git Hooks para Trading Projects

# scripts/setup_git_hooks.py
"""
Setup Git hooks específicos para proyectos de trading
"""

import os
import stat
from pathlib import Path

def create_pre_commit_hook():
    """Crear pre-commit hook para validaciones"""
    
    hook_content = '''#!/usr/bin/env python3
"""
Pre-commit hook para validar código de trading
"""

import sys
import subprocess
import os

def check_api_keys():
    """Verificar que no se suban API keys"""
    
    # Patrones a buscar
    dangerous_patterns = [
        r'api_key\s*=\s*["\'][^"\']+["\']',
        r'secret_key\s*=\s*["\'][^"\']+["\']',
        r'password\s*=\s*["\'][^"\']+["\']',
        r'token\s*=\s*["\'][^"\']+["\']'
    ]
    
    # Obtener archivos modificados
    result = subprocess.run(['git', 'diff', '--cached', '--name-only'], 
                          capture_output=True, text=True)
    
    if result.returncode != 0:
        return True
    
    modified_files = result.stdout.strip().split('\\n')
    
    # Revisar archivos Python
    for file in modified_files:
        if file.endswith('.py') and os.path.exists(file):
            with open(file, 'r') as f:
                content = f.read()
                
            for pattern in dangerous_patterns:
                import re
                if re.search(pattern, content, re.IGNORECASE):
                    print(f"❌ PELIGRO: Posible API key en {file}")
                    print("   Usa variables de entorno o archivos .env")
                    return False
    
    return True

def check_notebook_outputs():
    """Verificar que notebooks no tengan outputs"""
    
    result = subprocess.run(['git', 'diff', '--cached', '--name-only'], 
                          capture_output=True, text=True)
    
    if result.returncode != 0:
        return True
    
    modified_files = result.stdout.strip().split('\\n')
    
    for file in modified_files:
        if file.endswith('.ipynb') and os.path.exists(file):
            with open(file, 'r') as f:
                import json
                try:
                    notebook = json.load(f)
                    for cell in notebook.get('cells', []):
                        if cell.get('outputs') or cell.get('execution_count'):
                            print(f"⚠️  Notebook {file} tiene outputs")
                            print("   Ejecuta: jupyter nbconvert --clear-output --inplace *.ipynb")
                            return False
                except json.JSONDecodeError:
                    continue
    
    return True

def run_tests():
    """Ejecutar tests básicos"""
    
    if os.path.exists('tests/'):
        print("🧪 Ejecutando tests...")
        result = subprocess.run(['python', '-m', 'pytest', 'tests/', '-v'], 
                              capture_output=True, text=True)
        
        if result.returncode != 0:
            print("❌ Tests fallaron:")
            print(result.stdout)
            print(result.stderr)
            return False
        else:
            print("✅ Tests pasaron")
    
    return True

def main():
    """Función principal del hook"""
    
    print("🔍 Ejecutando validaciones pre-commit...")
    
    checks = [
        ("API Keys", check_api_keys),
        ("Notebook Outputs", check_notebook_outputs),
        ("Tests", run_tests)
    ]
    
    for check_name, check_func in checks:
        print(f"Verificando {check_name}...")
        if not check_func():
            print(f"❌ Falló verificación: {check_name}")
            sys.exit(1)
    
    print("✅ Todas las verificaciones pasaron")
    sys.exit(0)

if __name__ == "__main__":
    main()
'''
    
    # Crear directorio de hooks
    hooks_dir = Path('.git/hooks')
    hooks_dir.mkdir(exist_ok=True)
    
    # Escribir hook
    hook_file = hooks_dir / 'pre-commit'
    with open(hook_file, 'w') as f:
        f.write(hook_content)
    
    # Hacer ejecutable
    hook_file.chmod(hook_file.stat().st_mode | stat.S_IEXEC)
    
    print("✅ Pre-commit hook instalado")

def create_commit_msg_hook():
    """Crear commit message hook"""
    
    hook_content = '''#!/usr/bin/env python3
"""
Commit message hook para enforcer convenciones
"""

import sys
import re

def validate_commit_message(msg):
    """Validar formato de commit message"""
    
    # Patrón: tipo(scope): descripción
    # Ejemplo: feat(strategy): add VWAP reclaim strategy
    pattern = r'^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}'
    
    if not re.match(pattern, msg):
        print("❌ Formato de commit inválido")
        print("Usar: tipo(scope): descripción")
        print("Tipos: feat, fix, docs, style, refactor, test, chore")
        print("Ejemplo: feat(strategy): add VWAP reclaim strategy")
        return False
    
    return True

def main():
    """Función principal"""
    
    if len(sys.argv) != 2:
        sys.exit(1)
    
    commit_msg_file = sys.argv[1]
    
    with open(commit_msg_file, 'r') as f:
        commit_msg = f.read().strip()
    
    # Ignorar merge commits
    if commit_msg.startswith('Merge'):
        sys.exit(0)
    
    if not validate_commit_message(commit_msg):
        sys.exit(1)
    
    sys.exit(0)

if __name__ == "__main__":
    main()
'''
    
    hooks_dir = Path('.git/hooks')
    hook_file = hooks_dir / 'commit-msg'
    
    with open(hook_file, 'w') as f:
        f.write(hook_content)
    
    hook_file.chmod(hook_file.stat().st_mode | stat.S_IEXEC)
    
    print("✅ Commit message hook instalado")

if __name__ == "__main__":
    create_pre_commit_hook()
    create_commit_msg_hook()

Testing Framework

Unit Testing Setup

# tests/test_indicators.py
"""
Tests para indicadores técnicos
"""

import unittest
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import sys
import os

# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))

from indicators.custom_indicators import CustomIndicators

class TestCustomIndicators(unittest.TestCase):
    """Tests para indicadores personalizados"""
    
    def setUp(self):
        """Setup datos de prueba"""
        
        # Crear datos sintéticos
        dates = pd.date_range('2024-01-01', periods=50, freq='D')
        np.random.seed(42)  # Para reproducibilidad
        
        self.test_data = pd.DataFrame({
            'open': 100 + np.random.randn(50).cumsum() * 0.5,
            'high': 102 + np.random.randn(50).cumsum() * 0.5,
            'low': 98 + np.random.randn(50).cumsum() * 0.5,
            'close': 100 + np.random.randn(50).cumsum() * 0.5,
            'volume': np.random.randint(100000, 1000000, 50)
        }, index=dates)
        
        # Asegurar que high >= low
        self.test_data['high'] = np.maximum(self.test_data['high'], self.test_data['low'])
    
    def test_vwap_calculation(self):
        """Test cálculo de VWAP"""
        
        vwap = CustomIndicators.vwap(self.test_data)
        
        # VWAP debe ser un Series
        self.assertIsInstance(vwap, pd.Series)
        
        # VWAP debe tener misma longitud que data
        self.assertEqual(len(vwap), len(self.test_data))
        
        # VWAP no debe tener NaN (excepto primeros valores)
        self.assertTrue(pd.notna(vwap.iloc[-1]))
        
        # VWAP debe estar dentro de rango razonable
        min_price = self.test_data[['high', 'low', 'close']].min().min()
        max_price = self.test_data[['high', 'low', 'close']].max().max()
        
        self.assertTrue(vwap.iloc[-1] >= min_price * 0.9)
        self.assertTrue(vwap.iloc[-1] <= max_price * 1.1)
    
    def test_relative_volume(self):
        """Test cálculo de volumen relativo"""
        
        rvol = CustomIndicators.relative_volume(self.test_data, lookback_periods=10)
        
        # Debe ser Series
        self.assertIsInstance(rvol, pd.Series)
        
        # Debe ser positivo
        self.assertTrue((rvol >= 0).all())
        
        # Valores válidos después del período de lookback
        valid_values = rvol.iloc[10:]
        self.assertTrue(pd.notna(valid_values).all())
    
    def test_gap_percentage(self):
        """Test cálculo de gap percentage"""
        
        gap_pct = CustomIndicators.gap_percentage(self.test_data)
        
        # Debe ser Series
        self.assertIsInstance(gap_pct, pd.Series)
        
        # Primer valor debe ser NaN
        self.assertTrue(pd.isna(gap_pct.iloc[0]))
        
        # Valores posteriores deben ser numéricos
        self.assertTrue(pd.notna(gap_pct.iloc[1:]).all())
    
    def test_money_flow_index(self):
        """Test Money Flow Index"""
        
        mfi = CustomIndicators.money_flow_index(self.test_data, period=10)
        
        # Debe ser Series
        self.assertIsInstance(mfi, pd.Series)
        
        # MFI debe estar entre 0 y 100
        valid_mfi = mfi.dropna()
        self.assertTrue((valid_mfi >= 0).all())
        self.assertTrue((valid_mfi <= 100).all())
    
    def test_true_range(self):
        """Test True Range"""
        
        tr = CustomIndicators.true_range(self.test_data)
        
        # Debe ser Series
        self.assertIsInstance(tr, pd.Series)
        
        # True Range debe ser positivo
        valid_tr = tr.dropna()
        self.assertTrue((valid_tr >= 0).all())
    
    def test_squeeze_indicator(self):
        """Test Squeeze Indicator"""
        
        squeeze = CustomIndicators.squeeze_indicator(self.test_data)
        
        # Debe ser Series de booleanos
        self.assertIsInstance(squeeze, pd.Series)
        self.assertTrue(squeeze.dtype == bool)
    
    def test_consecutive_bars(self):
        """Test consecutive bars counter"""
        
        # Test up bars
        consecutive_up = CustomIndicators.consecutive_bars(self.test_data, 'up')
        self.assertIsInstance(consecutive_up, pd.Series)
        self.assertTrue((consecutive_up >= 0).all())
        
        # Test down bars
        consecutive_down = CustomIndicators.consecutive_bars(self.test_data, 'down')
        self.assertIsInstance(consecutive_down, pd.Series)
        self.assertTrue((consecutive_down >= 0).all())
    
    def test_pivot_points(self):
        """Test pivot points calculation"""
        
        # Traditional method
        pivots_trad = CustomIndicators.pivot_points(self.test_data, 'traditional')
        
        required_keys = ['pivot', 'r1', 'r2', 'r3', 's1', 's2', 's3']
        for key in required_keys:
            self.assertIn(key, pivots_trad)
            self.assertIsInstance(pivots_trad[key], pd.Series)
        
        # Fibonacci method
        pivots_fib = CustomIndicators.pivot_points(self.test_data, 'fibonacci')
        
        for key in required_keys:
            self.assertIn(key, pivots_fib)
            self.assertIsInstance(pivots_fib[key], pd.Series)

class TestDataValidation(unittest.TestCase):
    """Tests para validación de datos"""
    
    def test_data_integrity(self):
        """Test integridad de datos"""
        
        # Datos de ejemplo con problemas
        dates = pd.date_range('2024-01-01', periods=10, freq='D')
        
        # Datos con high < low (inválido)
        bad_data = pd.DataFrame({
            'open': [100] * 10,
            'high': [95] * 10,  # High menor que low
            'low': [98] * 10,
            'close': [97] * 10,
            'volume': [1000] * 10
        }, index=dates)
        
        # Función para validar datos
        def validate_ohlc_data(df):
            """Validar datos OHLC"""
            errors = []
            
            # High debe ser >= low
            if (df['high'] < df['low']).any():
                errors.append("High price less than low price")
            
            # High debe ser >= open y close
            if (df['high'] < df['open']).any():
                errors.append("High price less than open price")
            if (df['high'] < df['close']).any():
                errors.append("High price less than close price")
            
            # Low debe ser <= open y close
            if (df['low'] > df['open']).any():
                errors.append("Low price greater than open price")
            if (df['low'] > df['close']).any():
                errors.append("Low price greater than close price")
            
            # Volume debe ser positivo
            if (df['volume'] <= 0).any():
                errors.append("Volume must be positive")
            
            return errors
        
        # Test que detecta errores
        errors = validate_ohlc_data(bad_data)
        self.assertTrue(len(errors) > 0)

if __name__ == '__main__':
    # Ejecutar tests
    unittest.main(verbosity=2)

Integration Testing

# tests/test_integration.py
"""
Tests de integración para trading system
"""

import unittest
import pandas as pd
import numpy as np
from unittest.mock import Mock, patch
import sys
import os

# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))

class TestTradingSystemIntegration(unittest.TestCase):
    """Tests de integración del sistema completo"""
    
    def test_data_to_signals_pipeline(self):
        """Test pipeline desde datos hasta señales"""
        
        # Mock data provider
        mock_data = pd.DataFrame({
            'open': np.random.randn(100).cumsum() + 100,
            'high': np.random.randn(100).cumsum() + 102,
            'low': np.random.randn(100).cumsum() + 98,
            'close': np.random.randn(100).cumsum() + 100,
            'volume': np.random.randint(100000, 1000000, 100)
        }, index=pd.date_range('2024-01-01', periods=100, freq='D'))
        
        # Ensure valid OHLC
        mock_data['high'] = np.maximum(mock_data['high'], 
                                      np.maximum(mock_data['open'], mock_data['close']))
        mock_data['low'] = np.minimum(mock_data['low'], 
                                     np.minimum(mock_data['open'], mock_data['close']))
        
        # Simular pipeline completo
        def full_pipeline(data):
            """Pipeline completo de datos a señales"""
            
            # 1. Calcular indicadores
            data['sma_20'] = data['close'].rolling(20).mean()
            data['rsi'] = self.calculate_rsi(data['close'])
            data['volume_ratio'] = data['volume'] / data['volume'].rolling(20).mean()
            
            # 2. Generar señales
            signals = pd.DataFrame(index=data.index)
            
            # Señal simple: precio > SMA y RSI < 70 y volumen alto
            signals['long_signal'] = (
                (data['close'] > data['sma_20']) &
                (data['rsi'] < 70) &
                (data['volume_ratio'] > 1.5)
            )
            
            # 3. Calcular retornos de estrategia
            signals['strategy_return'] = (
                signals['long_signal'].shift(1) * data['close'].pct_change()
            )
            
            return signals
        
        # Ejecutar pipeline
        signals = full_pipeline(mock_data)
        
        # Validaciones
        self.assertIsInstance(signals, pd.DataFrame)
        self.assertIn('long_signal', signals.columns)
        self.assertIn('strategy_return', signals.columns)
        
        # Verificar que hay señales
        total_signals = signals['long_signal'].sum()
        self.assertGreater(total_signals, 0)
        
        # Verificar retornos son válidos
        valid_returns = signals['strategy_return'].dropna()
        self.assertTrue(len(valid_returns) > 0)
    
    def calculate_rsi(self, prices, period=14):
        """Helper para calcular RSI"""
        delta = prices.diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
        rs = gain / loss
        return 100 - (100 / (1 + rs))
    
    @patch('requests.get')
    def test_data_provider_fallback(self, mock_get):
        """Test fallback entre data providers"""
        
        # Simular primer provider fallando
        mock_get.side_effect = [
            Exception("Connection failed"),  # Primer intento falla
            Mock(status_code=200, json=lambda: {'price': 150.0})  # Segundo éxito
        ]
        
        # Simular UnifiedDataProvider
        class MockUnifiedProvider:
            def __init__(self):
                self.providers = {
                    'primary': Mock(),
                    'secondary': Mock()
                }
            
            def get_quote(self, symbol):
                try:
                    # Intentar primary
                    raise Exception("Primary failed")
                except:
                    # Fallback to secondary
                    return {'price': 150.0, 'source': 'secondary'}
        
        provider = MockUnifiedProvider()
        result = provider.get_quote('AAPL')
        
        self.assertEqual(result['price'], 150.0)
        self.assertEqual(result['source'], 'secondary')
    
    def test_risk_management_integration(self):
        """Test integración de risk management"""
        
        # Mock portfolio state
        portfolio = {
            'cash': 50000,
            'positions': {
                'AAPL': {'shares': 100, 'avg_price': 150.0},
                'TSLA': {'shares': 50, 'avg_price': 200.0}
            }
        }
        
        # Mock position sizer
        class MockPositionSizer:
            def __init__(self, max_position_size=0.2):
                self.max_position_size = max_position_size
            
            def calculate_position_size(self, signal_strength, current_price, stop_price):
                account_value = 50000 + (100 * 150) + (50 * 200)  # 75000
                risk_amount = account_value * 0.02  # 2% risk
                
                risk_per_share = abs(current_price - stop_price)
                if risk_per_share == 0:
                    return 0
                
                max_shares = int(risk_amount / risk_per_share)
                
                # Apply position size limit
                max_position_value = account_value * self.max_position_size
                max_shares_by_exposure = int(max_position_value / current_price)
                
                return min(max_shares, max_shares_by_exposure)
        
        # Test position sizing
        sizer = MockPositionSizer()
        
        # Strong signal, reasonable stop
        position_size = sizer.calculate_position_size(
            signal_strength=0.8,
            current_price=100.0,
            stop_price=95.0
        )
        
        self.assertGreater(position_size, 0)
        self.assertLessEqual(position_size * 100, 75000 * 0.2)  # Respeta límite de exposición

class TestPerformanceMetrics(unittest.TestCase):
    """Tests para métricas de performance"""
    
    def test_comprehensive_metrics(self):
        """Test cálculo de métricas comprehensivas"""
        
        # Generate synthetic returns
        np.random.seed(42)
        returns = pd.Series(
            np.random.normal(0.001, 0.02, 252),  # Daily returns for 1 year
            index=pd.date_range('2024-01-01', periods=252, freq='D')
        )
        
        def calculate_all_metrics(returns_series):
            """Calcular todas las métricas de performance"""
            
            metrics = {}
            
            # Basic metrics
            metrics['total_return'] = (1 + returns_series).prod() - 1
            metrics['annual_return'] = (1 + returns_series.mean()) ** 252 - 1
            metrics['annual_volatility'] = returns_series.std() * np.sqrt(252)
            
            # Risk-adjusted metrics
            metrics['sharpe_ratio'] = metrics['annual_return'] / metrics['annual_volatility']
            
            # Drawdown metrics
            cumulative = (1 + returns_series).cumprod()
            running_max = cumulative.expanding().max()
            drawdown = (cumulative - running_max) / running_max
            metrics['max_drawdown'] = drawdown.min()
            
            # Win rate
            metrics['win_rate'] = (returns_series > 0).mean()
            
            # VaR
            metrics['var_95'] = returns_series.quantile(0.05)
            
            return metrics
        
        metrics = calculate_all_metrics(returns)
        
        # Validations
        self.assertIn('total_return', metrics)
        self.assertIn('sharpe_ratio', metrics)
        self.assertIn('max_drawdown', metrics)
        self.assertIn('win_rate', metrics)
        
        # Sanity checks
        self.assertGreaterEqual(metrics['win_rate'], 0)
        self.assertLessEqual(metrics['win_rate'], 1)
        self.assertLessEqual(metrics['max_drawdown'], 0)

if __name__ == '__main__':
    unittest.main(verbosity=2)

Continuous Integration Setup

GitHub Actions Workflow

# .github/workflows/trading-ci.yml
name: Trading System CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8, 3.9, '3.10']

    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python $
      uses: actions/setup-python@v3
      with:
        python-version: $
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-cov flake8
    
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    
    - name: Test with pytest
      run: |
        pytest tests/ -v --cov=src --cov-report=xml
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml
        flags: unittests
        name: codecov-umbrella
        fail_ci_if_error: true

  security:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v3
      with:
        python-version: 3.9
    
    - name: Install security tools
      run: |
        pip install bandit safety
    
    - name: Run security scan with bandit
      run: |
        bandit -r src/ -f json -o bandit-report.json
    
    - name: Check for known security vulnerabilities
      run: |
        safety check --json --output safety-report.json

  performance:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v3
      with:
        python-version: 3.9
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest-benchmark
    
    - name: Run performance tests
      run: |
        pytest tests/test_performance.py -v --benchmark-only

Este workflow de desarrollo proporciona una base sólida para desarrollar, testear y mantener sistemas de trading cuantitativos de manera profesional y colaborativa.