diff --git a/quantmind/preprocess/news.py b/quantmind/preprocess/news.py index 12e7706..33427ce 100644 --- a/quantmind/preprocess/news.py +++ b/quantmind/preprocess/news.py @@ -56,6 +56,9 @@ r"(? tuple[NewsTickerHint, ...]: scan_text = _MARKDOWN_EMPHASIS_RE.sub(r"\1", scan_text) hints: list[NewsTickerHint] = [] seen: set[tuple[str, str | None]] = set() - for match in _EXCHANGE_TICKER_RE.finditer(scan_text): - raw_exchange = " ".join(match.group(1).upper().split()) - exchange = _EXCHANGE_NAMES.get(raw_exchange, raw_exchange) - symbol = match.group(2).upper() + + def append_hint( + *, + symbol: str, + exchange: str, + raw: str, + ) -> None: key = (symbol, exchange) if key in seen: - continue + return seen.add(key) hints.append( NewsTickerHint( symbol=symbol, exchange=exchange, - raw=match.group(0).strip(), + raw=raw, ) ) + + for match in _EXCHANGE_TICKER_RE.finditer(scan_text): + raw_exchange = " ".join(match.group(1).upper().split()) + exchange = _EXCHANGE_NAMES.get(raw_exchange, raw_exchange) + symbol = match.group(2).upper() + append_hint( + symbol=symbol, + exchange=exchange, + raw=match.group(0).strip(), + ) + + tail = re.split(r"[;)]", scan_text[match.end() :], maxsplit=1)[0] + for list_match in _EXCHANGE_TICKER_LIST_MEMBER_RE.finditer(tail): + append_hint( + symbol=list_match.group(1).upper(), + exchange=exchange, + raw=f"{raw_exchange}: {list_match.group(1).upper()}", + ) return tuple(hints) diff --git a/tests/preprocess/test_news.py b/tests/preprocess/test_news.py index 7a0b634..64d9cbd 100644 --- a/tests/preprocess/test_news.py +++ b/tests/preprocess/test_news.py @@ -117,6 +117,43 @@ def test_exchange_ticker_hints_ignore_markdown_decoration(self): expected, ) + def test_exchange_ticker_hints_capture_shared_prefix_lists(self): + cases = ( + ( + "(OTCID: QVCAQ, QVCGQ, QVCPQ)", + [], + ), + ( + "(NYSE: EVEX, EVEXW; B3: EVEB31)", + [("EVEX", "NYSE"), ("EVEXW", "NYSE")], + ), + ( + "(NASDAQ: ABC, a leading provider announced results)", + [("ABC", "NASDAQ")], + ), + ( + "(NYSE: TME and HKEX: 1698)", + [("TME", "NYSE")], + ), + ( + "(NASDAQ: VMAR; TSXV: VMAR)", + [("VMAR", "NASDAQ")], + ), + ( + "(NYSE: ASR; BMV: ASUR)", + [("ASR", "NYSE")], + ), + ) + + for text, expected in cases: + with self.subTest(text=text): + hints = extract_exchange_ticker_hints(text) + + self.assertEqual( + [(hint.symbol, hint.exchange) for hint in hints], + expected, + ) + def test_build_sec_news_identity(self): self.assertEqual( build_sec_news_identity(