1414
1515import math
1616import os
17+ import threading
1718import time
1819from contextlib import contextmanager
1920from typing import Any , Dict , Generator , Optional
@@ -54,6 +55,9 @@ def _bypass_proxy() -> Generator[None, None, None]:
5455_TD_TIMEOUT = 15
5556_TD_MAX_ATTEMPTS = 2
5657_TD_BACKOFF_SEC = 2.0
58+ _HK_INDICATOR_CACHE_TTL_SEC = 1_800
59+ _HK_INDICATOR_CACHE : Dict [str , tuple [float , Any ]] = {}
60+ _HK_INDICATOR_CACHE_LOCK = threading .Lock ()
5761
5862
5963def _float_clean (x : Any ) -> Optional [float ]:
@@ -68,6 +72,30 @@ def _float_clean(x: Any) -> Optional[float]:
6872 return None
6973
7074
75+ def _hk_financial_indicator_frame (hk5 : str ):
76+ now = time .monotonic ()
77+ with _HK_INDICATOR_CACHE_LOCK :
78+ cached = _HK_INDICATOR_CACHE .get (hk5 )
79+ if cached and cached [0 ] > now :
80+ return cached [1 ].copy (deep = True )
81+ try :
82+ import akshare as ak # type: ignore
83+
84+ with _bypass_proxy ():
85+ frame = ak .stock_hk_financial_indicator_em (symbol = hk5 )
86+ except Exception as exc :
87+ logger .debug ("stock_hk_financial_indicator_em failed %s: %s" , hk5 , exc )
88+ return None
89+ if frame is None or frame .empty :
90+ return frame
91+ with _HK_INDICATOR_CACHE_LOCK :
92+ _HK_INDICATOR_CACHE [hk5 ] = (
93+ now + _HK_INDICATOR_CACHE_TTL_SEC ,
94+ frame .copy (deep = True ),
95+ )
96+ return frame
97+
98+
7199# ---------------------------------------------------------------------------
72100# Twelve Data fundamentals (globally stable, paid)
73101# ---------------------------------------------------------------------------
@@ -426,13 +454,7 @@ def fetch_hk_fundamental_akshare(tencent_code: str) -> Dict[str, Any]:
426454 if not hk5 :
427455 return {}
428456 result : Dict [str , Any ] = {"source" : "akshare_em" }
429- try :
430- import akshare as ak # type: ignore
431- with _bypass_proxy ():
432- df = ak .stock_hk_financial_indicator_em (symbol = hk5 )
433- except Exception as e :
434- logger .debug ("stock_hk_financial_indicator_em failed %s: %s" , hk5 , e )
435- return result
457+ df = _hk_financial_indicator_frame (hk5 )
436458 if df is None or df .empty :
437459 return result
438460 r = df .iloc [0 ]
@@ -445,6 +467,13 @@ def fetch_hk_fundamental_akshare(tencent_code: str) -> Dict[str, Any]:
445467 if mcap is not None :
446468 result ["market_cap" ] = mcap
447469 result ["dividend_yield" ] = _float_clean (r .get ("股息率TTM(%)" ))
470+ result ["revenue" ] = _float_clean (r .get ("营业总收入(元)" )) or _float_clean (r .get ("营业总收入" ))
471+ result ["net_income" ] = _float_clean (r .get ("净利润(元)" )) or _float_clean (r .get ("净利润" ))
472+ result ["book_value" ] = _float_clean (r .get ("每股净资产(元)" ))
473+ result ["shares_outstanding" ] = _float_clean (r .get ("总股本(股)" )) or _float_clean (r .get ("已发行股本(股)" ))
474+ if result .get ("book_value" ) is not None and result .get ("shares_outstanding" ) is not None :
475+ result ["shareholder_equity" ] = result ["book_value" ] * result ["shares_outstanding" ]
476+ result ["revenue_growth" ] = _float_clean (r .get ("营业总收入滚动环比增长(%)" ))
448477 return result
449478
450479
@@ -671,23 +700,40 @@ def fetch_hk_financial_indicators(tencent_code: str) -> Dict[str, Any]:
671700 result : Dict [str , Any ] = {}
672701
673702 try :
674- import akshare as ak # type: ignore
675- with _bypass_proxy ():
676- df = ak .stock_hk_financial_indicator_em (symbol = hk5 )
703+ df = _hk_financial_indicator_frame (hk5 )
677704 if df is None or df .empty :
678705 return result
679706
680707 curr = df .iloc [0 ]
681708 prev = df .iloc [1 ] if len (df ) > 1 else None
682709
683- rev_curr = _float_clean (curr .get ("营业总收入(元)" )) or _float_clean (curr .get ("营业收入(元)" ))
710+ rev_curr = (
711+ _float_clean (curr .get ("营业总收入(元)" ))
712+ or _float_clean (curr .get ("营业收入(元)" ))
713+ or _float_clean (curr .get ("营业总收入" ))
714+ or _float_clean (curr .get ("营业收入" ))
715+ )
716+ direct_growth = _float_clean (curr .get ("营业总收入滚动环比增长(%)" ))
717+ if direct_growth is not None :
718+ result ["revenue_growth" ] = direct_growth
719+ direct_margin = _float_clean (curr .get ("销售净利率(%)" ))
720+ if direct_margin is not None :
721+ result ["profit_margin" ] = direct_margin
684722 if prev is not None :
685- rev_prev = _float_clean (prev .get ("营业总收入(元)" )) or _float_clean (prev .get ("营业收入(元)" ))
686- result ["revenue_growth" ] = _pct_change (rev_curr , rev_prev )
687-
688- net_curr = _float_clean (curr .get ("净利润(元)" ))
689- net_prev = _float_clean (prev .get ("净利润(元)" ))
723+ rev_prev = (
724+ _float_clean (prev .get ("营业总收入(元)" ))
725+ or _float_clean (prev .get ("营业收入(元)" ))
726+ or _float_clean (prev .get ("营业总收入" ))
727+ or _float_clean (prev .get ("营业收入" ))
728+ )
729+ if result .get ("revenue_growth" ) is None :
730+ result ["revenue_growth" ] = _pct_change (rev_curr , rev_prev )
731+
732+ net_curr = _float_clean (curr .get ("净利润(元)" )) or _float_clean (curr .get ("净利润" ))
733+ net_prev = _float_clean (prev .get ("净利润(元)" )) or _float_clean (prev .get ("净利润" ))
690734 result ["earnings_growth" ] = _pct_change (net_curr , net_prev )
735+ else :
736+ result ["earnings_growth" ] = _float_clean (curr .get ("净利润滚动环比增长(%)" ))
691737
692738 de = _float_clean (curr .get ("资产负债率(%)" ))
693739 if de is not None :
@@ -697,7 +743,7 @@ def fetch_hk_financial_indicators(tencent_code: str) -> Dict[str, Any]:
697743 result ["quick_ratio" ] = _float_clean (curr .get ("速动比率" ))
698744
699745 op_cf = _float_clean (curr .get ("每股经营现金流(元)" ))
700- shares = _float_clean (curr .get ("总股本(股)" ))
746+ shares = _float_clean (curr .get ("总股本(股)" )) or _float_clean ( curr . get ( "已发行股本(股)" ))
701747 if op_cf is not None and shares and shares > 0 :
702748 result ["operating_cash_flow" ] = round (op_cf * shares , 2 )
703749
@@ -717,9 +763,7 @@ def fetch_hk_financial_statements(tencent_code: str) -> Dict[str, Any]:
717763 statements : Dict [str , Any ] = {}
718764
719765 try :
720- import akshare as ak # type: ignore
721- with _bypass_proxy ():
722- df = ak .stock_hk_financial_indicator_em (symbol = hk5 )
766+ df = _hk_financial_indicator_frame (hk5 )
723767 if df is None or df .empty :
724768 return {}
725769
@@ -730,8 +774,13 @@ def fetch_hk_financial_statements(tencent_code: str) -> Dict[str, Any]:
730774 )
731775 report_date = str (curr [date_col ])[:10 ] if date_col and curr .get (date_col ) is not None else None
732776
733- rev = _float_clean (curr .get ("营业总收入(元)" )) or _float_clean (curr .get ("营业收入(元)" ))
734- net_income = _float_clean (curr .get ("净利润(元)" ))
777+ rev = (
778+ _float_clean (curr .get ("营业总收入(元)" ))
779+ or _float_clean (curr .get ("营业收入(元)" ))
780+ or _float_clean (curr .get ("营业总收入" ))
781+ or _float_clean (curr .get ("营业收入" ))
782+ )
783+ net_income = _float_clean (curr .get ("净利润(元)" )) or _float_clean (curr .get ("净利润" ))
735784 total_assets = _float_clean (curr .get ("总资产(元)" ))
736785 de_pct = _float_clean (curr .get ("资产负债率(%)" ))
737786
@@ -760,7 +809,7 @@ def fetch_hk_financial_statements(tencent_code: str) -> Dict[str, Any]:
760809 }
761810
762811 op_cf_per_share = _float_clean (curr .get ("每股经营现金流(元)" ))
763- shares = _float_clean (curr .get ("总股本(股)" ))
812+ shares = _float_clean (curr .get ("总股本(股)" )) or _float_clean ( curr . get ( "已发行股本(股)" ))
764813 op_cf = round (op_cf_per_share * shares , 2 ) if op_cf_per_share is not None and shares and shares > 0 else None
765814 statements ["cash_flow" ] = {
766815 "latest_date" : report_date ,
0 commit comments