diegobeyl/backtesting
2
1#!/usr/bin/env python32"""Validación SETRA: descarga histórico por ticker y guarda JSON serializable.3 4Genera `scripts/setra_validation.json` con tipos nativos (float, bool, int, str/None).5"""6import os7import sys8 9# Añadir la raíz del repo al PYTHONPATH para permitir imports relativos10ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))11if ROOT not in sys.path:12 sys.path.insert(0, ROOT)13 14from backtesting_app.core import data_loader15import yfinance as yf16from datetime import datetime, timedelta17import time18import json19import math20 21threshold = 30_000_00022end = datetime.utcnow()23start = end - timedelta(days=60)24 25# localizar clave SETRA26setra_key = None27for k in data_loader.AVAILABLE_SYMBOLS:28 if 'SETRA' in k or 'Alemania' in k:29 setra_key = k30 break31if not setra_key:32 raise SystemExit('No SETRA key found in AVAILABLE_SYMBOLS')33 34tickers = list(data_loader.AVAILABLE_SYMBOLS[setra_key].keys())35results = {}36 37def to_native(o):38 """Convierte tipos comunes de numpy/pandas a tipos nativos JSON-serializables."""39 if o is None:40 return None41 # números42 try:43 if isinstance(o, (int,)):44 return int(o)45 if isinstance(o, (float,)):46 if math.isfinite(o):47 return float(o)48 return None49 except Exception:50 pass51 # numpy / pandas scalars: intentar casting explícito52 try:53 return int(o)54 except Exception:55 pass56 try:57 f = float(o)58 if math.isfinite(f):59 return f60 except Exception:61 pass62 # booleanos (numpy.bool_ también cae aquí)63 try:64 if str(type(o)).lower().find('bool') != -1:65 return bool(o)66 except Exception:67 pass68 # cadenas69 try:70 return str(o)71 except Exception:72 return None73 74# validar tickers uno a uno para evitar timeouts75for t in tickers:76 try:77 tk = yf.Ticker(t)78 hist = tk.history(start=start.strftime('%Y-%m-%d'), end=end.strftime('%Y-%m-%d'), interval='1d')79 if hist is None or hist.empty:80 results[t] = {'status': 'no_data'}81 time.sleep(0.8)82 continue83 hist = hist.tail(30)84 if 'Close' not in hist.columns or 'Volume' not in hist.columns:85 results[t] = {'status': 'no_ohlc'}86 time.sleep(0.8)87 continue88 hist = hist[['Close', 'Volume']].dropna()89 if hist.empty:90 results[t] = {'status': 'no_data'}91 time.sleep(0.8)92 continue93 hist['traded_value'] = hist['Close'] * hist['Volume']94 monthly_value = float(hist['traded_value'].sum())95 info = {}96 try:97 info = tk.info or {}98 except Exception:99 info = {}100 short = info.get('shortName') if isinstance(info, dict) else None101 exch = info.get('exchange') if isinstance(info, dict) else None102 103 # normalizar valores a tipos nativos104 monthly_native = to_native(monthly_value)105 meets = False if monthly_native is None else bool(monthly_native >= threshold)106 entry = {107 'monthly_value': monthly_native,108 'meets_threshold': meets,109 'name': to_native(short),110 'exchange': to_native(exch),111 'rows': to_native(len(hist))112 }113 results[t] = entry114 except Exception as e:115 results[t] = {'error': to_native(str(e))}116 # pequeña pausa entre tickers117 time.sleep(0.8)118 119# guardar resultados en workspace con metadatos120out = {121 'key': setra_key,122 'timestamp': end.isoformat(),123 'total': to_native(len(results)),124 'threshold': to_native(threshold),125 'results': results,126}127 128out_path = 'scripts/setra_validation.json'129with open(out_path, 'w') as f:130 json.dump(out, f, indent=2, ensure_ascii=False)131 132meet = [t for t, v in results.items() if v.get('meets_threshold')]133not_meet = [t for t, v in results.items() if 'meets_threshold' in v and not v.get('meets_threshold')]134no_data = [t for t, v in results.items() if v.get('status')]135 136print('Wrote', out_path)137print('Checked', len(results), 'tickers')138print('Meets:', len(meet))139print('Not meet:', len(not_meet))140print('No data:', len(no_data))141print('Meets sample:', meet[:20])142print('Not meet sample:', not_meet[:20])143print('No data sample:', no_data[:20])144 