diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:07:00 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:07:00 +0800 |
| commit | b05e728ae23d088ca6c9ecccf6ed00d2ab6f3839 (patch) | |
| tree | 4846648aa87263742c1181c189287b53c3bf84de /src/FundLab.Api/akshare_collector.py | |
| parent | 27a85d9070abd000245b2a2e6460ddf9fd5eb97e (diff) | |
| download | fund-lab-b05e728ae23d088ca6c9ecccf6ed00d2ab6f3839.tar.gz | |
Add bond full milestone: profile probe, coupon/accrual rules, ledger and point-in-time valuation (3d-29)
Diffstat (limited to 'src/FundLab.Api/akshare_collector.py')
| -rw-r--r-- | src/FundLab.Api/akshare_collector.py | 58 |
1 files changed, 49 insertions, 9 deletions
diff --git a/src/FundLab.Api/akshare_collector.py b/src/FundLab.Api/akshare_collector.py index 2fb837e..9234d81 100644 --- a/src/FundLab.Api/akshare_collector.py +++ b/src/FundLab.Api/akshare_collector.py @@ -150,16 +150,34 @@ def nav(code): } -def bond_maturity_date(code): - # Maturity is a best-effort enrichment: the quote endpoint has no expiry - # date, so a failure here only means maturity_date stays null. +def bond_profile(code): + # Convertible-bond reference data (eastmoney). Best-effort enrichment: any + # failure leaves every descriptive field null rather than fabricating one. + # Plain treasury/policy bonds are not covered by this endpoint, so those + # fields legitimately stay null for them. try: frame = ak.bond_zh_cov_info(symbol=code, indicator="基本信息") if frame is None or frame.empty: - return None - return date_text(frame.iloc[0].get("EXPIRE_DATE")) + return {} + row = frame.iloc[0] + coupon_ir = decimal_text(row.get("COUPON_IR")) + coupon_rate = None + if coupon_ir is not None: + coupon_rate = format(Decimal(coupon_ir) / Decimal(100), "f") + return { + "par_value": decimal_text(row.get("PAR_VALUE")), + "issue_price": decimal_text(row.get("ISSUE_PRICE")), + "value_date": date_text(row.get("VALUE_DATE")), + "listing_date": date_text(row.get("LISTING_DATE")), + "maturity_date": date_text(row.get("EXPIRE_DATE")), + "pay_interest_day": text(row.get("PAY_INTEREST_DAY")), + "coupon_rate": coupon_rate, + "coupon_rate_explain": text(row.get("INTEREST_RATE_EXPLAIN")), + "bond_expire_years": text(row.get("BOND_EXPIRE")), + "rating": text(row.get("RATING")), + } except Exception: - return None + return {} def bond_quote_from_spot(code): @@ -199,7 +217,6 @@ def bond_quote_from_spot(code): "clean_price": price, "accrued_interest": None, "date": None, - "maturity_date": bond_maturity_date(code), "symbol": symbol, } @@ -244,7 +261,6 @@ def bond_quote_from_daily(code): "clean_price": close, "accrued_interest": None, "date": row_date, - "maturity_date": bond_maturity_date(code), "symbol": symbol, } @@ -263,6 +279,19 @@ def bond_quote(code): quote = None if quote is not None: + profile = bond_profile(code) + has_price = quote["price"] is not None + has_profile = profile.get("value_date") is not None or profile.get("maturity_date") is not None + + if has_price and has_profile: + data_status = "complete" + elif has_price: + data_status = "price_only" + elif has_profile: + data_status = "profile_only" + else: + data_status = "missing" + return { "schema_version": SCHEMA_VERSION, "operation": "bond-quote", @@ -276,7 +305,18 @@ def bond_quote(code): "clean_price": quote["clean_price"], "accrued_interest": quote["accrued_interest"], "date": quote["date"], - "maturity_date": quote["maturity_date"], + "publish_date": quote["date"], + "par_value": profile.get("par_value"), + "issue_price": profile.get("issue_price"), + "value_date": profile.get("value_date"), + "listing_date": profile.get("listing_date"), + "maturity_date": profile.get("maturity_date"), + "pay_interest_day": profile.get("pay_interest_day"), + "coupon_rate": profile.get("coupon_rate"), + "coupon_rate_explain": profile.get("coupon_rate_explain"), + "bond_expire_years": profile.get("bond_expire_years"), + "rating": profile.get("rating"), + "data_status": data_status, }, } |
