Download finance/processor.py from Ani-404/sentext: direct link, hf CLI and curl.
- Browser
- Download file 4.09 kB
-
https://huggingface.co/spaces/Ani-404/sentext/resolve/main/finance/processor.py
- Command line
-
hf download hf://spaces/Ani-404/sentext/finance/processor.py
-
curl -L -o processor.py https://huggingface.co/spaces/Ani-404/sentext/resolve/main/finance/processor.py
4.09 kB
| # processor.py | |
| # Processes earnings call transcripts into sentiment features using the | |
| # hosted HuggingFace model (Ani-404/finbert-model), and fetches post-earnings | |
| # stock returns as prediction targets. | |
| import os | |
| import re | |
| from datetime import datetime, timedelta | |
| from functools import lru_cache | |
| import numpy as np | |
| import pandas as pd | |
| import yfinance as yf | |
| # Financial sentiment model. Defaults to the project's fine-tuned model | |
| # (gated on HF, so needs an authorized token or the repo ungated). Override | |
| # with FINANCIAL_MODEL to point at a different/public model. | |
| MODEL_NAME = os.getenv("FINANCIAL_MODEL", "Ani-404/finbert-model") | |
| def get_classifier(): | |
| """Load and cache the financial sentiment classifier pipeline.""" | |
| from transformers import pipeline | |
| return pipeline( | |
| "text-classification", | |
| model=MODEL_NAME, | |
| tokenizer=MODEL_NAME, | |
| ) | |
| def ingest_transcripts(project_root: str) -> pd.DataFrame: | |
| """Load the sample earnings-call transcripts.""" | |
| file_path = os.path.join(project_root, "finance", "sample_transcripts_advanced.csv") | |
| try: | |
| return pd.read_csv(file_path) | |
| except FileNotFoundError: | |
| raise FileNotFoundError(f"Could not find transcript data at '{file_path}'.") | |
| def preprocess_and_split(text): | |
| """Clean text and split it into sentences.""" | |
| if not isinstance(text, str): | |
| return [] | |
| text = re.sub(r"\s+", " ", text).strip() | |
| sentences = re.split(r"(?<=[.!?])\s+", text) | |
| return [s.strip() for s in sentences if s.strip()] | |
| def _label_to_score(label: str) -> int: | |
| """Map a sentiment label to a simplified -1 / 0 / +1 score.""" | |
| l = label.lower() | |
| if "pos" in l: | |
| return 1 | |
| if "neg" in l: | |
| return -1 | |
| return 0 | |
| def score_sentences(sentences, classifier=None): | |
| """Run the classifier on sentences and return a -1/0/+1 score each.""" | |
| if not sentences: | |
| return [] | |
| classifier = classifier or get_classifier() | |
| results = classifier(sentences, truncation=True, max_length=128) | |
| return [_label_to_score(r["label"]) for r in results] | |
| def aggregate_sentiment_features(scores): | |
| """Aggregate sentence scores into summary features.""" | |
| if not scores: | |
| return {"mean_score": 0.0, "positive_ratio": 0.0, "negative_ratio": 0.0} | |
| total_sentences = len(scores) | |
| positive_sentences = sum(1 for s in scores if s > 0) | |
| negative_sentences = sum(1 for s in scores if s < 0) | |
| return { | |
| "mean_score": float(np.mean(scores)), | |
| "positive_ratio": positive_sentences / total_sentences, | |
| "negative_ratio": negative_sentences / total_sentences, | |
| } | |
| def get_stock_returns(ticker, earnings_date_str): | |
| """Fetch 1-day and 5-day returns following an earnings date.""" | |
| try: | |
| earnings_date = datetime.strptime(earnings_date_str, "%Y-%m-%d") | |
| start_date = earnings_date - timedelta(days=1) | |
| end_date = earnings_date + timedelta(days=10) | |
| stock_data = yf.download( | |
| ticker, start=start_date, end=end_date, auto_adjust=True, progress=False | |
| ) | |
| if stock_data.empty: | |
| return None | |
| # Recent yfinance returns MultiIndex columns even for a single ticker. | |
| if isinstance(stock_data.columns, pd.MultiIndex): | |
| stock_data.columns = stock_data.columns.get_level_values(0) | |
| price_on_date_series = stock_data.loc[stock_data.index >= earnings_date] | |
| if price_on_date_series.empty: | |
| return None | |
| price_on_date = float(price_on_date_series.iloc[0]["Close"]) | |
| post_earnings_prices = stock_data.loc[stock_data.index > earnings_date] | |
| if len(post_earnings_prices) < 5: | |
| return None | |
| price_1d_after = float(post_earnings_prices.iloc[0]["Close"]) | |
| price_5d_after = float(post_earnings_prices.iloc[4]["Close"]) | |
| return { | |
| "return_1d": (price_1d_after - price_on_date) / price_on_date, | |
| "return_5d": (price_5d_after - price_on_date) / price_on_date, | |
| } | |
| except Exception: | |
| return None | |