From 28537e9260c80ce44a6beb92cb2c05333d11969a Mon Sep 17 00:00:00 2001 From: lintao Date: Wed, 17 Dec 2025 11:48:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(market=5Fdata):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=97=A5=E5=92=8C=E5=81=9C=E7=89=8C=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 精简导入,移除未使用的functools.lru_cache依赖 - 新增is_trading_day函数,基于缓存交易日期集合快速判断交易日 - 优化get_latest_trade_date函数,避免整文件读取,改为逐行读取首条有效数据 - 简化calculate_trading_days_diff日期处理,改用字符串直接比较日期顺序 - 重构check_stock_suspended函数,统一处理单日和区间停牌数据格式并改进日志记录 - 统一输出字段命名,替换中文字段为英文is_suspended和suspend_dates - 规范日志和打印输出,修正检查文件数和检查日期显示 - 优化输出CSV字段顺序和内容,保证数据一致性和清晰度 --- __pycache__/check_market_data.cpython-310.pyc | Bin 14649 -> 15092 bytes check_market_data.py | 142 ++++++++++-------- test_time.py | 13 ++ 3 files changed, 90 insertions(+), 65 deletions(-) create mode 100644 test_time.py diff --git a/__pycache__/check_market_data.cpython-310.pyc b/__pycache__/check_market_data.cpython-310.pyc index fadd76302f81ebe587f430500ac659ace5435e4c..2e376e519606f14fb974242159aed364da45d8eb 100644 GIT binary patch delta 4837 zcmaJFYj9N6`JQ|4-o5+GX0u6l^A02-3z!6u1Y*Dl2qF?6pn+9P*PIP3*}Tr)<$2ak zCp<(!ky9TPDWUaIJ5xmCSg`8!k5hlNPMvl-)9o)E5=Gl_I-|DJj-7trxp~2~-OTyU zIp6)xcfRwT?{#v8eVy?S`Fw5#eg_w=OCIWf+FwERrk_1c=!6C`Joj+3qUJ`7KBzrjZ@B8h&mIY zo?=*MQWvaq!Mc(}34AXdBe5`Wk4Gqpmr*zM98qHB)JuKPS5QAKfWDFjXd(1fG)Rk} zucjed41EnPp{3B*(lCuczW^96>?5>{mLDOpx}^ z%O;MJw+P9LcI|2rZEvP=E8d(LhAz=V;|H6?GQHTLgQ`4Yi@t>D6Z`e&2w5!(jGq%z zyk|7R(kWvBxpl(jtR;Y2Vs;T-95N4+jpAGL?hY5MWvFcCLEuHuh+rv#UjZ3(O$J)yjJ3=e*X1Y+*j|O z+!#-#6Q5uB>1Qv!bM^8mSSJ2hQkCn9s%#^Qh+;+6yt{W#qPHIuZYA;_I8tJe##s;T zo2MGoO5UGL_u#pcgS+u|o&a1Lb!)l|ViKL08oL*!y#&C_&x8TetcRAi;LD*O<}5fS zB~+!_DebA_sDN>DK^a#sC|Q-O={idr*Qw5xvtZrlvx;g9M4!Jq>>@bXNwua^8#u(vxKVbv& zSi5c3wiq{vWNc0Ot$5O&QrV!y^ah^89c4mMQ5o`{B&^w^OpB#x^*)Wgfgj zl&q5|heE*0MZHo(KFiIWW8|!IPDf33!y0O2osT+kl|9-|_A60;W(jB?4eHH;HKh6* zCtS$Xv&XOg^6i?855_f<$Bs^3yaHzO`P&m+qA$=WM4)@SC$A3;CkFE-OT=j^Ihe>h z(iTe&vu0Ss@KymaoJv}GM{5FacfT=Tw-hCqJS=$ zIeUjvnSsGH+bWJ1Mz@>;>U1Xn)eI1?s*@m5N$_88)3it5xV@%^rsgJI8}p^RB>06| z7&KMVIOQ)dBYt9zs{dw>i0=w3H~s-w9lDDvob>C=%?VdjH483hDd2cuYtr#rEV7r3 zt6-Dr1vRTp&%i!mrgNQZVsEgzUZsu**loW9R^f>BlTNTfYD5(3>{mOLtie@rF&H69 z@nNvmL$cF18Yc5uX%o@Z16e){ejwEus22x*=NwAa8ea>UNQ1QZ;Ci^5IJ5E^cyP=GE}HJ4$-ch4ct2D|wu(Q6nwviWvUD8) z)oH43VrZu7SN)*RATdat79a-9LNi+2GqJk(*MxKnQp&g7hTF`Bz`TRTlc|Gj9b%=R zEjYU2<{d1F*lq+{5Zr}etN5^VDVY@Cl-84hiSqE@iAR!6$IZ&JGr7G$_fK*Y>xVw- zVb|{+bEPwBuu9rPZ`}XCnV(?`F{i6}lAt(q89;`)!0v=4n#z6TJJC^I-!Kd$y()m2 zUm*;<&}2xX@Z~f)-nAN1nmiqb`oai64kocppS_p{U7PXoZ=4A--MZj2k;2N9!C6jT=HUg3TSNAC`* zL>XzdoZ}{S%P8RC5CLdVQWkMPj6>&K7!Rl~>l|sN#d5zdsnhPcn>r&(79^80empJX z?@>m%qX*Ys$KL}yz2aPjFZzYe6TZ4=33o%}QIQ&UDIIrn0|ILScb+C`QzR>wkYy9k zR9+%sG=A2KU%{&e1m52MY@GgiF8yuNoR zIe4Sp7TP9rx8yLL9xdD3$SUz^&7I!7225?kW@1%sGx*n@+9)XzXKL&2F|_~$FSllD zVF*?#2|&;?0qVpHEu@9DI>e6#h^YoJoT*_LAttE0H3Mc|z}L*UA)hc79MhIOfQQ-! z0CKjLP7Jf{h>*S|)r+}Ss+U>f7xkMA+oy!ya2v#=jSY)EyO1D@V7oZna38r}eAKXv z>^QS%t?M-+Pn>BKqYV|JchR=8IiJP?jD-={ZYX}YsH{!;eH@T^Z6JPNp8hu@;oK$2 z8Y_i3bj`e|ZET97-sdzP^~$aDo}r92oUwW^RZIs9aiH;bQ0)7SO=Lv;wXwJ6D6G7$ zzChpgLqnG7phFBt&y#0FVDWlaEBA9pzs6n_4=vtV?xQ|h@FHY=qJYtBZ3-B=$(`j2 zEfK$29Lbf^uodD#8Yx%CNC-<0s9@(XVMXF9fTHrOPOG>H6xG}bu!a{&THc1jC4uoskc1bEt9i!5$--sm@22JTzXwi}A>($wdf^6Atg%xpG=%b@WcSoGo*t z^e&sZ)R`M7jsokg(@eYQ-C(+9^d5Sz?C+!7WWRm7zkj-afOenN&v~X-?HJ491)#w- zWs9vMxQLx#Vv-e%!TfA?ctJ3hEUhY-#UfzP_8evp&U}M0n&|8nr`3@i)C;95wCF>b zW8la0Up*&Ar^NS~4g@g3T?+tUc9avJH;t2n;zV8GOQ-9VI(@F?y6x;;z~oFJ-m)v@sb?--JN{IAO$O_P$rGnmUcLOY8%Xe__5Ks>HP;0xtR66kV(doWHZ3=r7zQ|$(;=9_^!g^-rc7-B~6L&`{nYMa|28P!z1h#2p z9a0X}+c|0=&iWG;zU1Of9jPIB%ca-CU}nWN2T*75IX3yOAj>*d$P94=w|N}DU{zzk zHYZMh5S)nf=w0Ekwz zxzw=+v1<`@0*L8*hBC}f6Lz8z9V^RXINc`k6ITP9}>8YPsO*b58kdq#y9_Adhm&| zT?=UjSpbE0iKaubjdg%ub3@J#Xf;|8GCwxAS_POY6~3$LDB}F$WLreM+ICaLdECkD z=^2P82YY&=4eSM!Myj(weBHK~jEd@2C0<+A_>yCOaqp^1(kzBnMb^oT-GSKI7r%U@ z+gi32j9;-<{t It?K{&4^bVHn*aa+ delta 4583 zcmaJ^dvF`Y8NaF)HhEL*nZw;ji};>3@|{wc5+K{=hRcaCgOjpx1~r@k}gq!iYa-MFuU7iuXnPyB}7>r=v45Hk~klVZ{2fbIbway?mwIOB@T|kWUh_fq&xo3DNlT z&K?l;M`s7wvhXiwI{{We9U%@LQy(Tf`8(>Jt*9sFM(`kb5qc1M5eE4#t%GB z*!-;fG1%Phi8w2>dpvcdk576ANI!qZ)9GA`n}+yrJuAr+zv5X{yaO0<0&w*Y&%?GA zTJC9>IrNetnD9_kq|$Ne$?p}2NlN6bP!J8#6z2(*rv=t!NF|v%OyLyh{)}{%5J3~h z1**t`EX+&9>W>PhNY$`Va=>oMluAxo)XtKEOkJk@5MetP332})A#zEfF4*TVRO+!W zF)wZt9#~s&nxg5f9H5vAd%{%U5IjSLb?Q7Z9n*xF*Uu?ZuYHhx!y9g^nvSp_f~kxH zF8(c}!hx~tLa6WSFFPpY&d$PX;QxJ=B&v+Un;6>M?}<=YuMy!;Er@c_D(mt5hTGKU z$tmHCjE^`Do2XLI9?@`{-J2z|LZo`3!M~k2m0(l2g?o?OM}^O$5lHJb^=6P zWo;svN<_2ZA!RaAc31*+_Y}xZuqcjn#UA8zpTT03rvvU9k}S(OS61SgRDLF%W4rmW zK;(|&ATx(|5LG`>iH8J;NCN-yC@vUT{VOg{K=KfoxUSBA;HnF}FIEQ>k@Q^hH8c<( zQ4fn>vKap&(7fkwAnedxhOl2K5j5DdBABQ>BGyO(>yV~p)@(vggjp~`SP4C$Kn!tS zD9ENv#k|1Q;0pd=P3z5I)L}6HX$Q<;k4%%z0`$Sks4^{X7Qp6Yv^ktDIl-{O?#2a6 z#mZco|GK8#Ef&;AR4Ri0ME-uwB_~@AHyz<`*KTtULyxUT(0EsHX>ucU%PJ@(Yb0k9 z484=BLRh4-cqX4VZq$y(Y}dy9pwt*M%2Fatb$2|L0vcmcIsvgeHkXUiGPKPp%6Pc@;3iRXepUqP{N-Qx3#PiqO``Ri&L=f zGqQpufkxbHGj6j38}xK7m(PK9( zB_Hvgrllm!N1B?|y`mtTAcrKLYf6$=_&ZJAjVhJ%LT<0=HdU~!G1Fa8O%K%|%VcIW*YA5g`nri&_Jq2T7NEv?;Ws2429S#p|g^m~+(Rt=@3(Q4BL z(LhA*fwer^TwnZ^>9tOK)%3z?KK6#`qX9VaO;axTEE!%1!!@Q4_F17A))QHf1c-fq zHhq8}q(zhl@>Phv7_{9c#2i{{;?5xMq;+Q?ZbFuXdmUIy>q{=v330T_RK(m@e6ppN z^epg}pOX+8Gh2hO5?~}EZ7&NW+u11J*Vax_ywElzvPu47TaQOq2P%JThX1Cm)w2#Z z8G03EnQWBiA;HA?@<>AjHFI4j5s$47cP4LS z^F|a?OvN>MCUPsh`74n=66fzm;%$$@&i|=Z&{KWTtXkzSGM`-b9N}E)-KwoNeUR!U zc8-ts-dC(U4WqRmtQ+szt_*Kx)}m(WhS^f=T$V+Tfz8mu@i zf9rw_@4^gEyKV2B6biMb3+k803qdnTdmtuwfkuG(fG&gF;+qyv5au>(OI2pjiUc3? zHPk690#6~O5OP95#cL;PY%>mL1!At^K( z4f`9-2D6cF;QwCHUVIUXPtVnIF0Bp1;knlq7h;Cn(4dfYVRbkU|Al7j;EiT8{KlIq zhhlu(bnPlPFXC=C!&jp+7|a&@p+IjjCFIVNqBY!#!fF4;e!9BYpPBxEXt!Q`{dq)tyjc^p;G6l%v zSB&?)H%?tR|H5$FzR$=Kt5{}OR9wc8dPsbrjjiA>4EEJvsu_!jECICam`LSwQ~VDr zx30Q=0E*4y7udxg9lrS7xeL$#;=Py8vB!{$3GCv@5_=pdV+&hWHIwQ}NLSK{SuB3p zo`uZnxVXW(^|iZ3TCFcbI}BS{?j{t02~L-%GI?f4S378P?@&YVUYw61P@zU- zd21X?rL1_I)A`?r23q%_%&&iU-oI{m?L)X`4q+Cd2%syFMXa#?6pYHwS%^Ogrn`-d z5v$1OPp|86o+nU-mihU0`|eRcQ{DJMFeW*rI!T67RE5OqgTfROZJQJzE)kncYynOS zBwqOY#N`eU53H_(;x&NlA^jqXU$uN5Py>YL)~{%L7Vn@&;R`*Pjz%LrWigXu-^IIH z3h?vS*DqUDu&!-@6~tJAEyLMPWN8!mblk{fQaL6g(@LV4fLImRhMUJxQ@WOggcqBH zx`MUiR2{-*gbfI{B3O03rAJ$Z$V($!S7Q?n@wUvxzct)dkA<@;;5*>QlE end: - start, end = end, start + # 确保日期格式正确并交换(如果需要) + if start_date > end_date: start_date, end_date = end_date, start_date # 获取缓存的交易日历 @@ -199,6 +211,8 @@ def calculate_trading_days_diff(start_date, end_date): if calendar_df is None: # 如果获取交易日历失败,使用简单的日期差作为近似值 + start = datetime.datetime.strptime(start_date, '%Y%m%d') + end = datetime.datetime.strptime(end_date, '%Y%m%d') days_diff = (end - start).days logger.warning(f"无法获取交易日历,使用自然日差近似:{days_diff}天") return days_diff @@ -287,17 +301,17 @@ def check_stock_suspended(ts_code, check_date): # 检查指定日期是否在任何停牌期间内 for _, row in suspend_df.iterrows(): - # 检查是否是单日停牌数据格式(包含trade_date和suspend_type) + # 处理单日停牌数据格式(包含trade_date和suspend_type) if 'trade_date' in row and 'suspend_type' in row: trade_date = row['trade_date'] suspend_type = row['suspend_type'] - # 更新最近的停牌日期 - if latest_suspend_start is None or trade_date > latest_suspend_start: - latest_suspend_start = trade_date - # 如果suspend_type为'S',表示该日停牌 if suspend_type == 'S': + # 更新最近的停牌日期(仅在实际停牌时更新) + if latest_suspend_start is None or trade_date > latest_suspend_start: + latest_suspend_start = trade_date + # 保存停牌日期 suspend_dates_list.append(trade_date) @@ -308,38 +322,38 @@ def check_stock_suspended(ts_code, check_date): continue # 处理传统的停牌数据格式(包含suspend_date和resume_date) - if 'suspend_date' not in row or 'resume_date' not in row: - # 检查是否是有效数据(可能是API返回的其他格式) - if 'ts_code' in row and 'trade_date' in row: - # 这是有效的单日停牌数据,只是字段名称不同 - trade_date = row['trade_date'] - logger.info(f"股票 {ts_code} 在 {trade_date} 处于停牌状态") - if trade_date == check_date: - is_suspended = True - suspend_dates_list.append(trade_date) - latest_suspend_start = trade_date - else: - logger.warning(f"停牌数据缺少必要字段: {row}") - continue + if 'suspend_date' in row and 'resume_date' in row: + suspend_start = row['suspend_date'] + suspend_end = row['resume_date'] - suspend_start = row['suspend_date'] - suspend_end = row['resume_date'] - - # 更新最近的停牌开始日期 - if latest_suspend_start is None or suspend_start > latest_suspend_start: - latest_suspend_start = suspend_start - - # 如果恢复日期为None或00000000,表示尚未复牌 - if not suspend_end or suspend_end == '00000000': - suspend_end = end_date # 使用查询结束日期 - - # 保存停牌日期范围 - suspend_dates_list.append(f"{suspend_start}-{suspend_end}") - - # 检查日期是否在停牌期间内 - if suspend_start <= check_date <= suspend_end: - logger.info(f"股票 {ts_code} 在 {check_date} 处于停牌状态({suspend_start}至{suspend_end})") - is_suspended = True + # 更新最近的停牌开始日期 + if latest_suspend_start is None or suspend_start > latest_suspend_start: + latest_suspend_start = suspend_start + + # 如果恢复日期为None或00000000,表示尚未复牌 + if not suspend_end or suspend_end == '00000000': + suspend_end = end_date # 使用查询结束日期 + + # 保存停牌日期范围 + suspend_dates_list.append(f"{suspend_start}-{suspend_end}") + + # 检查日期是否在停牌期间内 + if suspend_start <= check_date <= suspend_end: + logger.info(f"股票 {ts_code} 在 {check_date} 处于停牌状态({suspend_start}至{suspend_end})") + is_suspended = True + elif 'ts_code' in row and 'trade_date' in row: + # 处理其他格式的单日停牌数据 + trade_date = row['trade_date'] + if latest_suspend_start is None or trade_date > latest_suspend_start: + latest_suspend_start = trade_date + + suspend_dates_list.append(trade_date) + logger.info(f"股票 {ts_code} 在 {trade_date} 处于停牌状态") + + if trade_date == check_date: + is_suspended = True + else: + logger.warning(f"停牌数据缺少必要字段: {row}") # 合并停牌日期范围 suspend_dates = ", ".join(suspend_dates_list) @@ -492,8 +506,8 @@ def check_market_data(online_check=Config.DEFAULT_ONLINE_CHECK): 'trading_days_diff': 'N/A', 'online_data_exists': 'N/A', 'status': '文件内容异常', - '是否在停牌状态': 'N/A', - '停牌的日期': 'N/A' + 'is_suspended': 'N/A', + 'suspend_dates': 'N/A' }) elif latest_date != check_date: # 计算交易日差 @@ -515,11 +529,10 @@ def check_market_data(online_check=Config.DEFAULT_ONLINE_CHECK): 'latest_date': latest_date, 'trading_days_diff': trading_days_diff or 'N/A', 'online_data_exists': '是' if online_data_exists else '否' if online_data_exists is False else '未检查', - 'status': status - }) - - # 移除单个文件的完整日志 - + 'status': status, + 'is_suspended': 'N/A', # 先设为N/A,后续会更新 + 'suspend_dates': 'N/A' # 先设为N/A,后续会更新 + }) # 更新进度 completed += 1 progress = (completed / total) * 100 @@ -555,8 +568,8 @@ def check_market_data(online_check=Config.DEFAULT_ONLINE_CHECK): is_suspended, suspend_dates, latest_suspend_start = check_stock_suspended(ts_code, check_date) # 更新文件信息 - file_info['是否在停牌状态'] = '是' if is_suspended else '否' if is_suspended is not None else '检查失败' - file_info['停牌的日期'] = suspend_dates if suspend_dates else '无' + file_info['is_suspended'] = '是' if is_suspended else '否' if is_suspended is not None else '检查失败' + file_info['suspend_dates'] = suspend_dates if suspend_dates else '无' if is_suspended is True: logger.info(f"股票 {ts_code} 当前处于停牌状态") @@ -586,22 +599,21 @@ def check_market_data(online_check=Config.DEFAULT_ONLINE_CHECK): # 输出结果到CSV文件 output_file = Config.OUTPUT_FILE with open(output_file, 'w', newline='', encoding='utf-8') as csvfile: - fieldnames = ['file_name', 'ts_code', 'latest_date', 'trading_days_diff', 'online_data_exists', 'status', '是否在停牌状态', '停牌的日期'] + fieldnames = ['file_name', 'ts_code', 'latest_date', 'trading_days_diff', 'online_data_exists', 'status', 'is_suspended', 'suspend_dates'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for file_info in incomplete_files: writer.writerow(file_info) - total_files = len(list(data_dir.glob('*.txt'))) - logger.info(f"检查完成,共检查 {total_files} 个文件") + logger.info(f"检查完成,共检查 {total} 个文件") logger.info(f"发现 {len(incomplete_files)} 个未更新到最新的数据文件") logger.info(f"检查结果已输出到:{output_file}") # 打印总结 print(f"\n=== 行情数据检查结果 ===") - print(f"检查日期:{today}") - print(f"检查文件总数:{total_files}") + print(f"检查日期:{check_date}") + print(f"检查文件总数:{total}") print(f"未更新到最新的文件数:{len(incomplete_files)}") print(f"在线检查功能:{'开启' if online_check else '关闭'}") print(f"检查结果已保存到:{output_file}") diff --git a/test_time.py b/test_time.py new file mode 100644 index 0000000..1894646 --- /dev/null +++ b/test_time.py @@ -0,0 +1,13 @@ +import datetime + +now = datetime.datetime.now() +print('当前时间:', now) +print('小时:', now.hour) + +if now.hour < 16: + yesterday = now - datetime.timedelta(days=1) + check_date = yesterday.strftime('%Y%m%d') + print('检查日期:', check_date) +else: + check_date = now.strftime('%Y%m%d') + print('检查日期:', check_date) \ No newline at end of file