feat: integrate Alpha Vantage news sentiment + fix technical indicators
- Add alphavantage_service.py for per-article sentiment scores
- Add /stock/{symbol}/news-sentiment endpoint (Alpha Vantage)
- Merge Alpha Vantage data into /stock/{symbol}/sentiment
- Fix technical indicators: use close_ prefixed keys from OpenBB
- Increase historical data to 400 days for SMA_200 calculation
- Add .gitignore, handle Finnhub 403 on premium endpoints
- Add INVEST_API_ALPHAVANTAGE_API_KEY config
This commit is contained in:
@@ -12,7 +12,7 @@ PROVIDER = "yfinance"
|
|||||||
|
|
||||||
|
|
||||||
async def get_technical_indicators(
|
async def get_technical_indicators(
|
||||||
symbol: str, days: int = 200
|
symbol: str, days: int = 400
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Compute key technical indicators for a symbol."""
|
"""Compute key technical indicators for a symbol."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@@ -36,7 +36,7 @@ async def get_technical_indicators(
|
|||||||
try:
|
try:
|
||||||
rsi = await asyncio.to_thread(obb.technical.rsi, data=hist.results, length=14)
|
rsi = await asyncio.to_thread(obb.technical.rsi, data=hist.results, length=14)
|
||||||
rsi_items = _extract_latest(rsi)
|
rsi_items = _extract_latest(rsi)
|
||||||
result["rsi_14"] = rsi_items.get("RSI_14")
|
result["rsi_14"] = rsi_items.get("close_RSI_14")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("RSI calculation failed for %s", symbol, exc_info=True)
|
logger.warning("RSI calculation failed for %s", symbol, exc_info=True)
|
||||||
result["rsi_14"] = None
|
result["rsi_14"] = None
|
||||||
@@ -48,9 +48,9 @@ async def get_technical_indicators(
|
|||||||
)
|
)
|
||||||
macd_items = _extract_latest(macd)
|
macd_items = _extract_latest(macd)
|
||||||
result["macd"] = {
|
result["macd"] = {
|
||||||
"macd": macd_items.get("MACD_12_26_9"),
|
"macd": macd_items.get("close_MACD_12_26_9"),
|
||||||
"signal": macd_items.get("MACDs_12_26_9"),
|
"signal": macd_items.get("close_MACDs_12_26_9"),
|
||||||
"histogram": macd_items.get("MACDh_12_26_9"),
|
"histogram": macd_items.get("close_MACDh_12_26_9"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("MACD calculation failed for %s", symbol, exc_info=True)
|
logger.warning("MACD calculation failed for %s", symbol, exc_info=True)
|
||||||
@@ -63,7 +63,7 @@ async def get_technical_indicators(
|
|||||||
obb.technical.sma, data=hist.results, length=period
|
obb.technical.sma, data=hist.results, length=period
|
||||||
)
|
)
|
||||||
sma_items = _extract_latest(sma)
|
sma_items = _extract_latest(sma)
|
||||||
result[f"sma_{period}"] = sma_items.get(f"SMA_{period}")
|
result[f"sma_{period}"] = sma_items.get(f"close_SMA_{period}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("SMA_%d failed for %s", period, symbol, exc_info=True)
|
logger.warning("SMA_%d failed for %s", period, symbol, exc_info=True)
|
||||||
result[f"sma_{period}"] = None
|
result[f"sma_{period}"] = None
|
||||||
@@ -75,7 +75,7 @@ async def get_technical_indicators(
|
|||||||
obb.technical.ema, data=hist.results, length=period
|
obb.technical.ema, data=hist.results, length=period
|
||||||
)
|
)
|
||||||
ema_items = _extract_latest(ema)
|
ema_items = _extract_latest(ema)
|
||||||
result[f"ema_{period}"] = ema_items.get(f"EMA_{period}")
|
result[f"ema_{period}"] = ema_items.get(f"close_EMA_{period}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("EMA_%d failed for %s", period, symbol, exc_info=True)
|
logger.warning("EMA_%d failed for %s", period, symbol, exc_info=True)
|
||||||
result[f"ema_{period}"] = None
|
result[f"ema_{period}"] = None
|
||||||
@@ -87,9 +87,9 @@ async def get_technical_indicators(
|
|||||||
)
|
)
|
||||||
bb_items = _extract_latest(bbands)
|
bb_items = _extract_latest(bbands)
|
||||||
result["bollinger_bands"] = {
|
result["bollinger_bands"] = {
|
||||||
"upper": bb_items.get("BBU_20_2.0"),
|
"upper": bb_items.get("close_BBU_20_2.0"),
|
||||||
"middle": bb_items.get("BBM_20_2.0"),
|
"middle": bb_items.get("close_BBM_20_2.0"),
|
||||||
"lower": bb_items.get("BBL_20_2.0"),
|
"lower": bb_items.get("close_BBL_20_2.0"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("Bollinger Bands failed for %s", symbol, exc_info=True)
|
logger.warning("Bollinger Bands failed for %s", symbol, exc_info=True)
|
||||||
@@ -108,7 +108,9 @@ def _extract_latest(result: Any) -> dict[str, Any]:
|
|||||||
items = result.results
|
items = result.results
|
||||||
if isinstance(items, list) and items:
|
if isinstance(items, list) and items:
|
||||||
last = items[-1]
|
last = items[-1]
|
||||||
return last.model_dump() if hasattr(last, "model_dump") else vars(last)
|
if hasattr(last, "model_dump"):
|
||||||
|
return last.model_dump()
|
||||||
|
return vars(last) if vars(last) else {}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user