chatvns / app /intermarket.py
liamxdev's picture
Upload folder using huggingface_hub
34b531b verified
Raw
History Blame Contribute Delete
3.9 kB
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from app.config import RAW_DIR
@dataclass(frozen=True)
class IntermarketIndex:
symbol: str
name: str
price: str
change: str
change_percent: str
updated_at: str
@property
def trend(self) -> str:
if self.change.strip().startswith("-") or self.change_percent.strip().startswith("(-"):
return "decreasing"
if self.change.strip().startswith("+") or self.change_percent.strip().startswith("(+"):
return "increasing"
return "flat/unclear"
TARGET_INDEX_SYMBOLS = ["US 30", "Hang Seng", "Shanghai", "Nikkei 225", "KOSPI"]
INTERMARKET_QUERY_TERMS = [
"lien thi truong",
"the gioi",
"thi truong chung",
"vn-index",
"vnindex",
"dow",
"hang seng",
"shanghai",
"shang hai",
"nikkei",
"kospi",
]
def normalize_query(text: str) -> str:
import unicodedata
decomposed = unicodedata.normalize("NFD", str(text).lower())
without_accents = "".join(char for char in decomposed if unicodedata.category(char) != "Mn")
return re.sub(r"\s+", " ", without_accents).strip()
def is_intermarket_query(question: str) -> bool:
normalized = normalize_query(question)
return any(term in normalized for term in INTERMARKET_QUERY_TERMS)
def latest_world_market_text() -> tuple[Path | None, str]:
market_dir = RAW_DIR / "text" / "market"
if not market_dir.exists():
return None, ""
files = sorted(
market_dir.glob("*world_market*.txt"),
key=lambda path: path.stat().st_mtime,
reverse=True,
)
if not files:
return None, ""
path = files[0]
return path, path.read_text(encoding="utf-8", errors="ignore")
def parse_world_market_indices(text: str) -> list[IntermarketIndex]:
lines = [line.strip() for line in str(text).splitlines() if line.strip()]
indexes: list[IntermarketIndex] = []
for symbol in TARGET_INDEX_SYMBOLS:
try:
start = lines.index(symbol)
except ValueError:
continue
row = lines[start : start + 10]
if len(row) < 6:
continue
updated_at = next((value for value in row[6:] if re.match(r"^\d{1,2}:\d{2}:\d{2}$", value)), "")
indexes.append(
IntermarketIndex(
symbol=row[0],
name=row[2],
price=row[3],
change=row[4],
change_percent=row[5],
updated_at=updated_at,
)
)
return indexes
def build_intermarket_context(question: str) -> tuple[str, dict | None]:
if not is_intermarket_query(question):
return "", None
path, text = latest_world_market_text()
indexes = parse_world_market_indices(text)
if not path or not indexes:
return "", None
lines = [
"Intermarket context from 24HMoney world market page.",
(
"Use this as macro/market-background context only, not as a stock ticker. "
"For Vietnam market inference, treat broad weakness in US/Asia indexes as risk-off pressure, "
"and broad strength as supportive sentiment."
),
]
for item in indexes:
lines.append(
f"- {item.symbol} ({item.name}): {item.price}, {item.change} {item.change_percent}, "
f"trend={item.trend}, updated={item.updated_at}"
)
source = {
"score": 1.0,
"ticker": "",
"scope": "market",
"modality": "text",
"structure_type": "intermarket_context",
"heading_path": [],
"source_path": path.relative_to(RAW_DIR.parent).as_posix(),
"url": "https://24hmoney.vn/world",
"artifact_path": path.relative_to(RAW_DIR.parent).as_posix(),
}
return "\n".join(lines), source