Monitoring convertible-bond redemption and the double-low value
Two things convertible-bond holders fear most: missing a redemption announcement (the premium vanishes overnight) and buying at a high premium. Both are visible up front in one `/v1/bond` call — below is the screening and warning code.
Two numbers first: the double-low value and the premium
The double-low value is the most common "cheapness" metric in the convertible-bond world:
double_low = bond_price + conversion_premium × 100
# Example (sh113052, measured 2026-09-25):
# price (/v1/quote) 112.23
# pure-bond premium 26.35%
# double-low 138.577 ← returned by the API
# Lower double-low = "cheaper" (falls less, still tracks upside)- Premium = (bond price ÷ conversion value − 1) × 100% — how much more you pay than buying the stock
- Conversion value = 100 ÷ conversion price × stock price — what the bond is worth if converted
- Double-low strategy: rank the whole universe by double-low and hold the cheapest basket (traditionally 20 names, rotated)
- Note on our double-low definition: measured sh113052 = `138.577`, while price 112.23 + pure-bond premium 26.35% ≈ 138.58 — the premium term the API uses is not the pure-bond premium, so the two do not simply add up. To compute it yourself, verify the definition against the raw fields once
One request for every term
import requests
KEY = "ct-your-key"
r = requests.get("https://api.ashareapi.com/v1/bond",
headers={"Authorization": "Bearer " + KEY},
params={"code": "sh113052"}, timeout=30)
body = r.json()
print(body["data"][:400]) # NOTE: the payload is Markdown text, not a JSON array- This is the endpoint’s quirk: `data` is a Markdown table string (main terms + detail tables), not a structured array
- Render it directly as a table, or parse it for programmatic use (next section)
- The free `quote` endpoint also prices convertible bonds (`sh113052` form) — terms from `/v1/bond`, price from `/v1/quote`
Parse the Markdown into fields
The main terms live in the first `| item | value |` table — a few lines of regex is enough.
import re, requests
KEY = "ct-your-key"
r = requests.get("https://api.ashareapi.com/v1/bond",
headers={"Authorization": "Bearer " + KEY},
params={"code": "sh113052"}, timeout=30)
md = r.json()["data"]
def parse_terms(md: str) -> dict:
"""Parse the first | item | value | table into a dict."""
out = {}
for line in md.splitlines():
m = re.match(r"\|\s*([^|]+?)\s*\|\s*([^|]*?)\s*\|\s*$", line)
if not m:
continue
k, v = m.group(1), m.group(2)
if k in ("项目", "---") or set(k) <= {"-"}:
continue
out[k] = v
return out
t = parse_terms(md)
print("double-low ", t.get("双低"))
print("premium ", t.get("纯债溢价率"))
print("conv. value ", t.get("转股价值"))
print("call trigger ", t.get("强赎触发价"))
# Measured output (2026-09-25):
# double-low 138.577
# premium 26.35%
# conv. value 88.823
# call trigger 26.169Spotting redemption risk early
Forced redemption is the biggest trap: the issuer redeems at ~100 while the bond trades at 130 — fail to exit and the premium is wiped out.
The check is simple: compare the stock price against the call trigger price.
# Typical trigger: stock closes ≥ conversion price × 130% for 15 of 30 sessions.
# The endpoint gives you that threshold ("强赎触发价"); compare it to the stock price.
trig = float(t["强赎触发价"].replace("元", "").strip()) # 26.169
stock = requests.get("https://api.ashareapi.com/v1/quote",
params={"code": "sh601166"}).json()["data"][0]
price = float(stock["last"])
print(f"stock {price} / trigger {trig} = {price / trig:.1%}")
if price >= trig:
print("WARNING: above the call trigger — watch for an issuer announcement")
elif price >= trig * 0.95:
print("ALERT: within 5% of the trigger — watch list")
else:
print("no redemption pressure")- Important: hitting the trigger price does not mean redemption happens — the issuer may choose not to call. The trigger tells you to go read the announcements
- The real signal is the issuer announcement; the `赎回条款详情` table shows historical call tiers and start dates
- High premium + near trigger is the worst combination: if they call, the premium goes straight to zero
From one bond to a basket
A double-low value means nothing in isolation — it is a ranking metric. Build a bond pool, loop over it, then sort.
import time
codes = ["sh113052", "sz123138", "sh110043"] # your bond pool
rows = []
for c in codes:
try:
b = requests.get("https://api.ashareapi.com/v1/bond",
headers={"Authorization": "Bearer " + KEY},
params={"code": c}, timeout=30).json()
t = parse_terms(b["data"])
rows.append((c, float(t.get("双低", 999)), t.get("纯债溢价率", "")))
except Exception as e:
print(c, "failed:", e)
time.sleep(0.3) # be gentle with rate limits
for c, dl, prem in sorted(rows, key=lambda x: x[1]):
print(f"{c} double-low {dl:>7.2f} premium {prem}")- We do not provide a whole-market bond list — prepare your own pool (or confirm codes one by one via `/v1/search?q=
`) - Cheaper is not always better: a small-cap, low-rated bond near maturity may carry credit risk, not opportunity
- Do not loop intraday: terms do not need second-by-second refresh; once after the close is enough
Common mistakes and boundaries
- Treating `data` as a JSON array → it is a Markdown string; parse it (see above)
- Assuming the API computes the double-low → it returns the field directly, but ranking a pool is your job
- Panicking at the trigger price → trigger ≠ redemption; the announcement governs
- Misreading the putback price → in our measurement the putback trigger showed `0`, meaning the clause does not currently apply — not "the putback price is zero"
- Units → values carry "元" and percentages carry "%"; strip before casting to float
- Rate limits → `/v1/bond` is a paid endpoint; anonymous callers only get the five free endpoints
FAQ
The terms span many fields at different levels (main terms + coupon schedule + call terms + adjustment terms + cash flows). A Markdown table reads naturally and renders directly. For programmatic use, parse the first table with `parse_terms()` as shown.
The endpoint returns it directly. The common industry formula is "bond price + conversion premium × 100", but the premium term this API uses is not the pure-bond premium field (measured sh113052: double-low 138.577, while price 112.23 + pure-bond premium 26.35% ≈ 138.58 — close, but they do not add up exactly). So use the returned value as-is; if you must compute it yourself, verify the definition against the raw fields first.
No — we do not offer a whole-market bond list or bulk export. Prepare a pool of codes, call `/v1/bond` per code, then rank locally. Use `/v1/search?q=
It is the stock price at which the redemption condition is met (usually 130% of the conversion price). The actual rule requires the stock to close at or above it for 15 of 30 sessions, and the issuer may still choose not to call. Treat it as a reminder, not a forecast.
Use `/v1/quote?code=sh113052` — free, no key needed. Convertible bonds share the same code format as stocks. `/v1/bond` covers terms; the price comes from quote.
Last updated: 2026-09-25
Fields and values come from live responses (measured 2026-09-25 on sh113052: double-low 138.577 · pure-bond premium 26.35% · conversion value 88.823 · call trigger 26.169 · putback trigger 0 · underlying 601166; the payload is a Markdown string with four detail tables). The double-low formula follows the common industry convention; redemption rules follow the prospectus and issuer announcements.