| Server IP : 85.155.190.233 / Your IP : 216.73.216.103 Web Server : nginx/1.24.0 System : Linux antigravity-cli 6.8.0-31-generic #31-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 20 00:40:06 UTC 2024 x86_64 User : wp-moonbloom ( 1001) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /proc/2398464/root/opt/Gemini_Swarm_Core/scripts/ |
Upload File : |
#!/usr/bin/env python3
"""
Fetches real-time portfolio quotes from Yahoo Finance and prints a Markdown
snapshot block to stdout. Python3 stdlib only (no third-party deps).
Usage:
python3 fetch_portfolio_data.py
Reads ../portfolio.json (relative to this script's directory).
Output: a Markdown block with a portfolio summary table, meant to be
injected verbatim into the market-scout prompts / report.
On global failure (FX dead AND all quotes dead) prints "(котировки недоступны)"
and exits 0 (never fails the pipeline).
"""
import datetime
import json
import os
import sys
import time
import urllib.error
import urllib.request
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PORTFOLIO_PATH = os.path.join(SCRIPT_DIR, "..", "portfolio.json")
YAHOO_URL = "https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=7d"
FX_URL = "https://api.frankfurter.dev/v1/latest?base=USD&symbols=EUR"
ALPHA_VANTAGE_API_KEY = os.environ.get("ALPHA_VANTAGE_API_KEY", "JDJ4CE5RVBGSGDSS")
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
TIMEOUT = 3
DELAY_BETWEEN_CALLS = 0.1
def _get_json(url: str):
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
return json.loads(resp.read().decode("utf-8"))
def fetch_fx_rate() -> float:
"""USD -> EUR rate. Raises on failure."""
data = _get_json(FX_URL)
return float(data["rates"]["EUR"])
def fetch_quote(symbol: str):
"""
Returns dict: {price, week_change_pct, fifty_two_high, fifty_two_low}
Raises on failure (caller should catch per-ticker).
"""
url = YAHOO_URL.format(symbol=symbol)
data = _get_json(url)
result = data["chart"]["result"][0]
meta = result["meta"]
price = meta.get("regularMarketPrice")
if price is None:
raise ValueError(f"no regularMarketPrice for {symbol}")
fifty_two_high = meta.get("fiftyTwoWeekHigh")
fifty_two_low = meta.get("fiftyTwoWeekLow")
week_change_pct = None
try:
closes = result["indicators"]["quote"][0]["close"]
valid_closes = [c for c in closes if c is not None]
if len(valid_closes) >= 2:
first_close = valid_closes[0]
last_close = valid_closes[-1]
if first_close:
week_change_pct = (last_close - first_close) / first_close * 100.0
except (KeyError, IndexError, TypeError, ZeroDivisionError):
week_change_pct = None
return {
"price": float(price),
"week_change_pct": week_change_pct,
"fifty_two_high": fifty_two_high,
"fifty_two_low": fifty_two_low,
}
def fmt_money_eur(value: float) -> str:
return f"€{value:,.0f}"
def fmt_pct(value: float) -> str:
arrow = "▲" if value >= 0 else "▼"
sign = "+" if value >= 0 else ""
return f"{arrow} {sign}{value:.1f}%"
def fmt_signed_pct(value: float) -> str:
sign = "+" if value >= 0 else ""
return f"{sign}{value:.1f}%"
def load_portfolio():
with open(PORTFOLIO_PATH, "r", encoding="utf-8") as f:
return json.load(f)
def main():
try:
portfolio = load_portfolio()
except Exception as e:
print(f"(котировки недоступны)")
print(f"# fetch_portfolio_data: failed to load portfolio.json: {e}", file=sys.stderr)
return 0
positions = portfolio.get("positions", [])
fx_rate = None
try:
fx_rate = fetch_fx_rate()
except Exception as e:
print(f"# fetch_portfolio_data: FX fetch failed: {e}", file=sys.stderr)
rows = []
any_quote_ok = False
for i, pos in enumerate(positions):
ticker = pos["ticker"]
yahoo_symbol = pos["yahoo"]
shares = pos["shares"]
is_eur = pos.get("eur", False)
if i > 0:
time.sleep(DELAY_BETWEEN_CALLS)
try:
quote = fetch_quote(yahoo_symbol)
price = quote["price"]
week_change_pct = quote["week_change_pct"]
if is_eur:
position_eur = price * shares
else:
if fx_rate is None:
raise ValueError("FX rate unavailable, cannot convert to EUR")
position_eur = price * shares * fx_rate
any_quote_ok = True
rows.append({
"ticker": ticker,
"price": price,
"is_eur": is_eur,
"week_change_pct": week_change_pct,
"position_eur": position_eur,
"ok": True,
})
except Exception as e:
print(f"# fetch_portfolio_data: quote failed for {ticker} ({yahoo_symbol}): {e}", file=sys.stderr)
rows.append({"ticker": ticker, "ok": False})
if not any_quote_ok or fx_rate is None:
print("(котировки недоступны)")
return 0
total_eur = sum(r["position_eur"] for r in rows if r["ok"])
# Weighted portfolio week change (only over positions with a known week change).
weighted_sum = 0.0
weighted_value = 0.0
for r in rows:
if r["ok"] and r.get("week_change_pct") is not None:
weighted_sum += r["week_change_pct"] * r["position_eur"]
weighted_value += r["position_eur"]
portfolio_week_change = (weighted_sum / weighted_value) if weighted_value > 0 else 0.0
# Sort rows by position value desc (failed rows go last).
ok_rows = [r for r in rows if r["ok"]]
failed_rows = [r for r in rows if not r["ok"]]
ok_rows.sort(key=lambda r: r["position_eur"], reverse=True)
# Top gainer / loser by week %.
with_change = [r for r in ok_rows if r.get("week_change_pct") is not None]
top_gainer = max(with_change, key=lambda r: r["week_change_pct"]) if with_change else None
top_loser = min(with_change, key=lambda r: r["week_change_pct"]) if with_change else None
header_arrow = "▲" if portfolio_week_change >= 0 else "▼"
header_sign = "+" if portfolio_week_change >= 0 else ""
lines = []
lines.append(
f"## 💼 Портфель: {fmt_money_eur(total_eur)} "
f"({header_arrow} {header_sign}{portfolio_week_change:.1f}% за неделю)"
)
lines.append("")
lines.append("| Тикер | Цена | Δ неделя | Позиция € | Доля % |")
lines.append("|-------|------|----------|-----------|--------|")
for r in ok_rows:
price_str = f"€{r['price']:.2f}" if r["is_eur"] else f"${r['price']:.2f}"
change_str = fmt_pct(r["week_change_pct"]) if r["week_change_pct"] is not None else "н/д"
position_str = fmt_money_eur(r["position_eur"])
share_pct = (r["position_eur"] / total_eur * 100.0) if total_eur > 0 else 0.0
share_str = f"{share_pct:.1f}%"
lines.append(f"| {r['ticker']} | {price_str} | {change_str} | {position_str} | {share_str} |")
for r in failed_rows:
lines.append(f"| {r['ticker']} | н/д | н/д | н/д | н/д |")
gainer_str = f"{top_gainer['ticker']} {fmt_signed_pct(top_gainer['week_change_pct'])}" if top_gainer else "н/д"
loser_str = f"{top_loser['ticker']} {fmt_signed_pct(top_loser['week_change_pct'])}" if top_loser else "н/д"
today = datetime.date.today().isoformat()
lines.append(
f"Топ-гейнер: {gainer_str} · Топ-лузер: {loser_str} · "
f"USD/EUR: {fx_rate:.4f} · Данные: Yahoo Finance, {today}"
)
print("\n".join(lines))
return 0
if __name__ == "__main__":
sys.exit(main())