Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -111,9 +111,6 @@ def vector_to_score(vec: np.ndarray) -> float:
|
|
111 |
# -----------------------------
|
112 |
def get_decay_factor(num_headlines: int, max_headlines: int = MAX_HEADLINES,
|
113 |
min_decay: float = 0.6, max_decay: float = 0.95) -> float:
|
114 |
-
"""
|
115 |
-
Dynamic decay: more headlines → higher decay → score can approach extremes.
|
116 |
-
"""
|
117 |
ratio = min(num_headlines / max_headlines, 1.0)
|
118 |
return min_decay + ratio * (max_decay - min_decay)
|
119 |
|
@@ -127,31 +124,48 @@ class StocksRequest(BaseModel):
|
|
127 |
|
128 |
@cached(stock_cache)
|
129 |
def analyze_single_stock(stock: str) -> float | str:
|
|
|
130 |
headlines = fetch_headlines(stock)
|
131 |
headlines = [h for h in headlines if h and len(h.strip()) > 10]
|
132 |
|
133 |
if not headlines or len(headlines) < 2:
|
134 |
return "NO_DATA"
|
135 |
|
|
|
136 |
vectors = [headline_score_ensemble(h) for h in headlines]
|
137 |
agg = aggregate_headlines_vectors(vectors)
|
138 |
raw_score = vector_to_score(agg)
|
139 |
|
140 |
-
# Apply
|
141 |
decay = get_decay_factor(len(headlines))
|
142 |
-
|
143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
|
145 |
@app.get("/")
|
146 |
def root():
|
147 |
-
return {"message": "Fin-senti API is
|
148 |
|
149 |
@app.post("/analyze")
|
150 |
def analyze(req: StocksRequest):
|
151 |
results = {}
|
152 |
for stock in req.stocks:
|
153 |
results[stock] = analyze_single_stock(stock)
|
154 |
-
return results
|
155 |
|
156 |
if __name__ == "__main__":
|
157 |
import uvicorn
|
|
|
111 |
# -----------------------------
|
112 |
def get_decay_factor(num_headlines: int, max_headlines: int = MAX_HEADLINES,
|
113 |
min_decay: float = 0.6, max_decay: float = 0.95) -> float:
|
|
|
|
|
|
|
114 |
ratio = min(num_headlines / max_headlines, 1.0)
|
115 |
return min_decay + ratio * (max_decay - min_decay)
|
116 |
|
|
|
124 |
|
125 |
@cached(stock_cache)
|
126 |
def analyze_single_stock(stock: str) -> float | str:
|
127 |
+
# Fetch headlines
|
128 |
headlines = fetch_headlines(stock)
|
129 |
headlines = [h for h in headlines if h and len(h.strip()) > 10]
|
130 |
|
131 |
if not headlines or len(headlines) < 2:
|
132 |
return "NO_DATA"
|
133 |
|
134 |
+
# Ensemble sentiment
|
135 |
vectors = [headline_score_ensemble(h) for h in headlines]
|
136 |
agg = aggregate_headlines_vectors(vectors)
|
137 |
raw_score = vector_to_score(agg)
|
138 |
|
139 |
+
# Apply decay-weighted hybrid
|
140 |
decay = get_decay_factor(len(headlines))
|
141 |
+
hybrid_score = 0.7 * raw_score + 0.3 * (0.5 + decay * (raw_score - 0.5))
|
142 |
+
|
143 |
+
# Market momentum adjustment (5-day price change)
|
144 |
+
try:
|
145 |
+
ticker = yf.Ticker(stock)
|
146 |
+
hist = ticker.history(period="5d")
|
147 |
+
if len(hist) >= 2:
|
148 |
+
change = (hist["Close"].iloc[-1] - hist["Close"].iloc[0]) / hist["Close"].iloc[0]
|
149 |
+
momentum_correction = np.clip(change * 2, -0.2, 0.2) # ±0.2 max
|
150 |
+
final_score = np.clip(hybrid_score + momentum_correction, 0, 1)
|
151 |
+
else:
|
152 |
+
final_score = hybrid_score
|
153 |
+
except Exception as e:
|
154 |
+
print(f"[Market momentum error] {e}")
|
155 |
+
final_score = hybrid_score
|
156 |
+
|
157 |
+
return round(final_score, 2)
|
158 |
|
159 |
@app.get("/")
|
160 |
def root():
|
161 |
+
return {"message": "Fin-senti API is live! Use POST /analyze"}
|
162 |
|
163 |
@app.post("/analyze")
|
164 |
def analyze(req: StocksRequest):
|
165 |
results = {}
|
166 |
for stock in req.stocks:
|
167 |
results[stock] = analyze_single_stock(stock)
|
168 |
+
return {"confidence_scores": results}
|
169 |
|
170 |
if __name__ == "__main__":
|
171 |
import uvicorn
|