research(equity): screener short 'fond/news neg ma prezzo su' (forward, edge non provato)

Goal chiarito: short su titoli con fondamentali/notizie negativi ma prezzo in salita.

Gate dati (v2.0.0): NON backtestabile -> fondamentali da rete = snapshot correnti,
applicarli a prezzi passati = look-ahead. Come la term-structure: solo screener forward.

Costruito screener da dati di rete tokenless: fondamentali strutturati Yahoo
(quoteSummary via crumb: recommendationMean/surprise/revGrowth/sell-skew) + sentiment
headline + momentum 1m/3m. SHORT cand = (fond/news neg) AND prezzo su. Run live oggi:
nessun candidato (mercato in flessione ampia -> gamba 'prezzo su' non scatta).

Intuizione chiave: shortare la FORZA combatte momentum + PEAD; il rialzo 'malgrado'
brutte notizie spesso prezza info che i fondamentali trailing non hanno -> e' la
versione contrarian/rischiosa dell'anomalia. La versione pulita = short su fondamentali
deboli quando il prezzo CONFERMA (scende), non quando diverge.

Eseguibilita': borrow/squeeze/perdita illimitata/PDT 5k/IB instabile/00 -> non deployabile.

- scripts/research/eq_fundnews_short.py (+ forward log data/raw/fundnews_short_screen.parquet)
- tests/test_eq_fundnews_short.py (scoring puro offline)
- docs/diary/2026-06-26-equity-fundnews-short.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-26 19:51:22 +00:00
parent e28176efef
commit a158d0e2ae
3 changed files with 292 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
"""Lock 2026-06-26 (fond/news short screener): scoring puro testabile offline. La strategia NON e'
backtestabile (fondamentali snapshot = look-ahead) -> deliverable = screener forward + verdetto.
Diario docs/diary/2026-06-26-equity-fundnews-short.md."""
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
from eq_fundnews_short import fund_neg_score, headline_sentiment, UNIVERSE # type: ignore
def test_fund_neg_high_for_bad_company():
"""recMean verso sell + surprise negative + ricavi in calo + analisti a sell -> score alto."""
s = fund_neg_score(rec_mean=4.5, surp=[-0.10, -0.05], rev_g=-0.08, sell_skew=0.5)
assert s > 0.7
def test_fund_neg_low_for_healthy_company():
"""recMean buy + surprise positive + ricavi su + nessun sell -> score basso."""
s = fund_neg_score(rec_mean=1.5, surp=[0.10, 0.06], rev_g=0.20, sell_skew=0.0)
assert s < 0.2
def test_fund_neg_none_when_no_data():
assert fund_neg_score(None, None, None, None) is None
def test_headline_sentiment_directions():
neg, n_, p_ = headline_sentiment(["Stock plunges as company warns and cuts guidance"])
pos, _, _ = headline_sentiment(["Shares surge as earnings beat, analysts upgrade"])
neutral, _, _ = headline_sentiment(["Company holds annual shareholder meeting"])
assert neg > 0.6 and pos < 0.4 and neutral == 0.0
def test_universe_nonempty():
assert len(UNIVERSE) >= 10