StatistaAmeri/backend/bonds.py

101 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import urllib.request
import json
import time
_CACHE = {"data": [], "last_fetch": 0}
CACHE_TTL = 120 # 2 minutes
BONDS_DEF = [
# US Curve
{"symbol": "TVC:US02Y", "code": "US02Y", "title": "آمریکا ۲ ساله", "desc": "اوراق ۲ ساله خزانه‌داری آمریکا", "country": "United States", "flag": "🇺🇸", "tenor": "2Y"},
{"symbol": "TVC:US10Y", "code": "US10Y", "title": "آمریکا ۱۰ ساله", "desc": "اوراق ۱۰ ساله خزانه‌داری آمریکا (بنچ‌مارک)", "country": "United States", "flag": "🇺🇸", "tenor": "10Y"},
{"symbol": "TVC:US30Y", "code": "US30Y", "title": "آمریکا ۳۰ ساله", "desc": "اوراق ۳۰ ساله خزانه‌داری آمریکا", "country": "United States", "flag": "🇺🇸", "tenor": "30Y"},
# Global 10Y Benchmarks
{"symbol": "TVC:DE10Y", "code": "DE10Y", "title": "آلمان ۱۰ ساله (Bund)", "desc": "اوراق ۱۰ ساله دولتی آلمان", "country": "Germany", "flag": "🇩🇪", "tenor": "10Y"},
{"symbol": "TVC:GB10Y", "code": "GB10Y", "title": "بریتانیا ۱۰ ساله (Gilt)", "desc": "اوراق ۱۰ ساله دولتی انگلیس", "country": "United Kingdom", "flag": "🇬🇧", "tenor": "10Y"},
{"symbol": "TVC:JP10Y", "code": "JP10Y", "title": "ژاپن ۱۰ ساله (JGB)", "desc": "اوراق ۱۰ ساله دولتی ژاپن", "country": "Japan", "flag": "🇯🇵", "tenor": "10Y"},
{"symbol": "TVC:CN10Y", "code": "CN10Y", "title": "چین ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی چین", "country": "China", "flag": "🇨🇳", "tenor": "10Y"},
{"symbol": "TVC:FR10Y", "code": "FR10Y", "title": "فرانسه ۱۰ ساله (OAT)", "desc": "اوراق ۱۰ ساله دولتی فرانسه", "country": "France", "flag": "🇫🇷", "tenor": "10Y"},
{"symbol": "TVC:IT10Y", "code": "IT10Y", "title": "ایتالیا ۱۰ ساله (BTP)", "desc": "اوراق ۱۰ ساله دولتی ایتالیا", "country": "Italy", "flag": "🇮🇹", "tenor": "10Y"},
{"symbol": "TVC:CA10Y", "code": "CA10Y", "title": "کانادا ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی کانادا", "country": "Canada", "flag": "🇨🇦", "tenor": "10Y"},
{"symbol": "TVC:AU10Y", "code": "AU10Y", "title": "استرالیا ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی استرالیا", "country": "Australia", "flag": "🇦🇺", "tenor": "10Y"},
{"symbol": "TVC:IN10Y", "code": "IN10Y", "title": "هند ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی هند", "country": "India", "flag": "🇮🇳", "tenor": "10Y"},
{"symbol": "TVC:BR10Y", "code": "BR10Y", "title": "برزیل ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی برزیل", "country": "Brazil", "flag": "🇧🇷", "tenor": "10Y"},
{"symbol": "TVC:TR10Y", "code": "TR10Y", "title": "ترکیه ۱۰ ساله", "desc": "اوراق ۱۰ ساله دولتی ترکیه", "country": "Turkey", "flag": "🇹🇷", "tenor": "10Y"},
{"symbol": "TVC:EU10Y", "code": "EU10Y", "title": "منطقه یورو ۱۰ ساله", "desc": "اوراق ۱۰ ساله بنچ‌مارک اتحادیه اروپا", "country": "Euro Area", "flag": "🇪🇺", "tenor": "10Y"},
]
def get_bonds():
now = time.time()
if _CACHE["data"] and (now - _CACHE["last_fetch"] < CACHE_TTL):
return _CACHE["data"]
url = "https://scanner.tradingview.com/bonds/scan"
tickers = [b["symbol"] for b in BONDS_DEF]
payload = {
"symbols": {"tickers": tickers},
"columns": [
"name", "description", "close", "change", "change_abs", "open", "high", "low",
"Perf.W", "Perf.1M", "Perf.Y", "Perf.YTD"
]
}
try:
req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/json"
})
with urllib.request.urlopen(req, timeout=8) as resp:
data = json.loads(resp.read().decode('utf-8'))
scan_map = {r.get('s'): r.get('d', []) for r in data.get('data', [])}
out = []
for b in BONDS_DEF:
d = scan_map.get(b["symbol"], [])
close = round(d[2], 4) if len(d) > 2 and d[2] is not None else None
change = round(d[3], 2) if len(d) > 3 and d[3] is not None else 0.0
chg_abs = round(d[4], 4) if len(d) > 4 and d[4] is not None else 0.0
open_val = round(d[5], 4) if len(d) > 5 and d[5] is not None else close
high_val = round(d[6], 4) if len(d) > 6 and d[6] is not None else close
low_val = round(d[7], 4) if len(d) > 7 and d[7] is not None else close
perf_w = round(d[8], 2) if len(d) > 8 and d[8] is not None else None
perf_m = round(d[9], 2) if len(d) > 9 and d[9] is not None else None
perf_y = round(d[10], 2) if len(d) > 10 and d[10] is not None else None
if close is not None and open_val is not None:
step = (close - open_val) / 11.0
sparkline = [round(open_val + step * i, 4) for i in range(11)] + [close]
else:
sparkline = [close] if close is not None else []
out.append({
"symbol": b["symbol"],
"code": b["code"],
"title": b["title"],
"desc": b["desc"],
"country": b["country"],
"flag": b["flag"],
"tenor": b["tenor"],
"yield": close,
"change": change,
"change_abs": chg_abs,
"open": open_val,
"high": high_val,
"low": low_val,
"perf_w": perf_w,
"perf_m": perf_m,
"perf_y": perf_y,
"sparkline": sparkline
})
_CACHE["data"] = out
_CACHE["last_fetch"] = now
return out
except Exception as e:
print(f"[bonds] TradingView scanner error: {e}")
return _CACHE["data"] if _CACHE["data"] else []
if __name__ == "__main__":
bonds = get_bonds()
print(f"Loaded {len(bonds)} sovereign bonds from TradingView:")
for b in bonds[:5]:
print(f" {b['flag']} {b['code']:6} | {b['title']:20} | Yield: {b['yield']}% | Chg: {b['change']}%")