22数据源工厂
33根据市场类型返回对应的数据源
44"""
5+ import os
6+ import threading
7+ import time
58from typing import Dict , List , Any , Optional
69
710from app .data_sources .base import BaseDataSource
1013
1114logger = get_logger (__name__ )
1215
16+
17+ def _env_positive_int (key : str , default : int ) -> int :
18+ try :
19+ value = int (os .getenv (key , str (default )))
20+ return value if value > 0 else default
21+ except Exception :
22+ return default
23+
1324_MARKET_ALIASES : Dict [str , str ] = {
1425 "crypto" : "Crypto" ,
1526 "cryptocurrency" : "Crypto" ,
2637 "alpaca" : "USStock" ,
2738 "ibkr" : "USStock" ,
2839 "cnstock" : "CNStock" ,
40+ "cn_stock" : "CNStock" ,
41+ "ashare" : "CNStock" ,
42+ "a_share" : "CNStock" ,
43+ "astock" : "CNStock" ,
44+ "a_stock" : "CNStock" ,
45+ "cn" : "CNStock" ,
46+ "china" : "CNStock" ,
47+ "chinastock" : "CNStock" ,
2948 "hkstock" : "HKStock" ,
49+ "hk_stock" : "HKStock" ,
50+ "hshare" : "HKStock" ,
51+ "h_share" : "HKStock" ,
52+ "hkshare" : "HKStock" ,
53+ "hk_share" : "HKStock" ,
54+ "hk" : "HKStock" ,
55+ "hongkong" : "HKStock" ,
3056 "futures" : "Futures" ,
3157 "moex" : "MOEX" ,
3258 "rustock" : "MOEX" ,
@@ -43,10 +69,29 @@ class DataSourceFactory:
4369 """
4470
4571 _sources : Dict [str , BaseDataSource ] = {}
72+ _noise_lock = threading .Lock ()
73+ _noise_seen : Dict [str , tuple [float , int ]] = {}
74+ _noise_interval_sec = _env_positive_int ("LOG_DEDUPE_INTERVAL_SEC" , 60 )
4675
4776 # Markets that pass through normalize_market unchanged.
4877 _CANONICAL_MARKETS = ("Crypto" , "Forex" , "Futures" , "USStock" , "CNStock" , "HKStock" , "MOEX" )
4978
79+ @classmethod
80+ def _log_limited (cls , level : str , key : str , message : str , * args : Any ) -> None :
81+ """Log noisy market-data failures at most once per key per interval."""
82+ now = time .monotonic ()
83+ with cls ._noise_lock :
84+ last , suppressed = cls ._noise_seen .get (key , (0.0 , 0 ))
85+ if last > 0 and now - last < cls ._noise_interval_sec :
86+ cls ._noise_seen [key ] = (last , suppressed + 1 )
87+ return
88+ cls ._noise_seen [key ] = (now , 0 )
89+
90+ if suppressed :
91+ message = f"{ message } (suppressed { suppressed } duplicate log(s))"
92+ log_fn = getattr (logger , level , logger .warning )
93+ log_fn (message , * args )
94+
5095 @classmethod
5196 def normalize_market (cls , market : str ) -> str :
5297 """
@@ -73,6 +118,14 @@ def normalize_market(cls, market: str) -> str:
73118 key = raw .lower ().replace (" " , "" ).replace ("-" , "_" )
74119 if key in _MARKET_ALIASES :
75120 return _MARKET_ALIASES [key ]
121+ cls ._log_limited (
122+ "warning" ,
123+ f"unknown-market:{ raw } " ,
124+ "DataSourceFactory.normalize_market(): unknown market %r; "
125+ "passing through as-is; downstream get_source() will likely fail." ,
126+ raw ,
127+ )
128+ return raw
76129 logger .warning (
77130 "DataSourceFactory.normalize_market(): unknown market %r — "
78131 "passing through as-is; downstream get_source() will likely fail." ,
@@ -178,16 +231,24 @@ def get_kline(
178231 Returns:
179232 K线数据列表
180233 """
234+ m = cls .normalize_market (market or "" )
181235 try :
182- m = cls .normalize_market (market or "" )
183236 source = cls ._resolve_source (m , exchange_id = exchange_id , market_type = market_type )
184237 klines = source .get_kline (symbol , timeframe , limit , before_time , after_time )
185238
186239 klines .sort (key = lambda x : x ['time' ])
187240
188241 return klines
189242 except Exception as e :
190- logger .error (f"Failed to fetch K-lines { market } :{ symbol } (normalized={ cls .normalize_market (market or '' )} ) - { str (e )} " )
243+ cls ._log_limited (
244+ "error" ,
245+ f"kline:{ m } :{ symbol } :{ type (e ).__name__ } :{ str (e )[:160 ]} " ,
246+ "Failed to fetch K-lines %s:%s (normalized=%s) - %s" ,
247+ market ,
248+ symbol ,
249+ m ,
250+ str (e ),
251+ )
191252 return []
192253
193254 @classmethod
@@ -228,14 +289,26 @@ def get_ticker(cls, market: str, symbol: str, exchange_id: Optional[str] = None,
228289 ...
229290 }
230291 """
292+ m = cls .normalize_market (market or "" )
231293 try :
232- m = cls .normalize_market (market or "" )
233294 source = cls ._resolve_source (m , exchange_id = exchange_id , market_type = market_type )
234295 return source .get_ticker (symbol )
235296 except NotImplementedError :
236- logger .warning (f"get_ticker not implemented for market: { market } " )
297+ cls ._log_limited (
298+ "warning" ,
299+ f"ticker-not-implemented:{ m } " ,
300+ "get_ticker not implemented for market: %s" ,
301+ market ,
302+ )
237303 return {'last' : 0 , 'symbol' : symbol }
238304 except Exception as e :
239- logger .error (f"Failed to fetch ticker { market } :{ symbol } - { str (e )} " )
305+ cls ._log_limited (
306+ "error" ,
307+ f"ticker:{ m } :{ symbol } :{ type (e ).__name__ } :{ str (e )[:160 ]} " ,
308+ "Failed to fetch ticker %s:%s - %s" ,
309+ market ,
310+ symbol ,
311+ str (e ),
312+ )
240313 return {'last' : 0 , 'symbol' : symbol }
241314
0 commit comments