101 lines
6.4 KiB
Python
101 lines
6.4 KiB
Python
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']}%")
|