From 1c145453f49b30a65c7118d01f1f8685d83ffebc Mon Sep 17 00:00:00 2001 From: Lxy Date: Tue, 24 Feb 2026 20:56:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E4=B8=BB=E5=8A=9B=E5=90=88=E7=BA=A6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/data_fetcher.cpython-311.pyc | Bin 17168 -> 18368 bytes .../__pycache__/tqsdk_adapter.cpython-311.pyc | Bin 13333 -> 16251 bytes .../data/api_adapters/tqsdk_adapter.py | 69 ++++++++++++++++++ .../qihuo_analyzer/data/data_fetcher.py | 30 ++++++++ backend/service_implementation/service/app.py | 22 ++++++ .../service/data/futures_analysis.db | Bin 12312576 -> 12312576 bytes docs/开发文档/合约数据获取逻辑.md | 59 ++++++++++++++- 7 files changed, 179 insertions(+), 1 deletion(-) diff --git a/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc b/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc index 097dd0a98342c3549b8c3a5927077fe9ae77e20e..d4ab71b9534d62a5dc4be4780f39027c82abfea1 100644 GIT binary patch delta 1142 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 delta 1408 zcmZvcTWAwm7{}*iCh0X&t!dJ9*NnE+PGJ?V;8dFj@s8Je+bFhaX4;0PN&F_>pc)sv z>@He`gNTakzR5l=@kR7Wh200yH<4jspIsGHeA@NF?|-JaRXhW~`Of(+=R4ngbN)Mh zb!^t{XtYt|@ow@~{>if6XN`!ty+YHjX|9$Lj+O~IBBMJA`J-4T+T~-hL?q=0ajB!# zHQM2)#z@F%J8NXxocMrdb;!R$e+#R2U*B%5jIrJimnmPY_zmk%7X9*g!s;BoaUJ7s+Nz@+a5PF~ zx9A&V`Oz_7=OEN0A13~YnXDtEMVkR6MYjBt>|=0SndV3%9Tk=9F~L0&O|pac&G}h@oDghZzS&+^R}_Vst-16EM&dPAXhi% z(g4Ejk%iPM6N%7XU>{=-9e|ht4g!bdgH(6Kgcy^rQwyrfzKnSg1kw?J01v1D!$1yj z02fe2P-m)}ai*=T^23#UZrr1Bm=yqZusW8e4@C)>MQc-Lzuw#j*(hcwT-qd`cYfbA zGvPH|i`q;!P}TPvM%~Kbk)x%GOULBNuEpyop*jVe2CBdr;Q#EVJ5KEq4t!NQGrI0o z*EYTP^AG1Yl*t_odhm+!C$U3%$jJP9=f!`Z2l7jwMxOD-;A(jv?j9PJQ1k z5Yk@#D5d_ANVd$~?gU9pmkCMG2KlZ1O$6VFrexY)BsR)XJ2yYc3)?wx6BGddm|KEZ z5wEy>X)mwha`27eQmH@-StJW{ov#71F;zbhYCof?CNMn#=)id(3|s^*15=FOG(rlh zc?frx!W4a-MshnAaIS6>2! w3c5x`zKQ9l-Ttj#Gf{Gm7TkXNRqpS9YF7;rf1`b!SJH2l=KWXoawy&L5vJlTJ^%m! diff --git a/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py b/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py index 06ad9e8..86e86e8 100644 --- a/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py +++ b/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py @@ -324,6 +324,75 @@ class TqSdkAdapter(BaseDataAdapter): print(f"获取所有品种列表失败:{e}") return self._get_mock_all_symbols() + def get_main_contracts(self) -> Dict[str, str]: + """获取主力合约 + + Returns: + Dict[str, str]: 品种代码到主力合约代码的映射 + """ + try: + if TQSDK_AVAILABLE and self.api: + # 使用TQSDK的query_quotes方法获取主力合约 + main_contracts = {} + + # 尝试获取不同类别的主力合约 + for ins_class in ['FUTURE']: + try: + # 查询主力合约 + quotes = self.api.query_quotes(ins_class=ins_class) + + # 等待数据准备就绪 + import time + start_time = time.time() + timeout = 5 # 5秒超时 + + while True: + if quotes: + break + if time.time() - start_time > timeout: + print("获取主力合约数据超时") + break + time.sleep(0.1) + + # 处理获取到的主力合约 + for quote in quotes: + try: + # 获取合约信息 + contract_info = self.api.get_quote(quote) + self.api.wait_update() + + if hasattr(contract_info, 'underlying_symbol') and hasattr(contract_info, 'instrument_id'): + underlying_symbol = contract_info.underlying_symbol + instrument_id = contract_info.instrument_id + main_contracts[underlying_symbol] = instrument_id + except Exception as e: + print(f"处理主力合约 {quote} 失败:{e}") + except Exception as e: + print(f"获取 {ins_class} 类别的主力合约失败:{e}") + + print(f"获取到主力合约:{main_contracts}") + return main_contracts + else: + # 返回模拟数据 + print("无法获取真实主力合约数据,使用模拟数据") + return self._get_mock_main_contracts() + except Exception as e: + print(f"获取主力合约失败:{e}") + return self._get_mock_main_contracts() + + def _get_mock_main_contracts(self) -> Dict[str, str]: + """获取模拟主力合约数据""" + # 模拟主力合约数据 + mock_main_contracts = { + 'AU': 'AU2603', # 黄金 + 'AG': 'AG2603', # 白银 + 'CU': 'CU2603', # 铜 + 'NI': 'NI2603', # 镍 + 'SN': 'SN2603' # 锡 + } + print(f"使用模拟主力合约数据: {mock_main_contracts}") + return mock_main_contracts + def _get_mock_all_symbols(self) -> List[str]: """获取模拟品种列表""" # 返回exchange_map中映射的所有品种 diff --git a/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py b/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py index 3a35b76..0dd3543 100644 --- a/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py +++ b/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py @@ -433,6 +433,36 @@ class DataFetcher: {'symbol': 'AL2603', 'product': 'AL', 'product_name': '铝', 'exchange': 'SHFE', 'month': '2603'}, {'symbol': 'ZN2603', 'product': 'ZN', 'product_name': '锌', 'exchange': 'SHFE', 'month': '2603'} ] + + def get_main_contracts(self) -> Dict[str, str]: + """获取主力合约 + + Returns: + Dict[str, str]: 品种代码到主力合约代码的映射 + """ + try: + # 使用适配器的get_main_contracts方法 + result = self.adapter.get_main_contracts() + if result: + return result + else: + # 如果适配器返回空,使用模拟数据 + print("使用模拟主力合约数据") + return self._get_mock_main_contracts() + except Exception as e: + print(f"获取主力合约失败:{e}") + return self._get_mock_main_contracts() + + def _get_mock_main_contracts(self) -> Dict[str, str]: + """获取模拟主力合约数据""" + # 模拟主力合约数据 + return { + 'AU': 'AU2603', # 黄金 + 'AG': 'AG2603', # 白银 + 'CU': 'CU2603', # 铜 + 'NI': 'NI2603', # 镍 + 'SN': 'SN2603' # 锡 + } # 导入numpy diff --git a/backend/service_implementation/service/app.py b/backend/service_implementation/service/app.py index 6407599..dc3b923 100644 --- a/backend/service_implementation/service/app.py +++ b/backend/service_implementation/service/app.py @@ -46,6 +46,28 @@ def get_contracts(): except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 +# 主力合约获取接口 +@app.route('/api/main-contracts', methods=['GET']) +def get_main_contracts(): + try: + print("正在获取主力合约数据...") + main_contracts = data_fetcher.get_main_contracts() + print(f"获取到主力合约:{main_contracts}") + + # 转换为主力合约列表格式,包含品种名称 + main_contracts_list = [] + for product, contract in main_contracts.items(): + product_name = data_fetcher.get_product_name_cn(product) + main_contracts_list.append({ + 'product': product, + 'product_name': product_name, + 'main_contract': contract + }) + + return jsonify({'status': 'success', 'data': main_contracts_list}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 500 + # K线数据获取接口 @app.route('/api/kline', methods=['GET']) def get_kline(): diff --git a/backend/service_implementation/service/data/futures_analysis.db b/backend/service_implementation/service/data/futures_analysis.db index 13a0ef9977ea473194275ad5b065d2c2306878bb..46d48226ce81d3eb4fa6202c9eb689183bfcd8b8 100644 GIT binary patch delta 832 zcmb`>$8!t-7>02hA!-u6izrc(JlS1gqpjY1@4fdLT{d`h5<#Lz@4Yi-IJmo-F=PA% zW^l<(j03~feDl7S_jma6-h8Q)SDu0hSZcqNs?r#CAFoF)RlTtUm8e5X(WxMi8PgF(p*xcg``SLX(g?t zjigCiX(#QagLITmk}esNDdqoUNl-!(md=tbU8Ji-BuBbQcj+NLrI$pdxAc*|(og!! z02wHQWUvg8p)yQ{%Lo}Mqhz#)hmD3fHeOp&QFO{U8XnJKemw#<>aGEe5q z0$C`FWU(xfrLs(x%L-X3t7NsTk+rf;*2@OTm5s7VHp>>-D%)hc?2tT(NxtlqU9wx0 zJ+fE!$$mK?2j!3)mLpOig>qDiMD{@t? z$#uCQH|3VxmOFA+?#X?5AP?n{JeDW&RG!Imc_A;K@+!WxMttJ;3Q4b%HYasS{Qm51 zazb37_~J_pFgu-KdNAA~EBI@P##aBP pS6dAL0DxhqIkDuVyRgYiL z#rXsJ5iY*>`iuQ)2*w&K%M2vSv+@Ip>S%E!sWzwLPb4?cP>KX3Ns=W+g3?q{rI|FB z7Sd8$Nt(2lkfci+X)Eody>yU{(n&I;vviS6$&#+pO|qrCgr$e{lpN_LxzbzuNMGqE z{bhg*ltB`a#-G75M25;R87?Daq>Pf$GDgPAI2kV!WTNEBB$+JvGDW7!G?^|lWTwoL z*)m7w$~>7b3#336$|6}TOJu1mljX8PR>~?_Eo)@0tdpp$mkqK}Hpyn$B3osfY?mFf zQwn95?3Nm+Mj?H{_<=lDOQKJ91a<$$fbs59N_m%42yVPvx0BmlyI