diff --git a/kennis_streamlit.py b/kennis_streamlit.py index 1628670..161bf9e 100644 --- a/kennis_streamlit.py +++ b/kennis_streamlit.py @@ -37,7 +37,10 @@ st.markdown("---") # ------------------ Sidebar: Inputs & opciones ------------------ st.sidebar.header("Entradas (táctica / swing)") -uploaded = st.sidebar.file_uploader("Sube CSV histórico (opcional). Columnas requeridas: date,cot_long,cot_short,fed_prob,retail_pct,target_up", type=["csv"]) +uploaded = st.sidebar.file_uploader( + "Sube CSV histórico (opcional). Columnas requeridas: date,cot_long,cot_short,fed_prob,retail_pct,target_up", + type=["csv"] +) st.sidebar.markdown("O ingresa la fila live abajo:") par = st.sidebar.selectbox( @@ -71,7 +74,7 @@ def fit_bayesian_logit(X, y, prior_var=0.5, maxiter=200, tol=1e-8): n, p = X.shape beta = np.zeros(p) prior_prec = np.eye(p) / prior_var - H = None + H = np.eye(p) * 1e-6 for i in range(maxiter): eta = X.dot(beta) p_vec = sigmoid(eta) @@ -87,12 +90,15 @@ def fit_bayesian_logit(X, y, prior_var=0.5, maxiter=200, tol=1e-8): beta = beta + delta if np.linalg.norm(delta) < tol: break - cov = np.linalg.inv(H) + # final H may be singular in degenerate data; add jitter defensivo + try: + cov = np.linalg.inv(H) + except np.linalg.LinAlgError: + cov = np.linalg.pinv(H) return beta, cov def to_excel_bytes(df): out = BytesIO() - # pandas con openpyxl como engine — openpyxl debe estar en requirements.txt with pd.ExcelWriter(out, engine="openpyxl") as writer: df.to_excel(writer, index=False, sheet_name="signal") return out.getvalue() @@ -114,8 +120,8 @@ else: fed_demo = np.clip(np.random.normal(60, 12, size=N), 0, 100) retail_demo = np.clip(np.random.normal(50, 12, size=N), 0, 100) net_demo = cot_long_demo - cot_short_demo - z_net_demo = (net_demo - net_demo.mean()) / (net_demo.std() if net_demo.std()>0 else 1) - true_score = 0.8*z_net_demo + 1.5*((fed_demo - 50)/50) - 0.5*((retail_demo - 50)/50) + z_net_demo = (net_demo - net_demo.mean()) / (net_demo.std() if net_demo.std() > 0 else 1) + true_score = 0.8 * z_net_demo + 1.5 * ((fed_demo - 50) / 50) - 0.5 * ((retail_demo - 50) / 50) prob_demo = sigmoid(true_score) target_demo = (np.random.rand(N) < prob_demo).astype(int) df = pd.DataFrame({ @@ -162,7 +168,6 @@ with st.spinner("Calibrando modelo bayesiano táctico (MAP + Laplace)..."): beta_map, cov_post = fit_bayesian_logit(X_train, y_train, prior_var=prior_var) except Exception as e: st.error("Fallo en calibración del modelo: " + str(e)) - # fallback: coeficientes neutros beta_map = np.zeros(len(feature_cols)) cov_post = np.eye(len(feature_cols)) * 1.0 @@ -171,11 +176,15 @@ live = {"cot_long": float(cot_long), "cot_short": float(cot_short), "fed_prob": aug = pd.concat([df.head(1).copy(), pd.DataFrame([live])], ignore_index=True) live_feat = build_features(aug, s_cot_local=s_cot).iloc[-1] x_live = live_feat[feature_cols].fillna(0).values -net = live_feat["net"] +net = float(live_feat["net"]) # Posterior sampling + predicción (defensiva) try: - samples = np.random.multivariate_normal(beta_map, cov_post, size=4000) + # ensure cov_post is symmetric PSD (small jitter) + cov_post = np.array(cov_post) + cov_post = 0.5 * (cov_post + cov_post.T) + jitter = 1e-8 * np.eye(cov_post.shape[0]) + samples = np.random.multivariate_normal(beta_map, cov_post + jitter, size=4000) sample_probs = sigmoid(samples.dot(x_live)) p_mean = float(sample_probs.mean()) p_low = float(np.percentile(sample_probs, 2.5)) @@ -216,4 +225,149 @@ def narrative_tactical_simplificada(p_mean, p_low, p_high, conviction, decision_ Devuelve (texto_simple, texto_tecnico). """ # Frases simples y directas - prob_text = f"P({par} + prob_text = f"P({par} ↑) ≈ {p_mean*100:.1f}% (IC95%: {p_low*100:.1f}%–{p_high*100:.1f}%). Convicción: {conviction}/100." + if decision_flag == "buy": + simple = ( + "Recomendación: **Comprar / Abrir posición larga (táctica)**.\n\n" + "Por qué (simple): la probabilidad de subida es alta y hay soporte estructural. " + "Se sugiere entrar de forma gradual y controlar el riesgo con stops." + ) + elif decision_flag == "sell": + simple = ( + "Recomendación: **Vender / Abrir posición corta (táctica)**.\n\n" + "Por qué (simple): la probabilidad favorece la baja y la convicción es suficiente para una operación táctica. " + "Usa stops ajustados y tamaño reducido." + ) + else: + simple = ( + "Recomendación: **No operar (esperar)**.\n\n" + "Por qué (simple): la probabilidad está cerca de equilibrio y la incertidumbre es elevada; mejor esperar confirmación." + ) + + # Construimos el texto técnico (audit-friendly) — compacto + fed_s = x_live[3] + cot_s = x_live[0] + retail_s = x_live[4] + + tech_lines = [] + tech_lines.append(f"Resumen técnico — activo: {par}") + tech_lines.append(f"- Probabilidad bayesiana P({par} ↑) = {p_mean*100:.1f}% (IC95%: {p_low*100:.1f}%–{p_high*100:.1f}%). Convicción: {conviction}/100.") + # Macro + if fed_s > 0.4: + tech_lines.append("- Macro: FedWatch con sesgo hawkish → favorece USD fuerte en el corto plazo.") + elif fed_s < -0.4: + tech_lines.append("- Macro: FedWatch dovish → presión a la baja para USD.") + else: + tech_lines.append("- Macro: FedWatch neutral/moderado → macro no es el driver dominante ahora.") + # COT + if abs(cot_s) < 0.15: + tech_lines.append("- Posicionamiento (COT): cercano a neutral — riesgo de sobreacumulación limitado.") + elif cot_s >= 0.15: + tech_lines.append("- Posicionamiento (COT): sesgo long entre no-commercials — soporte estructural pero atención a toma de ganancias.") + else: + tech_lines.append("- Posicionamiento (COT): sesgo short — riesgo de short-squeeze si macro gira hawkish.") + # Retail + if retail_s > 0.15: + tech_lines.append("- Retail: minoristas inclinados a comprar — posible señal contraria a corto plazo.") + elif retail_s < -0.15: + tech_lines.append("- Retail: minoristas inclinados a vender — puede reforzar momentum bajista si lo institucional confirma.") + else: + tech_lines.append("- Retail: posicionamiento minorista balanceado — no prevalece contrarian ahora.") + # Contribuciones (compactas) + tech_lines.append("- Descomposición de contribuciones (valores y % firmadas):") + for name, pct, val in zip(feature_cols, contrib_pct, x_live): + sign = "+" if pct >= 0 else "-" + tech_lines.append(f" • {name}: {sign}{abs(pct):.1f}% (valor {val:.3f})") + # Razonamiento y ejecución + if decision_flag == "buy": + tech_lines.append("- Recomendación táctica: acumulación gradual LONG. Ejecución: scale-in 3 tramos; tamaño inicial 0.5–1% notional; stop 1.0–1.5×ATR; R:R ≥ 1:1.5.") + elif decision_flag == "sell": + tech_lines.append("- Recomendación táctica: considerar SHORT táctico o reducir largos. Ejecución: tamaño reducido; stops ajustados; vigilar noticias.") + else: + tech_lines.append("- Recomendación táctica: no operar. Esperar confirmación de flujos o ruptura técnica.") + tech_lines.append("- Nota: COT es semanal (retardo); combine con order-flow intradía y skew de opciones para ejecución.") + + texto_tecnico = "\n".join(tech_lines) + texto_simple = prob_text + "\n\n" + simple + return texto_simple, texto_tecnico + +# Obtener textos simple y técnico +texto_simple, texto_tecnico = narrative_tactical_simplificada( + p_mean, p_low, p_high, conviction, decision_flag, x_live, contrib_pct, feature_cols, par +) + +# ------------------ Interfaz: resultado y narrativa ------------------ +left, right = st.columns([2,3]) +with left: + st.markdown("