summaryrefslogtreecommitdiff
path: root/src/FundLab.Api/akshare_collector.py
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 08:58:52 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 08:58:52 +0800
commit72f466b9b3ea6ece726e6b182cfc36bbd3f6fa15 (patch)
tree8d1bf9285314eb37ce47525e4d0c052fcdf08ca8 /src/FundLab.Api/akshare_collector.py
parentd4b0c26b396bfcf1be029d8db3a1c0fc033a6765 (diff)
downloadfund-lab-72f466b9b3ea6ece726e6b182cfc36bbd3f6fa15.tar.gz
Add stock probe market/adjust/suspension fields and StockRules (3d-30 B1-B2)
Diffstat (limited to 'src/FundLab.Api/akshare_collector.py')
-rw-r--r--src/FundLab.Api/akshare_collector.py61
1 files changed, 47 insertions, 14 deletions
diff --git a/src/FundLab.Api/akshare_collector.py b/src/FundLab.Api/akshare_collector.py
index 9234d81..aed053a 100644
--- a/src/FundLab.Api/akshare_collector.py
+++ b/src/FundLab.Api/akshare_collector.py
@@ -333,17 +333,26 @@ def first_positive_decimal(*values):
def stock_price_from_bid_ask(code):
# Single-symbol realtime snapshot (eastmoney). Returns None when the venue
- # rejects the symbol or carries no valid last price.
+ # rejects the symbol or carries no valid last price. Volume is returned so
+ # callers can flag a suspended session (no trades => zero volume).
frame = ak.stock_bid_ask_em(symbol=code)
if frame is None or frame.empty:
return None
+ price = None
+ volume = None
+
for _, row in frame.iterrows():
item = text(row.get("item"))
if item in ("最新", "最新价"):
- return first_positive_decimal(row.get("value"))
+ price = first_positive_decimal(row.get("value"))
+ elif item in ("总量", "成交量", "总手"):
+ volume = decimal_text(row.get("value"))
- return None
+ if price is None:
+ return None
+
+ return price, volume
def stock_from_snapshot(code):
@@ -407,17 +416,22 @@ def stock_daily_observations(frame, date_key, close_key, volume_key, amount_key)
return rows
-def stock_market_symbol(code):
+def stock_market(code):
+ # A-share exchange mapping from the six-digit code prefix.
if code[0] in ("6", "9"):
- return f"sh{code}"
+ return "sh"
if code[0] in ("0", "3", "2"):
- return f"sz{code}"
+ return "sz"
if code[0] in ("4", "8"):
- return f"bj{code}"
- return f"sh{code}"
+ return "bj"
+ return "sh"
-def stock_daily(code, days_text):
+def stock_market_symbol(code):
+ return f"{stock_market(code)}{code}"
+
+
+def stock_daily(code, days_text, adjust_text=None):
code = text(code)
if code is None or not re.fullmatch(r"\d{6}", code):
raise ValueError("stock code must contain exactly six digits")
@@ -433,10 +447,12 @@ def stock_daily(code, days_text):
if days < 1 or days > 30:
raise ValueError("days must be an integer between 1 and 30")
+ adjust = adjust_text if adjust_text in ("qfq", "hfq") else ""
+
symbol = stock_market_symbol(code)
providers = [
- ((ak.stock_zh_a_hist, {"symbol": code, "period": "daily", "adjust": ""}), "日期", "收盘", "成交量", "成交额"),
- ((ak.stock_zh_a_daily, {"symbol": symbol, "adjust": ""}), "date", "close", "volume", "amount"),
+ ((ak.stock_zh_a_hist, {"symbol": code, "period": "daily", "adjust": adjust}), "日期", "收盘", "成交量", "成交额"),
+ ((ak.stock_zh_a_daily, {"symbol": symbol, "adjust": adjust}), "date", "close", "volume", "amount"),
((ak.stock_zh_a_hist_tx, {"symbol": symbol}), "date", "close", "volume", "amount"),
]
@@ -460,6 +476,8 @@ def stock_daily(code, days_text):
"source_revision": source_revision(),
"collected_at": collected_at(),
"instrument": {"code": code},
+ "market": stock_market(code),
+ "adjust": adjust or "none",
"observations": observations[-days:],
}
@@ -473,11 +491,15 @@ def stock_quote(code):
name = None
price = None
+ volume = None
try:
- price = stock_price_from_bid_ask(code)
+ direct = stock_price_from_bid_ask(code)
except Exception:
- price = None
+ direct = None
+
+ if direct is not None:
+ price, volume = direct
if price is None:
found = stock_from_snapshot(code)
@@ -485,6 +507,13 @@ def stock_quote(code):
raise ValueError(f"no valid quote for {code}")
name, price = found
+ suspended = False
+ if volume is not None:
+ try:
+ suspended = Decimal(volume) == 0
+ except Exception:
+ suspended = False
+
return {
"schema_version": SCHEMA_VERSION,
"operation": "stock-quote",
@@ -496,6 +525,9 @@ def stock_quote(code):
"name": name,
"price": price,
"currency": "CNY",
+ "market": stock_market(code),
+ "adjust": "none",
+ "suspended": suspended,
},
}
@@ -506,6 +538,7 @@ def main():
parser.add_argument("--query")
parser.add_argument("--code")
parser.add_argument("--days")
+ parser.add_argument("--adjust", choices=("none", "qfq", "hfq"))
args = parser.parse_args()
try:
@@ -518,7 +551,7 @@ def main():
elif args.operation == "stock-quote":
payload = stock_quote(args.code)
else:
- payload = stock_daily(args.code, args.days)
+ payload = stock_daily(args.code, args.days, args.adjust)
json.dump(payload, sys.stdout, ensure_ascii=False, separators=(",", ":"))
sys.stdout.write("\n")