From a12f34e80c64dab972d0029b8673aebfbbea81f0 Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Fri, 15 Aug 2025 11:37:06 +0800 Subject: [PATCH] invoke python file by schedule directly, instead of import way. The purpose is to make each event to be totally new initialization action. --- auto_schedule.py | 28 ++++++ .../__pycache__/huge_volume.cpython-312.pyc | Bin 16955 -> 16848 bytes .../huge_volume_chart.cpython-312.pyc | Bin 23518 -> 23619 bytes .../__pycache__/market_data.cpython-312.pyc | Bin 12766 -> 13102 bytes .../market_monitor.cpython-312.pyc | Bin 18085 -> 18009 bytes .../__pycache__/trade_data.cpython-312.pyc | Bin 5270 -> 5171 bytes core/biz/huge_volume.py | 20 ++-- core/biz/huge_volume_chart.py | 16 ++- core/biz/market_data.py | 61 +++++++----- core/biz/market_monitor.py | 20 ++-- core/biz/metrics_calculation.py | 38 ++++---- core/biz/quant_trader.py | 80 +++++++-------- core/biz/strategy.py | 92 +++++++++--------- core/biz/trade_data.py | 11 +-- .../db_huge_volume_data.cpython-312.pyc | Bin 30364 -> 30263 bytes .../db/__pycache__/db_manager.cpython-312.pyc | Bin 12164 -> 12110 bytes .../db_market_data.cpython-312.pyc | Bin 17012 -> 16911 bytes .../db_market_monitor.cpython-312.pyc | Bin 19075 -> 18974 bytes .../__pycache__/db_trade_data.cpython-312.pyc | Bin 17287 -> 17186 bytes core/db/db_huge_volume_data.py | 13 ++- core/db/db_manager.py | 33 +++---- core/db/db_market_data.py | 16 +-- core/db/db_market_monitor.py | 16 +-- core/db/db_trade_data.py | 12 +-- core/logger.py | 72 ++++++++++++++ core/statistics/price_volume_stats.py | 26 +++-- core/utils.py | 8 +- core/wechat.py | 4 +- huge_volume_main.py | 44 ++++----- market_data_main.py | 38 ++++---- market_monitor_main.py | 47 +++++---- monitor_schedule.py | 22 ++--- requirements.txt | 3 +- statistics_main.py | 4 +- trade_data_main.py | 14 +-- trade_main.py | 32 +++--- 36 files changed, 434 insertions(+), 336 deletions(-) create mode 100644 auto_schedule.py create mode 100644 core/logger.py diff --git a/auto_schedule.py b/auto_schedule.py new file mode 100644 index 0000000..225f26c --- /dev/null +++ b/auto_schedule.py @@ -0,0 +1,28 @@ +import schedule +import time +import datetime +import core.logger as logging +import subprocess +import os + +logger = logging.logger +# 定义要执行的任务 +def run_script(): + start_time = time.time() + logger.info(f"Executing script at: {datetime.datetime.now()}") + output_file = r'./output/auto_schedule.txt' + with open(output_file, 'a') as f: + f.write(f"Task ran at {datetime.datetime.now()}\n") + python_path = r"D:\miniconda3\envs\okx\python.exe" + script_path = r"D:\python_projects\crypto_quant\monitor_schedule.py" + subprocess.run([python_path, script_path]) + end_time = time.time() + logger.info(f"Script execution time: {end_time - start_time} seconds") +# 设置每20秒运行一次 +schedule.every(20).seconds.do(run_script) + +# 保持程序运行并检查调度 +logger.info("Scheduler started. Press Ctrl+C to stop.") +while True: + schedule.run_pending() + time.sleep(1) \ No newline at end of file diff --git a/core/biz/__pycache__/huge_volume.cpython-312.pyc b/core/biz/__pycache__/huge_volume.cpython-312.pyc index 95af9d0f75023cce41492b8f421e8ae57978fff8..1d435738377be007b4a0e21b123bb6dc12733fb7 100644 GIT binary patch delta 1616 zcmZvcT}&KR6vyZ8?9T2oyX?Y#b-R4E80a*GmhJ*g3iLxOqDY}3){d>qlo{xjnVt3S zETSf*!6vrp10`1jG_`1mF@7Y*>fE+M_au9Kv^Sl75 z|Mes|nRjr#oGM)6RQE@Y4X0f7ibL|utsgH=pl?x)pQ+Sg=BdX}CNwpJ^OA!)^O`<} z{p1XP7F8C%=Nw_ zH^GLR$xQeZijlQ&QH+6b4Dckm9(e=pCOf0UZb3K4$2CkYL|YQA%t@VwrerCfw^Y0j zg4+Noay!~R9kl&9#X3iw=gmwO+hz4wW?GvX)pY7T#)h1gF|$$tZ>e89EYkt#1RMZB zO=(5mQm~a)6-%QYEvwoVeg^#g;P;MYbS<4#42{gh8d&j{V;v|(XsoumdUoK-Kq>KT z**CBnimi*Gh162lV%Ht9dCgZn+kd6M6i=3Ysbbx>D%2aD_s#iAwL|6LiPc!*nz$N> zEDSH5UOas#Agu*Ay@Tc8(55%M@WfL8V*j1MuD`v<%fUf18PAE)`O3M_NK4`nYAPCuBVlxue6kns zI>_E}7;AGn|M9JH2nLq?$ z-D#}l3%Z3LCz1WP#5$&76R@59u)h^WNjTZCXB!iK90aunEE%lV7(U?e3>)%@p#g_L zzC=cn2hmUDM)Doj;N2;yuaC);S9D!+VhchFfJwm10Js2n8gKz{5nvzT(_q0l+P8?- zlO6kGXc|@+h7du?C zA(-%nGR43;@iYXC0C)x|v9ixai82`v?`cK+dxz*!7P=v7baUbA533YO%; zQF7x@#|!rM!|?wR0BmvNUb(@7&nbQ%kYb?Dv!;0ui=)kICI58g8rEWHoEd(Quy z`!V;PJH1OgQGvCbcF=N->sXV_LIC}MY65f_i`5Owqe(9*au!3P!%)<593Y4rMZtp1 zHFK_NnuoWOtFDf4IhctVXXP>Vv@$iOsFt^vc6l?a=Y#-$4G*vciY1w9j|W zbuRL|@05h5`!DwwtJ;@JItpJ$%FuzxtbfK|+?HAj9$Su7zr`;H!dHgpN9INr1Hzr) zgXqvwF!dl>ex+yr$lQ^|Kzt*5bSXGQqR~7bnGMVYim`)B!9(Po=*K8Y`l>Ru$@Rq8 zzFRwPg^Jtu-wt*agIz0u&?Ww#dv4@Ul44a7#S6je?sC*aQcXC%pMK*o?xGZs<&d$S z$8b}YGp9_MY0H%!Q*}eZaq?@^UzMY45#7v53&=uK2BJa?hspisUUY{%-I5JNAjO@= ziZQ90xR=~&`G${C4ZjSiA(vVkQ6;(A8f&Ve!s<~_;UTVyV4bB{VDJUnt z*^7Q7$+l~>l2_VYdr!M+%2vp{q3Lg z|CiYdB;3&!g`HSVtWV}-%Q>#WDle!NBJs#U505J2*ynVB+^-ruP8X z0CRw=fOpBwL<_CQ)kF`vUTEC&uR9EfKOmqpp2jA9(#%hqf)iJgWHRxhz3U1Xwz0Fd zm*oZxe!+MFkf5*}qq?qQ7x)_B3&7g|SOA9srRxY3ipe-?vm+o1TP#;z%E^*pe?K0E zM0mM44X~GYfOVX7cMFFtepE8DW6$cj@odI&92)2wT$_9IdiOn@nl4YON*Df)4u-lj z`Y7&k2%(>tnje``a=p9aaVB)$eVYj_GNEN(*>wHo`r`J(e{w8h|Mm?zQNte$(Eg17 E05Hv)L;wH) diff --git a/core/biz/__pycache__/huge_volume_chart.cpython-312.pyc b/core/biz/__pycache__/huge_volume_chart.cpython-312.pyc index 038ea1f9db9fa14cbdd34792366e33de3dfb7e6d..567511e2f5357066460d6fa3648414f6d4a8de8d 100644 GIT binary patch delta 4183 zcmb_feN0=|6@S;p#?O8R8-HMfAvgqs?F15NNcaqdg>UE*nzV7UdJcXhPK}-2X9}fj z$l9o)o2rK0b)nMMIoi}F>)N6*bxf+CONuYK}hd{ovVCkI!kXX!~5X_sF>3Q-nA_ z=T`;|;|7m`@$#V2W1=#7(Cjg@cd@6Kz11Eyedh-)<5rIqabnTwt>E+a$D^P7)M`@w zh<^d471XKvBogyaHxi%OpwYBav0vC2muX=v<9axM!FkSK_<}5L<*_{^ck0L#I=fge z)2j`IRGiRYQ4%BGK*%pnVI4UlEGj`x*+Ojyl`Njs+U1(aV#+9M7WXMXDD|xQWK{-VK@^EN4#eOqoNlFB+)zO3kESW zC;<)WG!~OZ!{3!`nMQKSSgzRti`_-ZbPdrvP2?{|1M-p^#y`>Zer?);P$OA0Ka1MP zQ^lp_J+O=nr_CuZLBbUkm^o=6g0n<_O7um>eP_I4D1v>VQE`IYD1J;=N9SP(5)T3y zB9B`hROV5Z=HlOj&!1wvzCTu4@I zt;0j0EeBEo#0~`ZHIXm*9;6jInNW-d!;?pFQ2}Ad`Z95x>Wl+K4v>~RX4I~ zvR*tocQl&!Jb#J5TobQYw3Z_zfOS#2o04axWx$)DDoH^}1w83J?4ZMG7X!kJ6(+}bV2VTuO zfJw?hpHzSwn)Z8jU9#M?8En~}ou@J2vp}XO8Fnhu0Ajnx^3Q1l)uuMcDMC&!sHng7 zUZ}`VA4=WirTXssQ&b^!+?jSE-!G$S@7}Y%skJf7uDDR2$y%%Jzr8U8JrxYBR=ZcK zTjSNODNbYRmyza%H!Z->9-;cA9L$mmaL$Tx6_HPNKbc#QQm8#1O1P05v%uUOmS*0z*FXY$HcE%nz)Tf+g;vG@B{J4ljpn3|M>hok})qSg7= z-dFE{3JX66FHb=7n?RGF3mR6P9OP{NG;5w*N3%Sx> z0lUr~a=+QU__y}wls0xh>Zd|HOi4oR$G%A~_C*3=d^fprz~i_8x;Y@<1VSkJ0Iq3H z*#`ip4+M~Yi8#9>=mdGTJKE_7fe2)b5~m3VfIkfc?p^o@C5gOXK#C;t9uOara1aD4 zY2pOjDJ6POo9Q{^{)|f)#$YZ{;w(w`Kf`ki`Lw5G8|0DDjs=1d5sQAW-xu-W+|WsL z?^!fKe%4!=zNW%ef!)eXao(8SCQ3*8HQW5(92s@k&Xm4dpYHeOOoJDo2e&3%u`;r+`#^UU=FU;J1uD^}B9i3t_aqPVavR@JmDG_Qel zR6?dxkVPW9VlsGU4Lm;;05K}Vj3RwDhJ zZh|ipQhcyGSGM@@!9G+5r_;1hb+l@{%3sx6HB>b+t*D&}O7QJ4MBX`Ma6V0E;A1dN zF%cQtu{VJ_@4?@uW2Y>UcOo40C-UHaiN8h4M#S1IH~^dpc_=(du@J#yk-)f^dllD_ zg^_mjEAr2gVEWUh9oA|jWB1%k_w^U|OexuyLl0>^Y_I$VZ0BPXs&qSzneA*`m|h6J z=7<^luB!UUxx=HXWq11x&dyuO`-fe-munBCxDx%YxY2c?KdOG$YX4r@GiA}~Wo!K! zhfI4GMptWhy?pA*skp^GuZlW9ky9aQIr3W-HCp4rjq2W-8R|2|DfliF7piCb=L=U| zO)IW_ao4_OS0}y2XsJTyxS*czUz5r8UFdD2`C{K(-~8z}jn1S3)JZv5f6|4h`y?!U z>t;P?EK6}Rtraf)!kiG>vTMP$U|Tx++E-p1jg|FZ)eT(JS#GT8P732Q6asRC~In|Qi-}_Z1>;C~Vc;9|^E$jl_rf?89L2=@FjygfOL|V?<;eC9oQ#O?1!TMDsF9rn`I6{65yl}L0@F9hGah%t>y%AsM#EUFO@FnqLnV<9gFxsc zX4p}l@rC?834a83<>VbtIVn13s$mhVfs7r55Y@j`t>a<;L{MzU|D{6;6i$keyN{LG wuW@Dc_ZDY(i{sg^@ETWfo!328{zUmX<#kR=qQ`a?bfFY?Mv1!U#fF;x4|H7v&j0`b delta 4102 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 diff --git a/core/biz/__pycache__/market_data.cpython-312.pyc b/core/biz/__pycache__/market_data.cpython-312.pyc index b6b8bfc4023822a7355cb1bd2b3174ec45b9ed06..ebfe61ce632c5c9aca0e7e2707af0dab7e3a577b 100644 GIT binary patch delta 3642 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 delta 3418 zcmb`Jdu$WQ8Ng?~-nC;pjuYEBu@lD-@~{a>Adlls67qzQz$F0^9y!O}1e0|Pv(Axl zfl!s|k`q^&oAwA2C!%Xrj6kQfQ8`yFBD7TtIw39BRHO@BuM(|}KU#%{+KN_H=d;P- zw0AvK>i+SY?=`ctGv7C}AM||UGk%nutV8rEy)}OD??vOrbmq!@+e+g#`AH6GmP9P^)XP1vEc*g}8}|AG{k=8buvalAC{;Tem839r3SD#%H?OEALF|bQiw$&`*jU&I zl9@u{DO{w1-M<68Qa!~<^jL>%&M|4SbX0xc?z(ID+_8J+ERM+5nRRoi)@K~+(2c53 zr##ald+yzg(mNTYi@bK~ecMQ$us_x(tikkE^=3x9V0dSxbvliILzNIwFg>B|V(QE3 z6mSLL6~G$6TEIHMdJ2X2g#_#qE-?x(U>w2{E~Dm4EM5U2g@9_ZNw;h#q!P}iQ1o;$ zyd`JhsMsrfIBcV(sLv z$%e?bC_6O}t@^2NCjTd5^nko=zkHxi-Y?1`mZcLi9+^kTUZr}5P;&M3_2gwtw)srU zSj&VMsfZLzEt_hcs+HI7kuy7H4V`ml8_xP+ij~o)&^g4OLnh)f9ZAqFGW_e zAdj@$i}_Lz3&>mcm3%6Q`Q)1YJL}z4Y{MQftOOJRiUB2nRe;qL1CZB$Jb&f0Yae{} z%@6-@=3Ts9IFU=N^!v}fhj zbb`1Sun({w&;@vnLSch}ehTlP+m2r*qzHbf0lXC8BGoTT8~A*)dUf$DA`MeQfqj6+|mOUR{0P@*<{kCjD2LH!^Pua=Z7}Ro!x> zPhReywFu<**)8tu3n}MPWKW~KwNq}~EAN%$@Nt^^^9YSF?JC;1cR~JV>?R$q>Pp*L z!?rfJ7=UNy22 z_O6Vg?M1qRUO=Jn-2+2i(lJFtFA0$f$GDZPyQ_9!Or36BJR3V8`el-7g?_(uG!Qn@Z)E`@}M=+U3@JHm<;w7Zmle1?CeDN{|X$N4C;*vI@JY$vV0#|H2 zC`pC$g$DY=`0G@b;5!%A1j7Y_2gprNnd25Xze{lv(ZhXIJ7-EA<>riO^C}~;ttek- zM5gr7l=)O-$)rdz|LvItS+brWE;;+Y=|6I2&SafP9oKz}n=_}ARmDrl?G>8!dK&xu zDV!v4dMeP>q-vhMzO0&IZ{%Akf5vds@PFHrk^gN^@Vl@SpRYbXN7YA=AF%qgQB07C zK5xHY6r^y#*C*lg(BsSY1ZTZR-CwZUt<-#MB^fcKlXAV)@d-q|Pw@rI{!UTZe_3|t zS1)^(hAz4DIle&Ji+`E_@(70dLm?4oQZasI!MF)!-v{cOu|Sd5 zH2yZ|0{~bSd=c?FZe*F0t}{R!ZyhvVqq%fh(s)P##w4c48cWp&0#8x<8}sBf@n zyBGHfVfZzUO$1xOlLDTEA7yL)vkq5hsgucB^ju50 diff --git a/core/biz/__pycache__/market_monitor.cpython-312.pyc b/core/biz/__pycache__/market_monitor.cpython-312.pyc index 00e9a93a9c8642cb327a85dc14e503cfc4fb3a37..82415c4f2c1442645e0417d094a01c5c5db71aca 100644 GIT binary patch delta 3702 zcmb_fYj6|S72d0rSF88S@@gg7k{_}y8`%~wV`DIW;9!gaV{pMCID!>^VC1rrpwO#2 znaPAWC3W2UqnQaXN3Zy zk|kz8%}LhRw37}xi9?yh3UIAtqj2X%6mpUzijY5}H2OU`uGz~M10a9dTtyDD0(qpd zuTzSW>6-pF%3~38(VQ%I%N%3}(K;XUv9@oAU2>fnWEfc^x#LI@(@v~^myvjEpwfdK zO5aJP7u%`yT{EOBF)wjvnyF6}c`69HYFtSzHDshBDCGP8YiubL%)ROcoEX4HimS!k zyUaCh$sQNLYR+5hunDXrViBzMB3(iemv#XPHIN8bD+aNd&eVu4R5oENmCe{j{dlnI zLxo6L7_lwbMSji&$xfY{G}0EQZ9?ZpVe(Wfi0-7Wn5U$^*u#v}2391Jdpa-qNM|8m z(3aC#=)QcZ^IA8mBC96tyqC1`7IKufy}YG$i^?8ITFYkMsQVV~>(Py3Eyeh?Gijy-uT`HtSm#hb9V0O~CRIdQdYS4ek} zMZG}&tG9UTlJ@5TvekrNc~qe>4v2Fxep@^s1_9@5tAPc zkTH{A3`*Q>O<}f1+(-)ticP=`1ev}5-8e`WrYX&@ASfOX(h!LnM3!Pm*y1I;(L%m5 zSkNA#H+rqZD#j2wVDysx6!9(E=8TrbeJc9~RkWijTUgqkZlkT8whr1NxLG;?<()9$ z7U|%5Cc)!Y<*CJCc+#UXmhPaaL(fs;1r$61H9k>*MQ~6WRL%-Gwb9Yg^HIAhsKctD zHsUt zk7=jGLzFl|iI1kYQsQIjt!2bpzZPPa62rENld5%hN-yG0n!7@QR3FEk>Lo}o743!c zpa^o#8b&9e+{j1PDz;c$Q@Ba(i?^*^K1(rW-mlxLSWmt*^)J)6z#fQ={L&^=P%Y(q z&*s$?wZIuwIQ#8x`d)%^K8ZtX7`xXoQjEfw_kC9MC36nD1#efu#~iQbCnkS)_}IdE zpnW@8wEEF$h5Wp;K#r!#ZKt=0VyE#2g+@V%;j%i}iFZ<#@>4j2!)w9@gsM(hih=F2 z&#Jbo7k3bQWi6_wvD9d!BM=hm1u449D0ksqYbfWiOHH{52o_2a0;qBA7NI zJ%f9tZz_z36h=tKDE1^c?4$3LnuG@Xci@tyJCH*`67z22;QFht zCs)0{$Q<(>(a>*$4{8L|Mt)QCE7V2a@&{2*Cgb;Mb=>^I^mI%nf2_Z39F#fgCd2mw znQ9P^=lD|#@nkHXOvn~6z@;zSK*02}1B45N0KyFdGAMgM_^8Nnr)Hw@=~%Afq&yot zE!Tj-4Y=DnLBW($R%rp&GIF?72Mu@N3>zs^cZ72W3 zYRr~@^NiV9(0j1jhVNj@33#D z-Wk3zf8)fR$eoEhd+zq%y>NH-{<*AanD|2jk*ufn*2t|L*=?iQ{U@@cQ(5=RpYhQr zylvUKWGxvV%szNA>pu1=Kbg55s?c%|y@4$6&fE!q#&JqkTQlF?{1c6_iY`O>&57^{ zo_VSBAE>&OYKB6ZoaW@koIaX36^l!=@#*}&ILU0+L7TE2O6s8;Wgq#tYZz4%XZM?E zj9l-oaUBHp0T5ac6ClRPC*76eKHAMy#AJCv4%uZWs{A&$W}bCdBWnT_hl`>!)6(-H za#)s4OQx(aaKG>VrmX9QPx(EWr#oI}U8N#X0)p+$?<9XoTVG!vkSocDeTTRWpx;T% z{aZTwXg8#hJ3-S8q8|k8a7Zg}gMJr??I3zU>;~}yx!B*_I}F{uRL}_yL4OST)_ftG zXg|lvu|#SbkKyn0kmVnaPzi1~kP{89)2 delta 3821 zcmb_fYj9J?6~3#tWIZj(lB_3|Wm{n37q+nse%D|EB-n=V3<$0)+sG2RlIcKJHSHud zrZl!^f7G3Xi96HI(4k4`IBl3rJK;||$@CGm6B6o_hM5kb{Hojj=uCh0oNMca>96)` z_S`+^eCO=hb9V2p{&=1I;M+v^Yn@KR!Sm$f8{@-^ItSUiSljJ6u9k37^`r$gr4L9e z*!Omrt@0l!Zb{a4gj0(@-n{s8_u`_3s25+i$u&_jS&2|bSyaz!q3NQAF+Hz;Res#a z=e1Tt)W92~rZEL?dR2D3T+Q(oUI`8i2$%8Y05@MEK`R?2O&ynO0(Ftpx%9+TG??-Q z>f+Ih(Rd<)To~A?Xet$n0TYz0%0=C(Vk{|4Mbhjwk|7^5sbpUn$<1g|g5k0AlDcQe z?Cc|n@fmX4ie4Hb&rsOe%d#H7`VEd(Q4LT#H34K)JI6 zi@}Gf9VD{CWT;apM{84OL6+#Gjt%LqB55tHT$i#RtF&@6bqOYRS5-Ar5?psl;9JtB z{G_B*L$}v9N4O>fBv)oJzU@vK$?yr;#Qd1JZAiJz_ zvP&BMwz?Ae-V&Q&iEfDf4s3p_(X)R+d!o_L)bsl@A!ys7H9%{mwfq6xBq=Qo@&_++ zDHYu+zGbu?--3jHKGOiG7xJh>8z_7u3f>~Bkyi74;)X+;5O4!|Zu@#%^_Wq}=5R^S zO*ACT@_m_R+8{{zkxWZM!jEQJ*lo3!3}cE(Gg7uq=W{j{C3^?mu_@VyY1325Modw% z{hJZnh1X~!jC>^13M0RmY0Z!9k<5rglUA|P|MTEd7#u6@JX(@~HvSlGgDmFPjPp2c zE1d>^B8L!H4CtB15F#hVxN3@$(oWkqCFK;YDfK2|ijpD)#D>2GeXG1Ow|4z6L9qrg z)~D=uZX^|u7>_!S6TO@k%XQvRAkiewk}w&UlZ4gk3BSd$p}uHbcrRg6qTf#c0&}UABFL!zwaWZs3g|Sh{lr9j{HAI zYuY?o7e66xU!IzHs~G)h*rye=A!W%e58GtnyGgf$u2IPA%;$@AKEax^vBnGXgq?Z| zn%z@H8lvWC7oQL#7#1VI+#H~el!7|pv*S)ls7DAvL^h}bc*kYPW-7E&hyeT63k7fM zv0>b(Aur?6q!Wtt0d~gb^`?p>u=KFjnI4FHK%Yn}2=pde}FO7s`O|$?W{&*Jr~%R_Sb!;knL6obO9lNnl0iIxKNC zfMi+^3A{`JDnO&3hC1qBk}2|(2SrKBs-9_B56a?j$|hb z1)51WI}_Lshw|Hj>*N3n*9<`DGc|+kS2d%WX@F^=r7Wzvc1UiX-#@pXE!S4E>$L{C zWrJ&x8|JlhT2cOw+HY-e->BFk!k98QuhmgpS97S@NErczOgV0@-M+G4%1WiI2 zDmJ3|Mc7RYX%rmEk+zj5oj)bzhI!qbE^8mSd+2U|wqoxms-czJyT2(dD=ZERd#kU? zv|cVSzAW?m{XV#xr+bGL+tFJO`%Q0adp~r85}^YnyO6+llM@C}Digxk??kd2Nf(km zNQT(XzPi@uu)7}!I2gtL2=)zv3j5;HRS414bUa|J{#xF$ztO~C={Sahr@MOMWbO!M)%_?lW|HJqnvtxW7{Ih%7$ XCwBFm##Z@H(ev6Uvk&f;;SBx*p#pcT diff --git a/core/biz/__pycache__/trade_data.cpython-312.pyc b/core/biz/__pycache__/trade_data.cpython-312.pyc index 4d0e21c0a74825a296deb6df05d7db5a4bf77156..d62b15f6bf767407c796b9293d6309af682d21a9 100644 GIT binary patch delta 796 zcmY+CL1@!Z7{}jh(zIQYrfc2Sty@=->55y`Ib~a&u6pp`5bHoBA|-1=*DYD=OFax^ z4)ih+W_h4&2lXQ2b{X`t!w!1!B1pgk7zW4oK9(kIq z^~)b*xdpMsy?#FPq$+pf$JJwr`<%&Hyvk#wBORMwRdC-v%dZApjW=5?Q5D_TYf6@^ z%71%WdHFyIVBgSU&iK?WgD0nXBOc^Y?6IoR(L%u=CX z5IP>TaFlKYM~-$vi#rCclk@@-42>4eG;4r`EXh5k5a3(TBA1H@$jJ{Gnceg(i ztD+OnyiGZotdq?J#Jvv6)!o%V4%H;B- z6H9MpwoW^p7wXaky%nY>N5Ph#E|p9Y1FH$wkKqmbT!`HRz|TYg5{*Z!Q*Hs$WSERY zi39Kq4WY77F3uN-*GF1EH1pa(qen%@;6^ J{z5=^<{vWXx)}ff delta 876 zcmY+CPiWIn9LL{F`oBr~Cu4uRwHtJ6+1!T8ob8$G@ zeSnbCAsapNB2rT+jD4sos&2_c51vX zNCE^5P}#)}+I0URZfw6pmEwBle#-pyY3(VXc*56#;{9hzp;eglm3O9(u64%~vN2c6 z>uS=7#S@x(P1OoARVgBM)iC575lO~!+BwTLUqrfGs(9f!2zh*kX@CK|!t}xre!#?9 zc`_CW6ak7LMTnvi*BBKFl?=NJ{a)hOXR)yob{=B)7(P7ZxxZIeetVuUunRx*55NZe z-oN0AlTsAL8SYXrLPEC*LSQ+JyrQB3ss|~C@hqQ(E%*hW@VK>NE|)Ll@HbwEt$0=#?rx*c*nMI& z>ZIr*uza$nT`nP6Q4Q3Cp9>`y-4La4Z=e%K@MPdqr0FhK=FI5$t5I`&#+*@1b=g#q z*=|&WrAj7v6^5c){3icM*k0bAGrLpO<}`j8Ivf^nPu`p~BO^Q0I}>K>Se46QHjD?; zbgUy=ENZBojvBY;5j+Nn{xQg2fTIxwk9WiRke#418AiuxiluN+93!w?rPU?!#>i=l zCApx;20DQiF$SCP4e{W5RyOi;6UD-OK4&>jpE-HfZjNNy;r~?fVf3Q1qN$^3kt86w zj4^`mEQGKI{9iz#2823js{yeNqIJ+$2M4|aal`!)i0^=SuV<@ov(Fqj{{dY1<;p?W L`~y&;4!Z6i{430| diff --git a/core/biz/huge_volume.py b/core/biz/huge_volume.py index 5a27ef3..14763a9 100644 --- a/core/biz/huge_volume.py +++ b/core/biz/huge_volume.py @@ -1,5 +1,5 @@ from pandas import DataFrame -import logging +import core.logger as logging import os import re import pandas as pd @@ -7,9 +7,7 @@ from datetime import datetime from copy import deepcopy from typing import Optional, List, Dict, Any, Tuple -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) +logger = logging.logger class HugeVolume: @@ -81,11 +79,11 @@ class HugeVolume: DataFrame: 包含异常检测结果的DataFrame """ if data is None or len(data) == 0: - logging.warning("数据为空,无法进行成交量异常检测") + logger.warning("数据为空,无法进行成交量异常检测") return None if "volume" not in data.columns: - logging.error("数据中缺少volume列") + logger.error("数据中缺少volume列") return None # 按时间戳排序 @@ -115,13 +113,13 @@ class HugeVolume: # 如果check_price为True,检查价格分位数 if check_price: if "close" not in data.columns: - logging.error("数据中缺少close列,无法进行价格检查") + logger.error("数据中缺少close列,无法进行价格检查") return data if "high" not in data.columns: - logging.error("数据中缺少high列,无法进行价格检查") + logger.error("数据中缺少high列,无法进行价格检查") return data if "low" not in data.columns: - logging.error("数据中缺少low列,无法进行价格检查") + logger.error("数据中缺少low列,无法进行价格检查") return data for price_column in ["close", "high", "low"]: @@ -137,7 +135,7 @@ class HugeVolume: if output_excel: # 检查数据是否为空 if len(data) == 0: - logging.warning("数据为空,无法导出Excel文件") + logger.warning("数据为空,无法导出Excel文件") return data start_date = data["date_time"].iloc[0] @@ -154,7 +152,7 @@ class HugeVolume: ) as writer: data.to_excel(writer, sheet_name="volume_spike", index=False) except Exception as e: - logging.error(f"导出Excel文件失败: {e}") + logger.error(f"导出Excel文件失败: {e}") return data diff --git a/core/biz/huge_volume_chart.py b/core/biz/huge_volume_chart.py index fcab219..8c80e2b 100644 --- a/core/biz/huge_volume_chart.py +++ b/core/biz/huge_volume_chart.py @@ -6,7 +6,7 @@ import seaborn as sns from openpyxl import Workbook from openpyxl.drawing.image import Image from PIL import Image as PILImage -import logging +import core.logger as logging from datetime import datetime import pandas as pd import os @@ -14,9 +14,7 @@ import re import openpyxl from openpyxl.styles import Font -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) +logger = logging.logger sns.set_theme(style="whitegrid") # 设置中文 @@ -140,7 +138,7 @@ class HugeVolumeChart: for symbol in self.symbol_list: charts_dict[f"{symbol}_{ratio_column}_heatmap"] = {} for price_type in self.price_type_list: - logging.info(f"绘制{symbol} {price_type} {ratio_column}热力图") + logger.info(f"绘制{symbol} {price_type} {ratio_column}热力图") df = self.data[(self.data["symbol"] == symbol) & (self.data["price_type"] == price_type)] pivot_table = df.pivot_table(values=ratio_column, index='window_size', columns='bar', aggfunc='mean') plt.figure(figsize=(10, 6)) @@ -323,7 +321,7 @@ class HugeVolumeChart: """ 绘制价格上涨下跌图 """ - logging.info(f"绘制价格上涨下跌图: {prefix}") + logger.info(f"绘制价格上涨下跌图: {prefix}") # 根据price_type_list,得到各个price_type的平均rise_ratio,平均fall_ratio,平均draw_ratio, 平均average_return price_type_data_dict = {} for price_type in self.price_type_list: @@ -404,7 +402,7 @@ class HugeVolumeChart: } } """ - logging.info(f"输出Excel文件,包含所有{chart_type}图表") + logger.info(f"输出Excel文件,包含所有{chart_type}图表") file_name = f"huge_volume_{chart_type}_{datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx" file_path = os.path.join(self.output_folder, file_name) @@ -444,7 +442,7 @@ class HugeVolumeChart: # Update row offset (chart height + padding) row_offset += chart_rows + 5 # Add 5 rows for padding between charts except Exception as e: - logging.error(f"输出Excel Sheet {sheet_name} 失败: {e}") + logger.error(f"输出Excel Sheet {sheet_name} 失败: {e}") continue # Save Excel file @@ -456,4 +454,4 @@ class HugeVolumeChart: try: os.remove(chart_path) except Exception as e: - logging.error(f"删除临时文件失败: {e}") + logger.error(f"删除临时文件失败: {e}") diff --git a/core/biz/market_data.py b/core/biz/market_data.py index dd1c6de..4607c5f 100644 --- a/core/biz/market_data.py +++ b/core/biz/market_data.py @@ -1,12 +1,13 @@ import time from datetime import datetime, timedelta -import logging from typing import Optional import pandas as pd import okx.MarketData as Market import okx.TradingData as TradingData from core.utils import transform_date_time_to_timestamp -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') +import core.logger as logging + +logger = logging.logger class MarketData: def __init__(self, @@ -14,15 +15,18 @@ class MarketData: secret_key: str, passphrase: str, sandbox: bool = True): - flag = "1" if sandbox else "0" # 0:实盘环境 1:沙盒环境 + self.flag = "1" if sandbox else "0" # 0:实盘环境 1:沙盒环境 + self.api_key = api_key + self.secret_key = secret_key + self.passphrase = passphrase self.market_api = Market.MarketAPI( api_key=api_key, api_secret_key=secret_key, passphrase=passphrase, - flag=flag - ) - self.trade_api = TradingData.TradingDataAPI( - api_key=api_key, api_secret_key=secret_key, passphrase=passphrase, - flag=flag + flag=self.flag ) + # self.trade_api = TradingData.TradingDataAPI( + # api_key=api_key, api_secret_key=secret_key, passphrase=passphrase, + # flag=flag + # ) def get_realtime_kline_data(self, symbol: str = None, bar: str = '5m', end_time: int = None, limit: int = 50) -> Optional[pd.DataFrame]: """ @@ -38,7 +42,7 @@ class MarketData: else: end_time = transform_date_time_to_timestamp(end_time) if end_time is None: - logging.error(f"end_time参数解析失败: {end_time}") + logger.error(f"end_time参数解析失败: {end_time}") return None response = self.get_realtime_candlesticks_from_api(symbol, bar, end_time, limit) if response: @@ -47,7 +51,7 @@ class MarketData: to_time = int(candles[0][0]) from_time_str = pd.to_datetime(from_time, unit='ms', utc=True).tz_convert('Asia/Shanghai') to_time_str = pd.to_datetime(to_time, unit='ms', utc=True).tz_convert('Asia/Shanghai') - logging.info(f"已获取{symbol}, 周期:{bar} {len(candles)} 条数据,从: {from_time_str} 到: {to_time_str}") + logger.info(f"已获取{symbol}, 周期:{bar} {len(candles)} 条数据,从: {from_time_str} 到: {to_time_str}") columns = ["timestamp", "open", "high", "low", "close", "volume", "volCcy", "volCCyQuote", "confirm"] candles_pd = pd.DataFrame(candles, columns=columns) for col in ['open', 'high', 'low', 'close', 'volume', 'volCcy', 'volCCyQuote']: @@ -67,7 +71,7 @@ class MarketData: return candles_pd else: - logging.warning(f"未获取到{symbol}, {bar} 最新数据,请稍后再试") + logger.warning(f"未获取到{symbol}, {bar} 最新数据,请稍后再试") return None @@ -95,7 +99,7 @@ class MarketData: else: start_time = transform_date_time_to_timestamp(start) if start_time is None: - logging.error(f"start参数解析失败: {start}") + logger.error(f"start参数解析失败: {start}") return None columns = ["timestamp", "open", "high", "low", "close", "volume", "volCcy", "volCCyQuote", "confirm"] all_data = [] @@ -105,10 +109,10 @@ class MarketData: # after,真实逻辑是获得指定时间之前的数据 !!! response = self.get_historical_candlesticks_from_api(symbol, bar, end_time, limit) if response is None: - logging.warning(f"请求失败,请稍后再试") + logger.warning(f"请求失败,请稍后再试") break if response["code"] != "0" or not response["data"]: - logging.warning(f"请求失败或无数据: {response.get('msg', 'No message')}") + logger.warning(f"请求失败或无数据: {response.get('msg', 'No message')}") break candles = response["data"] @@ -120,14 +124,14 @@ class MarketData: if latest_timestamp > from_time: latest_timestamp = from_time else: - logging.warning(f"上一次数据最早时间戳 {latest_timestamp} 小于等于 from_time {from_time}, 停止获取数据") + logger.warning(f"上一次数据最早时间戳 {latest_timestamp} 小于等于 from_time {from_time}, 停止获取数据") break from_time_str = pd.to_datetime(from_time, unit='ms', utc=True).tz_convert('Asia/Shanghai') to_time_str = pd.to_datetime(to_time, unit='ms', utc=True).tz_convert('Asia/Shanghai') - logging.info(f"已获取{symbol}, 周期:{bar} {len(candles)} 条数据,从: {from_time_str} 到: {to_time_str}") + logger.info(f"已获取{symbol}, 周期:{bar} {len(candles)} 条数据,从: {from_time_str} 到: {to_time_str}") if from_time < start_time: start_time_str = pd.to_datetime(start_time, unit='ms', utc=True).tz_convert('Asia/Shanghai') - logging.warning(f"本轮获取{symbol}, {bar} 数据最早时间 {from_time_str} 早于 此次数据获取起始时间 {start_time_str}, 停止获取数据") + logger.warning(f"本轮获取{symbol}, {bar} 数据最早时间 {from_time_str} 早于 此次数据获取起始时间 {start_time_str}, 停止获取数据") # candels中仅保留start_time之后的数据 candles = [candle for candle in candles if int(candle[0]) >= start_time] if len(candles) > 0: @@ -147,7 +151,7 @@ class MarketData: end_time = from_time - 1 time.sleep(0.5) except Exception as e: - logging.error(f"请求出错: {e}") + logger.error(f"请求出错: {e}") break if all_data: @@ -169,14 +173,14 @@ class MarketData: df = df[['symbol', 'bar', 'timestamp', 'date_time', 'open', 'high', 'low', 'close', 'volume', 'volCcy', 'volCCyQuote', 'create_time']] df.sort_values('timestamp', inplace=True) df.reset_index(drop=True, inplace=True) - logging.info(f"总计获取 {len(df)} 条 K 线数据(仅confirm=1)") + logger.info(f"总计获取 {len(df)} 条 K 线数据(仅confirm=1)") # 获取df中date_time的最早时间与最新时间 earliest_time = df['date_time'].min() latest_time = df['date_time'].max() - logging.info(f"本轮更新{symbol}, {bar} 数据最早时间: {earliest_time}, 最新时间: {latest_time}") + logger.info(f"本轮更新{symbol}, {bar} 数据最早时间: {earliest_time}, 最新时间: {latest_time}") return df else: - logging.warning(f"未获取到{symbol}, {bar} 最新数据,请稍后再试") + logger.warning(f"未获取到{symbol}, {bar} 最新数据,请稍后再试") return None def set_buy_and_sell_sz(self, symbol: str, candles: list, columns: list): @@ -201,6 +205,7 @@ class MarketData: def get_historical_candlesticks_from_api(self, symbol, bar, end_time, limit): response = None count = 0 + while True: try: response = self.market_api.get_history_candlesticks( @@ -212,8 +217,13 @@ class MarketData: if response: break except Exception as e: - logging.error(f"请求出错: {e}") + logger.error(f"请求出错: {e}") count += 1 + # 重新初始化,目的是为了避免长时间运行,导致被平台段封禁 + self.market_api = Market.MarketAPI( + api_key=self.api_key, api_secret_key=self.secret_key, passphrase=self.passphrase, + flag=self.flag + ) if count > 3: break time.sleep(10) @@ -233,8 +243,13 @@ class MarketData: if response: break except Exception as e: - logging.error(f"请求出错: {e}") + logger.error(f"请求出错: {e}") count += 1 + # 重新初始化,目的是为了避免长时间运行,导致被平台段封禁 + self.market_api = Market.MarketAPI( + api_key=self.api_key, api_secret_key=self.secret_key, passphrase=self.passphrase, + flag=self.flag + ) if count > 3: break time.sleep(5) diff --git a/core/biz/market_monitor.py b/core/biz/market_monitor.py index 5ad69e3..bde56d3 100644 --- a/core/biz/market_monitor.py +++ b/core/biz/market_monitor.py @@ -3,10 +3,9 @@ import numpy as np from metrics_config import METRICS_CONFIG from config import BAR_THRESHOLD from time import time +import core.logger as logging -import logging - -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger def create_metrics_report( @@ -29,16 +28,16 @@ def create_metrics_report( date_time = row["date_time"] if only_output_huge_volume: if huge_volume == 1: - logging.info( + logger.info( f"symbol: {symbol} {bar} window_size: {window_size} date_time: {date_time} 巨量" ) else: - logging.info( + logger.info( f"symbol: {symbol} {bar} window_size: {window_size} date_time: {date_time} 非巨量,此次不发送相关数据" ) return else: - logging.info( + logger.info( f"symbol: {symbol} {bar} window_size: {window_size} date_time: {date_time}" ) @@ -51,7 +50,7 @@ def create_metrics_report( low = round(float(row["low"]), 10) pct_chg = round(float(row["pct_chg"]), 4) if only_output_rise and pct_chg < 0: - logging.info( + logger.info( f"symbol: {symbol} {bar} window_size: {window_size} date_time: {date_time} 下跌,不发送相关数据" ) return @@ -63,9 +62,9 @@ def create_metrics_report( else: contents.append(f"## {brief} 交易量报告") if now_datetime_str is not None: - contents.append(f"## {symbol} {bar} 滑动窗口: {window_size} 最新数据时间: {now_datetime_str}") + contents.append(f"## 滑动窗口: {window_size} 最新数据时间: {now_datetime_str}") else: - contents.append(f"## {symbol} {bar} 滑动窗口: {window_size} 交易周期时间: {date_time}") + contents.append(f"## 滑动窗口: {window_size} 交易周期时间: {date_time}") k_shape = str(row["k_shape"]) contents.append(f"### 价格信息") contents.append(f"当前价格: {close}, 开盘价: {open}, 最高价: {high}, 最低价: {low}") @@ -160,6 +159,7 @@ def create_metrics_report( if ma_divergence_value < 1: long_short_info["空"].append(f"均线形态: {ma_divergence}") if is_short: + check_long_short = "空" if is_over_sell: check_over_sell = "超卖" else: @@ -354,7 +354,7 @@ def get_last_huge_volume_record(all_data: pd.DataFrame, bar: str, timestamp: int results.append(f"最近十个周期内,出现巨量的次数: {huge_volume_in_ten_period_count}") return results except Exception as e: - logging.error(f"获取最近一次巨量记录信息失败: {e}") + logger.error(f"获取最近一次巨量记录信息失败: {e}") results.append(f"获取最近一次巨量记录信息失败: {e}") return results diff --git a/core/biz/metrics_calculation.py b/core/biz/metrics_calculation.py index 2710927..225c10d 100644 --- a/core/biz/metrics_calculation.py +++ b/core/biz/metrics_calculation.py @@ -45,15 +45,13 @@ data = metrics.set_ma_long_short_advanced(data, method="hybrid") - "震荡":震荡市场,建议观望或区间交易 """ -import logging +import core.logger as logging import pandas as pd import numpy as np import talib as tb from talib import MA_Type -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) +logger = logging.logger class MetricsCalculation: @@ -72,7 +70,7 @@ class MetricsCalculation: return df def macd(self, df: pd.DataFrame): - logging.info("计算MACD指标") + logger.info("计算MACD指标") data = np.array(df.close) ndata = len(data) m, n, T = 12, 26, 9 @@ -112,7 +110,7 @@ class MetricsCalculation: return df def kdj(self, df: pd.DataFrame): - logging.info("计算KDJ指标") + logger.info("计算KDJ指标") low_list = df["low"].rolling(window=9).min() low_list.fillna(value=df["low"].expanding().min(), inplace=True) high_list = df["high"].rolling(window=9).max() @@ -149,7 +147,7 @@ class MetricsCalculation: KDJ_K < 30, KDJ_D < 30, KDJ_J < 20: 超卖 否则为"徘徊" """ - logging.info("设置KDJ形态") + logger.info("设置KDJ形态") # 初始化kdj_pattern列 df["kdj_pattern"] = "徘徊" @@ -204,7 +202,7 @@ class MetricsCalculation: 使用20个周期的滚动窗口计算相对统计特征,避免绝对阈值过于严格的问题 """ - logging.info("设置均线多空和发散") + logger.info("设置均线多空和发散") # 通过趋势强度计算多空 # 震荡:不满足多空条件的其他情况 @@ -265,7 +263,7 @@ class MetricsCalculation: required_columns = ["timestamp", "close", "dif", "macd", "kdj_j"] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: - print(f"缺少必要的列: {missing_columns}") + logger.info(f"缺少必要的列: {missing_columns}") return df # 按时间戳排序(升序) @@ -362,7 +360,7 @@ class MetricsCalculation: required_columns = ["timestamp", "close", "dif", "macd", "kdj_j"] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: - print(f"缺少必要的列: {missing_columns}") + logger.info(f"缺少必要的列: {missing_columns}") return df # 按时间戳排序(升序) @@ -416,7 +414,7 @@ class MetricsCalculation: 支持所有均线交叉类型:5上穿10/20/30,10上穿20/30,20上穿30 以及对应的下穿信号:30下穿20/10/5, 20下穿10/5,10下穿5 """ - logging.info("计算均线指标") + logger.info("计算均线指标") df["ma5"] = df["close"].rolling(window=5).mean().dropna() df["ma10"] = df["close"].rolling(window=10).mean().dropna() df["ma20"] = df["close"].rolling(window=20).mean().dropna() @@ -492,7 +490,7 @@ class MetricsCalculation: return df def rsi(self, df: pd.DataFrame): - logging.info("计算RSI指标") + logger.info("计算RSI指标") df["rsi_14"] = tb.RSI(df["close"].values, timeperiod=14) df["rsi_signal"] = "" rsi_high = df["rsi_14"] > 70 @@ -507,7 +505,7 @@ class MetricsCalculation: return df def boll(self, df: pd.DataFrame): - logging.info("计算BOLL指标") + logger.info("计算BOLL指标") df["boll_upper"], df["boll_middle"], df["boll_lower"] = tb.BBANDS( df["close"].values, timeperiod=20, matype=MA_Type.SMA ) @@ -524,7 +522,7 @@ class MetricsCalculation: 超卖:价格接近下轨,且KDJ超卖 震荡:其他情况 """ - logging.info("设置BOLL形态") + logger.info("设置BOLL形态") # 初始化boll_pattern列 df["boll_pattern"] = "震荡" @@ -532,7 +530,7 @@ class MetricsCalculation: required_columns = ["close", "boll_upper", "boll_lower", "kdj_j"] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: - print(f"缺少必要的列: {missing_columns}") + logger.info(f"缺少必要的列: {missing_columns}") return df # 计算价格与布林带的距离百分比 @@ -600,7 +598,7 @@ class MetricsCalculation: - 长:K线实体或影线较长 - 超长:K线实体和影线都很长 """ - logging.info("设置K线长度") + logger.info("设置K线长度") # 检查必要的列是否存在 required_columns = ["close", "open", "high", "low"] missing_columns = [col for col in required_columns if col not in df.columns] @@ -707,12 +705,12 @@ class MetricsCalculation: - 超大实体:实体占比70%-90% - 光头光脚:实体占比>90%(非一字情况) """ - logging.info("设置K线形状") + logger.info("设置K线形状") # 检查必要的列是否存在 required_columns = ["close", "open", "high", "low"] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: - print(f"缺少必要的列: {missing_columns}") + logger.info(f"缺少必要的列: {missing_columns}") return df # 计算K线的基本特征 @@ -923,7 +921,7 @@ class MetricsCalculation: - "statistical": 统计分布方法 - "hybrid": 混合方法 """ - logging.info(f"使用{method}方法设置均线多空") + logger.info(f"使用{method}方法设置均线多空") if method == "weighted_voting": return self._weighted_voting_method(data) @@ -936,7 +934,7 @@ class MetricsCalculation: elif method == "hybrid": return self._hybrid_method(data) else: - logging.warning(f"未知的方法: {method},使用默认加权投票方法") + logger.warning(f"未知的方法: {method},使用默认加权投票方法") return self._weighted_voting_method(data) def _weighted_voting_method(self, data: pd.DataFrame): diff --git a/core/biz/quant_trader.py b/core/biz/quant_trader.py index 933de49..7b96e84 100644 --- a/core/biz/quant_trader.py +++ b/core/biz/quant_trader.py @@ -3,10 +3,10 @@ import okx.Trade as Trade import okx.MarketData as Market import okx.PublicData as Public import pandas as pd -import logging +import core.logger as logging from typing import Optional -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') +logger = logging.logger class QuantTrader: def __init__(self, @@ -60,20 +60,20 @@ class QuantTrader: details = balance.get('details', []) for detail in details: if detail.get('ccy') == 'USDT': - logging.info(f"USDT余额: {detail.get('availBal')}") + logger.info(f"USDT余额: {detail.get('availBal')}") result['USDT'] = float(detail.get('availBal', 0)) if detail.get('ccy') == self.symbol_prefix: - logging.info(f"{self.symbol_prefix}余额: {detail.get('availBal')}") + logger.info(f"{self.symbol_prefix}余额: {detail.get('availBal')}") result[self.symbol_prefix] = float(detail.get('availBal', 0)) if detail.get('ccy') == self.symbol_swap: - logging.info(f"{self.symbol_swap}余额: {detail.get('availBal')}") + logger.info(f"{self.symbol_swap}余额: {detail.get('availBal')}") result[self.symbol_swap] = float(detail.get('availBal', 0)) return result else: - logging.error(f"获取余额失败: {search_result}") + logger.error(f"获取余额失败: {search_result}") return {} except Exception as e: - logging.error(f"获取余额异常: {e}") + logger.error(f"获取余额异常: {e}") return {} def get_current_price(self, symbol: str = None) -> Optional[float]: @@ -89,16 +89,16 @@ class QuantTrader: data = result.get('data', []) if data and 'last' in data[0]: price = float(data[0]['last']) - logging.info(f"当前{symbol_prefix}价格: ${price:,.2f}") + logger.info(f"当前{symbol_prefix}价格: ${price:,.2f}") return price else: - logging.error(f"ticker数据格式异常: {data}") + logger.error(f"ticker数据格式异常: {data}") return None else: - logging.error(f"获取价格失败: {result}") + logger.error(f"获取价格失败: {result}") return None except Exception as e: - logging.error(f"获取价格异常: {e}") + logger.error(f"获取价格异常: {e}") return None def get_kline_data(self, symbol: str = None, after: str = None, before: str = None, bar: str = '1m', limit: int = 100) -> Optional[pd.DataFrame]: @@ -129,7 +129,7 @@ class QuantTrader: if result.get('code') == '0': data = result.get('data', []) if not data: - logging.warning("K线数据为空") + logger.warning("K线数据为空") return None df = pd.DataFrame(data, columns=[ 'timestamp', 'open', 'high', 'low', 'close', @@ -140,10 +140,10 @@ class QuantTrader: df['timestamp'] = pd.to_datetime(df['timestamp'].astype(int), unit='ms', errors='coerce') return df else: - logging.error(f"获取K线数据失败: {result}") + logger.error(f"获取K线数据失败: {result}") return None except Exception as e: - logging.error(f"获取K线数据异常: {e}") + logger.error(f"获取K线数据异常: {e}") return None def place_market_order(self, side: str, size: float) -> Optional[str]: @@ -154,7 +154,7 @@ class QuantTrader: if side == 'sell': try: if symbol_balance < size: - logging.error(f"{self.symbol_prefix}余额不足,目前余额: {symbol_balance}") + logger.error(f"{self.symbol_prefix}余额不足,目前余额: {symbol_balance}") return None result = self.trade_api.place_order( instId=self.symbol, @@ -164,20 +164,20 @@ class QuantTrader: sz=str(size) ) if result.get('code') == '0': - logging.info(f"下单成功: {side} {size} {self.symbol_prefix}") + logger.info(f"下单成功: {side} {size} {self.symbol_prefix}") return result['data'][0]['ordId'] else: - logging.error(f"下单失败: {result}") + logger.error(f"下单失败: {result}") return None except Exception as e: - logging.error(f"下单异常: {e}") + logger.error(f"下单异常: {e}") return None elif side == 'buy': try: instrument_result = self.public_api.get_instruments(instType="SPOT", instId=self.symbol) instrument_data = instrument_result.get("data", []) if not instrument_data: - logging.error(f"未获取到合约信息: {instrument_result}") + logger.error(f"未获取到合约信息: {instrument_result}") return None min_sz = float(instrument_data[0].get("minSz", 0)) if size < min_sz: @@ -186,7 +186,7 @@ class QuantTrader: last_price = float(ticker["data"][0]["last"]) usdt_amount = float(last_price * size) if usdt_balance < usdt_amount: - logging.error(f"USDT余额不足,目前余额: {usdt_balance}") + logger.error(f"USDT余额不足,目前余额: {usdt_balance}") return None result = self.trade_api.place_order( instId=self.symbol, @@ -196,16 +196,16 @@ class QuantTrader: sz=str(usdt_amount) ) if result.get('code') == '0': - logging.info(f"下单成功: {side} {usdt_amount} USDT") + logger.info(f"下单成功: {side} {usdt_amount} USDT") return result['data'][0]['ordId'] else: - logging.error(f"下单失败: {result}") + logger.error(f"下单失败: {result}") return None except Exception as e: - logging.error(f"下单异常: {e}") + logger.error(f"下单异常: {e}") return None else: - logging.error(f"不支持的下单方向: {side}") + logger.error(f"不支持的下单方向: {side}") return None # 设置杠杆倍数 @@ -218,9 +218,9 @@ class QuantTrader: posSide=posSide ) if result["code"] == "0": - logging.info(f"设置杠杆倍数 {leverage}x 成功") + logger.info(f"设置杠杆倍数 {leverage}x 成功") else: - logging.error(f"设置杠杆失败: {result['msg']}") + logger.error(f"设置杠杆失败: {result['msg']}") return result["code"] == "0" # 计算保证金需求 @@ -231,10 +231,10 @@ class QuantTrader: contract_value = quantity * slot * price # 每张 0.01 BTC initial_margin = contract_value / leverage recommended_margin = initial_margin * (1 + buffer_ratio) - logging.info(f"开仓{self.symbol_swap}价格: {price:.2f} USDT") - logging.info(f"合约总价值: {contract_value:.2f} USDT") - logging.info(f"初始保证金: {initial_margin:.2f} USDT") - logging.info(f"推荐保证金 (含 {buffer_ratio*100}% 缓冲): {recommended_margin:.2f} USDT") + logger.info(f"开仓{self.symbol_swap}价格: {price:.2f} USDT") + logger.info(f"合约总价值: {contract_value:.2f} USDT") + logger.info(f"初始保证金: {initial_margin:.2f} USDT") + logger.info(f"推荐保证金 (含 {buffer_ratio*100}% 缓冲): {recommended_margin:.2f} USDT") return recommended_margin, price # 开空头仓位(卖出空单) @@ -243,7 +243,7 @@ class QuantTrader: # 计算所需保证金和开仓价格 margin_data = self.calculate_margin(quantity, leverage, slot, buffer_ratio) if not margin_data: - logging.error("无法计算保证金,终止下单") + logger.error("无法计算保证金,终止下单") return None, None required_margin, entry_price = margin_data @@ -251,7 +251,7 @@ class QuantTrader: balance = self.get_account_balance() avail_bal = balance.get('USDT') if avail_bal is None or avail_bal < required_margin: - logging.error(f"保证金不足,需至少 {required_margin:.2f} USDT,当前余额: {avail_bal}") + logger.error(f"保证金不足,需至少 {required_margin:.2f} USDT,当前余额: {avail_bal}") return None, None # 设置杠杆 @@ -270,10 +270,10 @@ class QuantTrader: } result = self.trade_api.place_order(**order_data) if result.get("code") == "0": - logging.info(f"开空单成功,订单ID: {result['data'][0]['ordId']}") + logger.info(f"开空单成功,订单ID: {result['data'][0]['ordId']}") return result["data"][0]["ordId"], entry_price else: - logging.error(f"开空单失败: {result.get('msg', result)}") + logger.error(f"开空单失败: {result.get('msg', result)}") return None, None # 平空单(买入平仓) @@ -290,10 +290,10 @@ class QuantTrader: } result = self.trade_api.place_order(**order_data) if result.get("code") == "0": - logging.info(f"平空单成功,订单ID: {result['data'][0]['ordId']}") + logger.info(f"平空单成功,订单ID: {result['data'][0]['ordId']}") return True else: - logging.error(f"平空单失败: {result.get('msg', result)}") + logger.error(f"平空单失败: {result.get('msg', result)}") return False def get_minimun_order_size(self) -> None: @@ -304,10 +304,10 @@ class QuantTrader: instrument = result.get("data", [{}])[0] min_sz = float(instrument.get("minSz", 0)) lot_sz = float(instrument.get("lotSz", 0)) - logging.info(f"最小交易量 (minSz): {min_sz} {self.symbol_prefix}") - logging.info(f"交易量精度 (lotSz): {lot_sz} {self.symbol_prefix}") + logger.info(f"最小交易量 (minSz): {min_sz} {self.symbol_prefix}") + logger.info(f"交易量精度 (lotSz): {lot_sz} {self.symbol_prefix}") else: - logging.error(f"错误: {result.get('msg', result)}") + logger.error(f"错误: {result.get('msg', result)}") except Exception as e: - logging.error(f"异常: {str(e)}") + logger.error(f"异常: {str(e)}") diff --git a/core/biz/strategy.py b/core/biz/strategy.py index fc4305f..cd17f7a 100644 --- a/core/biz/strategy.py +++ b/core/biz/strategy.py @@ -1,11 +1,11 @@ import time from datetime import datetime -import logging +import core.logger as logging from typing import Optional import pandas as pd from core.biz.quant_trader import QuantTrader -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') +logger = logging.logger class QuantStrategy: def __init__(self, @@ -31,10 +31,10 @@ class QuantStrategy: 计算简单移动平均线 """ if 'close' not in df: - logging.error("DataFrame缺少'close'列,无法计算SMA") + logger.error("DataFrame缺少'close'列,无法计算SMA") return pd.Series([float('nan')] * len(df)) if len(df) < period: - logging.warning(f"数据长度不足{period},SMA结果将包含NaN") + logger.warning(f"数据长度不足{period},SMA结果将包含NaN") return df['close'].rolling(window=period).mean() def calculate_rsi(self, df: pd.DataFrame, period: int = 14) -> pd.Series: @@ -42,10 +42,10 @@ class QuantStrategy: 计算RSI指标 """ if 'close' not in df: - logging.error("DataFrame缺少'close'列,无法计算RSI") + logger.error("DataFrame缺少'close'列,无法计算RSI") return pd.Series([float('nan')] * len(df)) if len(df) < period: - logging.warning(f"数据长度不足{period},RSI结果将包含NaN") + logger.warning(f"数据长度不足{period},RSI结果将包含NaN") delta = df['close'].diff() gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() @@ -58,111 +58,111 @@ class QuantStrategy: """ 简单移动平均线策略 """ - logging.info("=== 执行移动平均线策略 ===") + logger.info("=== 执行移动平均线策略 ===") try: df = self.quant_trader.get_kline_data(bar='5m', limit=max(50, sma_long_period+2)) except Exception as e: - logging.error(f"获取K线数据失败: {e}") + logger.error(f"获取K线数据失败: {e}") return if df is None or len(df) < max(sma_short_period, sma_long_period, 2): - logging.warning("数据不足,无法执行策略") + logger.warning("数据不足,无法执行策略") return df['sma_short'] = self.calculate_sma(df, sma_short_period) df['sma_long'] = self.calculate_sma(df, sma_long_period) if len(df) < 2: - logging.warning("数据不足2条,无法判断金叉死叉") + logger.warning("数据不足2条,无法判断金叉死叉") return latest = df.iloc[-1] prev = df.iloc[-2] if pd.isna(latest['sma_short']) or pd.isna(latest['sma_long']) or pd.isna(prev['sma_short']) or pd.isna(prev['sma_long']): - logging.warning("均线数据存在NaN,跳过本次信号判断") + logger.warning("均线数据存在NaN,跳过本次信号判断") return - logging.info(f"短期均线: {latest['sma_short']:.2f}") - logging.info(f"长期均线: {latest['sma_long']:.2f}") - logging.info(f"当前价格: {latest['close']:.2f}") + logger.info(f"短期均线: {latest['sma_short']:.2f}") + logger.info(f"长期均线: {latest['sma_long']:.2f}") + logger.info(f"当前价格: {latest['close']:.2f}") if (latest['sma_short'] > latest['sma_long'] and prev['sma_short'] <= prev['sma_long']): - logging.info("信号: 买入") + logger.info("信号: 买入") self.quant_trader.place_market_order('buy', self.quant_trader.position_size) elif (latest['sma_short'] < latest['sma_long'] and prev['sma_short'] >= prev['sma_long']): - logging.info("信号: 卖出") + logger.info("信号: 卖出") self.quant_trader.place_market_order('sell', self.quant_trader.position_size) else: - logging.info("信号: 持仓观望") + logger.info("信号: 持仓观望") def rsi_strategy(self, period: int = 14, oversold: int = 30, overbought: int = 70) -> None: """ RSI策略 """ - logging.info("=== 执行RSI策略 ===") + logger.info("=== 执行RSI策略 ===") try: df = self.quant_trader.get_kline_data(bar='5m', limit=max(50, period+2)) except Exception as e: - logging.error(f"获取K线数据失败: {e}") + logger.error(f"获取K线数据失败: {e}") return if df is None or len(df) < period: - logging.warning("数据不足,无法执行策略") + logger.warning("数据不足,无法执行策略") return df['rsi'] = self.calculate_rsi(df, period) latest_rsi = df['rsi'].iloc[-1] if pd.isna(latest_rsi): - logging.warning("最新RSI为NaN,跳过本次信号判断") + logger.warning("最新RSI为NaN,跳过本次信号判断") return - logging.info(f"当前RSI: {latest_rsi:.2f}") + logger.info(f"当前RSI: {latest_rsi:.2f}") if latest_rsi < oversold: - logging.info("信号: RSI超卖,买入") + logger.info("信号: RSI超卖,买入") self.quant_trader.place_market_order('buy', self.quant_trader.position_size) elif latest_rsi > overbought: - logging.info("信号: RSI超买,卖出") + logger.info("信号: RSI超买,卖出") self.quant_trader.place_market_order('sell', self.quant_trader.position_size) else: - logging.info("信号: RSI正常区间,持仓观望") + logger.info("信号: RSI正常区间,持仓观望") def grid_trading_strategy(self, grid_levels: int = 5, grid_range: float = 0.02) -> None: """ 网格交易策略 """ if grid_levels <= 0: - logging.error("网格数必须大于0") + logger.error("网格数必须大于0") return if grid_range <= 0: - logging.error("网格范围必须大于0") + logger.error("网格范围必须大于0") return - logging.info(f"=== 执行网格交易策略 (网格数: {grid_levels}, 范围: {grid_range*100}%) ===") + logger.info(f"=== 执行网格交易策略 (网格数: {grid_levels}, 范围: {grid_range*100}%) ===") try: current_price = self.quant_trader.get_current_price() except Exception as e: - logging.error(f"获取当前价格失败: {e}") + logger.error(f"获取当前价格失败: {e}") return if current_price is None: - logging.warning("当前价格获取失败") + logger.warning("当前价格获取失败") return grid_prices = [] for i in range(grid_levels): price = current_price * (1 + grid_range * (i - grid_levels//2) / grid_levels) grid_prices.append(price) - logging.info(f"网格价格: {[f'${p:.2f}' for p in grid_prices]}") + logger.info(f"网格价格: {[f'${p:.2f}' for p in grid_prices]}") try: df = self.quant_trader.get_kline_data(bar='1m', limit=10) except Exception as e: - logging.error(f"获取K线数据失败: {e}") + logger.error(f"获取K线数据失败: {e}") return if df is None or len(df) == 0 or 'close' not in df: - logging.warning("K线数据无效,无法执行网格策略") + logger.warning("K线数据无效,无法执行网格策略") return latest_price = df['close'].iloc[-1] if pd.isna(latest_price): - logging.warning("最新价格为NaN,跳过本次信号判断") + logger.warning("最新价格为NaN,跳过本次信号判断") return closest_grid = min(grid_prices, key=lambda x: abs(x - latest_price)) - logging.info(f"当前价格: ${latest_price:.2f}, 最近网格: ${closest_grid:.2f}") + logger.info(f"当前价格: ${latest_price:.2f}, 最近网格: ${closest_grid:.2f}") if latest_price < closest_grid * 0.995: - logging.info("信号: 价格下跌,网格买入") + logger.info("信号: 价格下跌,网格买入") self.quant_trader.place_market_order('buy', self.quant_trader.position_size) elif latest_price > closest_grid * 1.005: - logging.info("信号: 价格上涨,网格卖出") + logger.info("信号: 价格上涨,网格卖出") self.quant_trader.place_market_order('sell', self.quant_trader.position_size) else: - logging.info("信号: 价格在网格内,持仓观望") + logger.info("信号: 价格在网格内,持仓观望") def run_strategy_loop(self, strategy: str = 'sma', @@ -172,16 +172,16 @@ class QuantStrategy: 运行策略循环 """ if interval <= 0: - logging.error("循环间隔必须大于0秒") + logger.error("循环间隔必须大于0秒") return - logging.info(f"开始运行{strategy}策略,间隔{interval}秒") + logger.info(f"开始运行{strategy}策略,间隔{interval}秒") while True: try: - logging.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) try: self.quant_trader.get_account_balance() except Exception as e: - logging.error(f"获取账户余额失败: {e}") + logger.error(f"获取账户余额失败: {e}") if strategy == 'sma': sma_short_period = trading_config.get("sma_short_period", 5) sma_long_period = trading_config.get("sma_long_period", 20) @@ -196,14 +196,14 @@ class QuantStrategy: grid_range = trading_config.get("grid_range", 0.02) self.grid_trading_strategy(grid_levels, grid_range) else: - logging.error("未知策略") + logger.error("未知策略") break - logging.info(f"等待{interval}秒后继续...") + logger.info(f"等待{interval}秒后继续...") time.sleep(interval) except KeyboardInterrupt: - logging.info("策略运行被用户中断") + logger.info("策略运行被用户中断") break except Exception as e: - logging.error(f"策略运行异常: {e}") + logger.error(f"策略运行异常: {e}") time.sleep(interval) \ No newline at end of file diff --git a/core/biz/trade_data.py b/core/biz/trade_data.py index 7a4fcad..97cc230 100644 --- a/core/biz/trade_data.py +++ b/core/biz/trade_data.py @@ -1,14 +1,13 @@ import time from datetime import datetime, timedelta -import logging from typing import Optional import pandas as pd import okx.MarketData as Market from core.utils import timestamp_to_datetime from core.db.db_trade_data import DBTradeData +import core.logger as logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') - +logger = logging.logger class TradeData: def __init__(self, @@ -57,7 +56,7 @@ class TradeData: if result: break except Exception as e: - logging.error(f"请求出错: {e}") + logger.error(f"请求出错: {e}") count += 1 if count > 3: break @@ -75,7 +74,7 @@ class TradeData: from_date_time = timestamp_to_datetime(from_time) to_date_time = timestamp_to_datetime(to_time) - logging.info(f"获得交易数据,最早时间: {from_date_time}, 最近时间: {to_date_time}") + logger.info(f"获得交易数据,最早时间: {from_date_time}, 最近时间: {to_date_time}") df = pd.DataFrame(trades) # 过滤时间范围 @@ -112,7 +111,7 @@ class TradeData: else: return None except Exception as e: - logging.error(f"获取历史交易数据失败: {e}") + logger.error(f"获取历史交易数据失败: {e}") return None diff --git a/core/db/__pycache__/db_huge_volume_data.cpython-312.pyc b/core/db/__pycache__/db_huge_volume_data.cpython-312.pyc index a76e2d6f695bfc76012f9d653b3497565a164cad..b90e83a3b612fef5f19e21ad753195cc225d235c 100644 GIT binary patch delta 3065 zcmbVOZA?>F7`~_9<+f1T3Rou;7K{}pYIO?ZLl_^m)`Eg;76mH3p~c>|p0*QR{D@N{ znH#}loO38K%Zy}+y4+-2rhjHkmc>7kXlA;xk(m9PiE&FzmL=Q!-Xb50E@^)}=RN1V z_j%s;^qlvc(MRN)KZx;;(U`@-@82&j2FFK?i`xdF^9dK=0wm1&h}1he#d?n8wc2M%QI_EUR}y$Twd%L=UaelreR5+PBDC;Vc}UJJ8e(FASA zncUf#YM2os0onrN4n<1ikHxUtgk7G>ATZG-q8Tbqf8tzYzj)H74w zMt(YDjvG+3t!9f9$vaN+HQ{J~zd%#keSR7VMfzzCkfp4w*JdpmSEQBNU}z?J)anE{ zP|8MZrDTxZv@I?%4||8acbBx>Gq@fY%u_nUaL!Q9PrC96UHO%5_jRjiJsT^iem(*g z$#Y5dinMi5Vj-Kur6DX`5iFlsqbK?Y!&1L3iES>*_160GJR@uEud2= zon2BxUVDiSD5PwV@}d=e!DM8QLphr_^`X2Hz>a@Cv~(xHfilQ`S=#*!dN=`&aF5NL zA^T+2IrCV-`GQ{zbE7-7v;d-e$ttkQ!arXX+!73q?JCmdzpxtJ7)?1w(-IGfv9Rzt zhO;^yPWH>fnfos}Cv?v14fl2HDemSz6&vBL(mzELq-XvXYaS;l0~HcU8tD;};Y0}b zhrC;|_9g|ozbD{N_#K*sHfKs1-y5bL3`_$6D+xb1N983|NShAcfSX7!I-?KBNb?{f*=(sJNTXv0vdckE} zMO&dssRp8>)6kj#?;s$&AJx-Ye&-Bk$kw^e zt8M|dIJ;C)$bMKiovrlyn>Cp-ZtxI+?QASzhw6b%Dy}4o%+{>)EwB2h99wVWyx6KU zj;+?ikKx)KnO(bRT{y!`bLu_nn=@-CXDjcy(T4gA8FL%mBn4~tY?q8Sy`3SGrNzTP zeJ;~-SEg}Y+T`Y~aJkW;*>(HYJ&<8+ogO#88rj~PWfQm5Wym(=btzgEY*%A3+uZu> zu#Pt5Nr&6YGNk-v)& zB>m-UB$rubR{~Dd)vmk5M3HDtDPkY*YW1Fn!8x_apR2ex@ls2Omt|7q6&&L;Y^e6G%S3hplu}F-veA@NEjV zFWLy7Q@r>o-XSSvD3YK}?5m*duzXV}Vu6&t5w5&u>K6t1Nkn&`H$D$2-pW)!=tqbl zBoXjkOW#L0h;SI;D8ey>69^{}K1BEkVHjZq0UsLlEVmr+Q#3~rE&_~0sYP3Uo*2+V z&`UJbU~tMa*msyxnwUQl@W<(8=%)C-C>2(w-+%I}E2YH_JObH7a&wQ&L9Za&4Ef)> zYB3r}hJ{*s4JP0SbMch`kClXwpE=7UxAZs8d7pDma;1~p`YF9>xNxZOh~}xP%Rt)6 K6HabqzW)G{QLDfJ delta 2901 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 delta 2708 zcmb_eYiv`=6`t|EaS}g*9oup2I4|encajnwfej^2Xh?R=Mv&c7q3nWVUs7kU?J(Eb zmsZMFLMpV{g=i|_rCmtX9}Q~NgiRj-t*ZV+YE{{b*}!F6*gvh*Uxpy1TU4oY#tkuO ze^9CS$LBNWoI7*p`{tbSpI<+H-1tYMQHR)rO+WnZ#Jtgge>Cse?LSr(M{z-}PU?>7 zFw!8YYPt@xo_?6Oo}Oy5bcnyK_=TnvgY1JSu04(7m7Bg}#&@ubHIpx!DxH8d)4)Ffa5jbX)hgum+r&3ZZqiQqhx|qQMBgkBW83P;+K)w)S%U#riJ{mUClg$8xTbf?@Qw zq4vIU+j;SVVcw88HlEQGMSJxizHhc&XqsY?C+glELbd)!oycfC7dacr zIr?uI_HB5WFK6~We&j_a$3ukKt1$hcvxjo7{?)^)LpkTbnjxGwgdZ0L7Nu_c%u__J zEZGCNjI9!bDNQxSju*=OoW-9VbKsr3*tW<3K$wBAh75u|1aJTV&LO9id!T+P+?sfY2Z<1Rug*VKsY}U-aJhZdttQXOftzp)t0Vi-l$Y9k(60ELk`n$pb1CliGUTi(b<4oyT)=A8~r%2OMupWH{d<+ z<=Ct1g2v;#Gxq8Yq2TKNwYWU~yNRn4H+u$exrT1MSrm|?y@*&wQHxpvoC`)o(TZBy zH*BSTLPvwX0cL{V4gR*0|Eq^O_TE<4Y3B!1Q_U|LeiQU8o0o;0b4S6jldcB;^jbUY zs0W}IwxyYgtJMdhndmTK|6T~(TVxypLL!wRufgyHz)A3llj*cflFYnr-n^6*&cL*Is1o_xGo)t^kqXJzS|WR3+`XFCL*04Q ueGgggqME-T|9xb6zj6&(^2n0)Tn;)u3g)_x-a_Bm(53}k{}2K4$^Qd1Ua-sn diff --git a/core/db/__pycache__/db_market_data.cpython-312.pyc b/core/db/__pycache__/db_market_data.cpython-312.pyc index f85cbaa9213c495bbe96a6b5a8000f8ff6b1e1da..cf781406127b68fdfa694dd50a2868126026a2ff 100644 GIT binary patch delta 2528 zcmb7`e`r%z6vy9v`SsEyHO&$=O|>s+t4+~qja{d0wYA#TIfF=bGZSN)JZoz5(%$!K zZ5URzg4PX6cgOmlFc`zKF*+UtVSngf+h7h_Q5+G6`)m70=Eh)5wy|?w+9Yi)=!1N6 zf4y_Q=iK}5y?>6A$#LTN-r*?Y;Hmrc+F5PN;q5yKm1!=)B}j^k5dEJ|8zM%6ZG6g< zHb=~Q*^siNg@`~n$v9v}6HA|nf#83nMs=&2Muvx%{AZ$`T^$%&AO&zeIDEkrqqYi+i53^ zXE)8eR*GN`tbbQ^L3hwZyU}zH;vhm7;sqg|b!TFVN)JKf5*3G8mF+-a73YofLgQ@U zp3p=)*}Jx$6`pr%0Xu}hiffi~1On}-0%NCPfMNiMiy1fqmF#B7K!DwGRNG2Kuzwuu zjn58PlgoFn?(*gu|GE`19z{R(x*xAoY%K9pn(5HI5Srb0PiWRNO>w`I{GFc=v*E!n$+K4Re7!~o{!A zxPVZpzN+X2cCFq|M%k@;|5)~gH3!QJninKrKpUe7-P-f$hQUfXz*(FTEWwlv2HP=| zFB_)$&C5dZ^kq`1XUqx9Y~8T)#gk9ZFJ$lj)*U3j7k=SdtJQ3~Lu!|Le)gnl-o{4b z815vn4;eB10mM1PUWER+*_LPPdDDYCV@Y=M-_`nlNsX#f{H&ae#~>Ffd(tGZ`KF%> z`q}MQ+Lj$7o0>yQTeS1dPM-8?mv{Myr?@{Blx4qVQ!N)+Q_JhNl3w0y_~;f*JEXz+>vs%G|;m?RG2uMMR(H3dxusf_nXJ^+ti>)h) zHZ)b!R_Se{eQ5g7mlB(F&7`RhP3lY2Hcc9c66vU|Z;de~P1B^&^r836y0{>l=p_5i zx#!+{&Ud~$_uf4}gZ`O8j$a%OD?^{cZ!a9aKkM-HP0_|hCd`D9#0(?-%?`8l%}R!- zaoC8U&mfti=3z5pSVmwc@*%*=hk6Uhyw_dxmFY5@?t;O)nXq|=30pG0;k;wW&&{5B zsU=xgThA+@gcue4inq325{?Rz%tNb{Hlu=~@DbYZvzkfo)6C;B9OV;NzWd3D^2ocU zMv7NWf7%QgCKE<7W$Pyc`A*ej*@|2iDHe%{azwMd#bcQoxRdm86{S1r1l&zQ0j6Fr z4uYefoZxo&T;~VR4$fEf+_3j9*^BOS_VdoO&YN6dfeU;&w8(A2UE~k0eXX42bl$_# z9vaDS!o3i%4`2^KCz-WYmcC5Q6&VMqBUwj2bNa|V>xNvZ8y2{Rly8x1qN)A1p6t}> zlx-U2%vt2{tzRw`?}h9jl{ZuH5ZUMGuE|kw;{w;1YD^^;xt(j5{KY_i?$m;_s_3b5 z_mfM`-GLnWwk~j6Q#%*AZMcha#C8COYAX;?TiT|M?m48{v5=U=@@RBYN{DkT?xcRr zdSp_-$40|^f~Vr^HK!+$uM4YDkX$c3V%Edvt9?Ze(7xq}7`|luI)(lCAegUG@LRG& zfOmNmJ_OAX3W|jSl@cBtBxl^`0+3-qGbsrkCp3c~hjA5*t^;TSXaNXN2w6!%fm^UI-**TMD z=HqclWTZ{aI1v+NT@XA>+x~*pW$@IC#{hB>)2yMG92OH|Ojhs+xm?zW4w2u>JZ9J- znvZNQ_oqD==UjKxYsEOQ&%d zED>OW!s8`)lbU|}YVLZBCk4nbX=Ec{3?L401OQe@fd$r=<}u))db-jMv_JHI%-XVO zlTFoSNFhDd71Kvy`g75obF4{I544&q1Ye7)BSY1iAxz0WsICLD?plYTI*TkFnkv z`u&y$<1}n4dVUz`3rhe|fKC8?L)lgf^{mR+Xow6u`qu9oZCyl2j3$K8VOb3E(x{Rk zv8H>di=;Mt2yX7kKIh?LCBHT|WcyU>HXn-)sGC~KQ89e)7=%dgFxXtlA4$CBB)Qev zU$C+_`Gel^O9mz+gsrf-)tr2G b@Oj|V!2F8`7nxW7HFt7o2>s3IFFo%6M(-(k diff --git a/core/db/__pycache__/db_market_monitor.cpython-312.pyc b/core/db/__pycache__/db_market_monitor.cpython-312.pyc index 9642ab78593bf0e330094f69ccb6d16686712825..96fdf78fce751f9f36d5babfa93cca071823ef47 100644 GIT binary patch delta 2604 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 delta 2294 zcmbVNZA@EL7`~^Ux3o|wsieB#9leyoy=`}HaRYzg zj7k<`$UMnDeyNLzAuNjNpBXhV`(-p3NJh5YWp2MUCdNeli8r~=Iq&

w1Q{>?^{%-=8C$AU=kG5qz_l$`hm8Jo%NIktXFMIrko8& z9};5rH-(t^Ko@n-kdWj2M6E)X?E*X1|}A(LU{ zHp4caqMDrf?9VT(#LceR!o;2Z$@U5HR0AKa1JIFKl4uQE5>L4#5QeA+kd|i~9q*dr z*q?RjiI^fiA$4I(%eg9}OFgK`$TUL(s44(eH>i|kj0QmgZrJ2>8pV1P3n*vJtqux4 zi;tuYtjaY^YMAQ!i%4v$Fd~{^0DkOju#82D2eKtatz^)yrsDBLGH%#U%QOicS_NYa zZz8EHG&35LGxBIAHLA_%r_@DLj;|F)Jd6_hbT7cH#8yfqw&eCXOBaUE4X;)mzTp~R ztL}2(|H|9Fg+Ci9j*yBlObc0QJ1+4!LI*+>yI4}|*$3S%9G&c&l0JXl(zjfmd!lQ> zea?MTY+DoCmi_Bu2bI`kp2J(XAl9zzil=R7zA~@h&l8D((Jo}hWJy>=)N}*P1|hsU z*5b9YIj^sN2S37VVt6^cJi9LTf+vqgjag@-Wk2s2_*i*e#Xkb?LG}jD{$;teQwG}C z#P;Rhb#Wi?-t|qgiHhHehmBTNwDEctoSIhX3@_)RWpmdUW+*Fd1_s>%01@N9{kSp> zHOsA1!7lImp%!;oKaBv=UW7h?{23Q~J(OCWSV5QB9vhc$D|) zsx!cQKX9GBRsF4#?`k{-G{~YgGbbX@1>aksF)Wj*M3V1RdJxs(W=MHwU^kdJiQzgu zp-^RXOqHi$TO+wH0${xOLCmeSpA#wjW9@Y^x7~3X!lj-?cn;urE)_?i8*=2R@d)RR zdCp}NAM(@ZQRK7^=yA>*FGxmZt~MDe#}ud#+wK1lTv_q^Nnix*`SsciEKsgVlJ<+QJQeTF$CPbyv8AW zUX5|^w&}HlVI#dzR9_f9xDnoMB1hiqXqubAX$gew>bYp;n`>M0jhl9J0Upf1mm1%{ z4PT&vtAs6ftb;#ud-sDs?Are32@QQo?&J`i0?UI-esk#mpk~i>-hx5~x`yUzkqxgm zin%h(iDZW2V?yx)FszyBw5m`mDr^Y+xS7&1!_fy%K|h7Jkze<;6RmhNQoNvOKf(aQ zAi@g>xO?ddgjWzwBJ7DF#sLiLxSEnPR7FJ^U~%L6;CF5jYKyY92ZHu$iT43Q#fB{{ zCu6cs&!7$;4R)q?aL#a^l=Z~e!BlcQ5jQLYL;b^)ukacUlMrxKL+sX4v1wK5p>Kdl z;MXyPU(AG%+d}DWq3EvAzAm)i5dwFFefNaY*X=ij(lw#10万条) """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return df = df[self.columns] @@ -180,12 +179,12 @@ class DBData: # 删除临时表 conn.execute(text(f"DROP TABLE IF EXISTS {temp_table_name}")) - logging.info( + logger.info( f"已处理 {min(i+chunk_size, total_rows)}/{total_rows} 条记录" ) - logging.info("数据已成功写入数据库。") + logger.info("数据已成功写入数据库。") except Exception as e: - logging.error(f"数据库连接或写入失败: {e}") + logger.error(f"数据库连接或写入失败: {e}") def insert_data_to_mysql_simple(self, df: pd.DataFrame): """ @@ -195,7 +194,7 @@ class DBData: 注意:会抛出重复键错误,需要额外处理 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return df = df[self.columns] @@ -208,9 +207,9 @@ class DBData: index=False, method="multi", ) - logging.info("数据已成功写入数据库。") + logger.info("数据已成功写入数据库。") except Exception as e: - logging.error(f"数据库连接或写入失败: {e}") + logger.error(f"数据库连接或写入失败: {e}") def query_data(self, sql: str, condition_dict: dict, return_multi: bool = True): """ @@ -245,5 +244,5 @@ class DBData: else: return None except Exception as e: - logging.error(f"查询数据出错: {e}") + logger.error(f"查询数据出错: {e}") return None diff --git a/core/db/db_market_data.py b/core/db/db_market_data.py index b35b41f..d922c95 100644 --- a/core/db/db_market_data.py +++ b/core/db/db_market_data.py @@ -1,9 +1,9 @@ import pandas as pd -import logging +import core.logger as logging from core.db.db_manager import DBData from core.utils import transform_date_time_to_timestamp -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger class DBMarketData: @@ -78,7 +78,7 @@ class DBMarketData: :param df: K线数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql(df) @@ -92,7 +92,7 @@ class DBMarketData: :param df: K线数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_fast(df) @@ -107,7 +107,7 @@ class DBMarketData: :param chunk_size: 分块大小 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_chunk(df, chunk_size) @@ -120,7 +120,7 @@ class DBMarketData: 注意:会抛出重复键错误,需要额外处理 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_simple(df) @@ -489,12 +489,12 @@ class DBMarketData: if start is not None: start = transform_date_time_to_timestamp(start) if start is None: - logging.warning(f"开始时间格式错误: {start}") + logger.warning(f"开始时间格式错误: {start}") return None if end is not None: end = transform_date_time_to_timestamp(end) if end is None: - logging.warning(f"结束时间格式错误: {end}") + logger.warning(f"结束时间格式错误: {end}") return None if start is not None and end is not None: if start > end: diff --git a/core/db/db_market_monitor.py b/core/db/db_market_monitor.py index e578459..5e31045 100644 --- a/core/db/db_market_monitor.py +++ b/core/db/db_market_monitor.py @@ -1,10 +1,10 @@ import pandas as pd -import logging +import core.logger as logging from typing import Optional, List, Dict, Any, Union from core.db.db_manager import DBData from core.utils import transform_date_time_to_timestamp -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger class DBMarketMonitor: @@ -94,7 +94,7 @@ class DBMarketMonitor: :param df: 市场监控数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql(df) @@ -108,7 +108,7 @@ class DBMarketMonitor: :param df: 市场监控数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_fast(df) @@ -123,7 +123,7 @@ class DBMarketMonitor: :param chunk_size: 每块大小 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_chunk(df, chunk_size) @@ -137,7 +137,7 @@ class DBMarketMonitor: :param df: 市场监控数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_simple(df) @@ -432,8 +432,8 @@ class DBMarketMonitor: result = conn.execute(text(sql), condition_dict) conn.commit() deleted_count = result.rowcount - logging.info(f"删除了 {deleted_count} 条旧的市场监控数据") + logger.info(f"删除了 {deleted_count} 条旧的市场监控数据") return deleted_count except Exception as e: - logging.error(f"删除旧数据时发生错误: {e}") + logger.error(f"删除旧数据时发生错误: {e}") return 0 \ No newline at end of file diff --git a/core/db/db_trade_data.py b/core/db/db_trade_data.py index ead917f..775d655 100644 --- a/core/db/db_trade_data.py +++ b/core/db/db_trade_data.py @@ -1,10 +1,10 @@ import pandas as pd -import logging +import core.logger as logging from typing import Optional, List, Dict, Any, Union from core.db.db_manager import DBData from core.utils import transform_date_time_to_timestamp -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger class DBTradeData: @@ -88,7 +88,7 @@ class DBTradeData: :param df: 交易数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql(df) @@ -102,7 +102,7 @@ class DBTradeData: :param df: 交易数据DataFrame """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_fast(df) @@ -117,7 +117,7 @@ class DBTradeData: :param chunk_size: 分块大小 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_chunk(df, chunk_size) @@ -130,7 +130,7 @@ class DBTradeData: 注意:会抛出重复键错误,需要额外处理 """ if df is None or df.empty: - logging.warning("DataFrame为空,无需写入数据库。") + logger.warning("DataFrame为空,无需写入数据库。") return self.db_manager.insert_data_to_mysql_simple(df) diff --git a/core/logger.py b/core/logger.py new file mode 100644 index 0000000..62df942 --- /dev/null +++ b/core/logger.py @@ -0,0 +1,72 @@ +import logging +import time +from logging.handlers import TimedRotatingFileHandler +import os +''' +日志工具类 +''' + + +class Logger: + def __init__(self): + # log文件存储路径 + current_dir = os.getcwd() + if current_dir.endswith('crypto_quant'): + output_folder = r'./output/log/' + elif current_dir.startswith(r'/root/'): + output_folder = r'/root/crypto_quant/output/log/' + else: + output_folder = r'./output/log/' + os.makedirs(output_folder, exist_ok=True) + # add self._log_filename to be adata_yyyyMMddHHmm.log + self._log_filename = os.path.join(output_folder, 'crypto_monitor_{}.log'.format(time.strftime("%Y%m%d%H%M%S", time.localtime()))) + + # self._log_filename = os.path.join(output_folder, 'adata.log') + + ''' + %(levelno)s: 打印日志级别的数值 + %(levelname)s: 打印日志级别名称 + %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0] + %(filename)s: 打印当前执行程序名 + %(funcName)s: 打印日志的当前函数 + %(lineno)d: 打印日志的当前行号 + %(asctime)s: 打印日志的时间 + %(thread)d: 打印线程ID + %(threadName)s: 打印线程名称 + %(process)d: 打印进程ID + %(message)s: 打印日志信息 + ''' + logging.basicConfig() + # 日志信息输出格式 + self._formatter = logging.Formatter('%(asctime)s - %(process)d - %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + # 创建一个日志对象 + self._logger = logging.getLogger() + # self.set_console_logger() + self.set_file_logger() + self._logger.setLevel(logging.INFO) + + def set_console_logger(self): + '''设置控制台日志输出''' + console_handler = logging.StreamHandler() + console_handler.setFormatter(self._formatter) + console_handler.setLevel(logging.INFO) + self._logger.addHandler(console_handler) + + def set_file_logger(self): + '''设置日志文件输出''' + log_file_handler = TimedRotatingFileHandler(filename=self._log_filename, + when="D", + interval=1, + backupCount=3, + encoding='utf-8') + log_file_handler.setFormatter(self._formatter) + log_file_handler.setLevel(logging.INFO) + # log_file_handler.suffix = "%Y%m%d_%H%M%S.log" + self._logger.addHandler(log_file_handler) + + def get_logger(self): + return self._logger + + +logger = Logger().get_logger() diff --git a/core/statistics/price_volume_stats.py b/core/statistics/price_volume_stats.py index 7f8489e..33e34fd 100644 --- a/core/statistics/price_volume_stats.py +++ b/core/statistics/price_volume_stats.py @@ -1,4 +1,4 @@ -import logging +import core.logger as logging import os import pandas as pd import numpy as np @@ -19,9 +19,7 @@ from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp # seaborn支持中文 plt.rcParams["font.family"] = ["SimHei"] -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) +logger = logging.logger class PriceVolumeStats: @@ -61,7 +59,7 @@ class PriceVolumeStats: price_volume_stats_list = [] earliest_market_timestamp = None latest_market_timestamp = None - logging.info( + logger.info( f"开始统计{self.initial_date}到{self.end_date}的价格、成交量、成交量与高价低价关系数据" ) for symbol in self.symbol_list: @@ -86,22 +84,22 @@ class PriceVolumeStats: if data["timestamp"].iloc[-1] > latest_market_timestamp: latest_market_timestamp = data["timestamp"].iloc[-1] # 统计高成交量小时分布 - logging.info(f"统计{symbol} {bar} 巨量小时分布数据") + logger.info(f"统计{symbol} {bar} 巨量小时分布数据") high_volume_hours_data = self.stats_high_volume_hours(data) high_volume_hours_list.append(high_volume_hours_data) huge_high_volume_hours_data = self.stats_high_volume_hours(data, 4) huge_high_volume_hours_list.append(huge_high_volume_hours_data) - logging.info(f"统计{symbol} {bar} 价格数据") + logger.info(f"统计{symbol} {bar} 价格数据") price_stats_data = self.calculate_price_statistics(data) - logging.info(f"统计{symbol} {bar} 涨跌百分比数据") + logger.info(f"统计{symbol} {bar} 涨跌百分比数据") pct_change_stats_data = self.calculate_pct_change_statistics(data) - logging.info(f"统计{symbol} {bar} 价格变化峰值和谷值数据") + logger.info(f"统计{symbol} {bar} 价格变化峰值和谷值数据") peak_valley_data, peak_valley_stats_data = ( self.calculate_price_change_peak_valley_statistics(data) ) - logging.info(f"统计{symbol} {bar} 成交量数据") + logger.info(f"统计{symbol} {bar} 成交量数据") volume_stats_data = self.calculate_volume_statistics(data) - logging.info(f"统计{symbol} {bar} 成交量与高价低价关系数据") + logger.info(f"统计{symbol} {bar} 成交量与高价低价关系数据") price_volume_stats_data = self.calculate_price_volume_statistics( data ) @@ -136,7 +134,7 @@ class PriceVolumeStats: latest_market_date_time = re.sub(r"[\:\-\s]", "", str(latest_market_date_time)) output_file_name = f"price_volume_stats_window_size_{self.window_size}_from_{earliest_market_date_time}_to_{latest_market_date_time}.xlsx" output_file_path = os.path.join(self.stats_output_dir, output_file_name) - logging.info(f"导出{output_file_path}") + logger.info(f"导出{output_file_path}") with pd.ExcelWriter(output_file_path) as writer: price_stats_df.to_excel(writer, sheet_name="价格统计", index=False) pct_change_stats_df.to_excel( @@ -600,7 +598,7 @@ class PriceVolumeStats: } } """ - logging.info(f"将图表输出到{excel_file_path}") + logger.info(f"将图表输出到{excel_file_path}") # 打开已经存在的Excel文件 wb = openpyxl.load_workbook(excel_file_path) @@ -639,7 +637,7 @@ class PriceVolumeStats: chart_rows + 5 ) # Add 5 rows for padding between charts except Exception as e: - logging.error(f"输出Excel Sheet {sheet_name} 失败: {e}") + logger.error(f"输出Excel Sheet {sheet_name} 失败: {e}") continue # Save Excel file wb.save(excel_file_path) diff --git a/core/utils.py b/core/utils.py index 919a1c5..8aeecf4 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,9 +1,9 @@ from datetime import datetime, timezone, timedelta from decimal import Decimal import re -import logging +import core.logger as logging -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger def datetime_to_timestamp(date_str: str) -> int: """ @@ -61,11 +61,11 @@ def transform_date_time_to_timestamp(date_time: int | str): else: date_time = check_date_time_format(date_time) if date_time is None: - logging.error(f"日期时间格式错误: {date_time}") + logger.error(f"日期时间格式错误: {date_time}") return None # 按北京时间字符串处理,转换为毫秒级timestamp date_time = datetime_to_timestamp(date_time) return date_time except Exception as e: - logging.error(f"start参数解析失败: {e}") + logger.error(f"start参数解析失败: {e}") return None \ No newline at end of file diff --git a/core/wechat.py b/core/wechat.py index a47c0ae..ad4ea3d 100644 --- a/core/wechat.py +++ b/core/wechat.py @@ -4,11 +4,11 @@ 但需要管理员提供企业id以及secret信息 通过wechatpy库实现 """ -import logging +import core.logger as logging import requests from config import WECHAT_CONFIG -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') +logger = logging.logger class Wechat: def __init__(self): diff --git a/huge_volume_main.py b/huge_volume_main.py index 67b0b32..7e5b8aa 100644 --- a/huge_volume_main.py +++ b/huge_volume_main.py @@ -5,16 +5,14 @@ from core.db.db_huge_volume_data import DBHugeVolumeData from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp from market_data_main import MarketDataMain from core.wechat import Wechat -import logging +import core.logger as logging from config import MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE from datetime import datetime, timedelta import pandas as pd import os import re -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) +logger = logging.logger class HugeVolumeMain: @@ -55,9 +53,9 @@ class HugeVolumeMain: is_update=False, ) if data is not None and len(data) > 0: - logging.info(f"此次初始化巨量交易数据: {len(data)}条") + logger.info(f"此次初始化巨量交易数据: {len(data)}条") else: - logging.info(f"此次初始化巨量交易数据为空") + logger.info(f"此次初始化巨量交易数据为空") def detect_volume_spike( self, @@ -75,20 +73,20 @@ class HugeVolumeMain: ) if end is None: end = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - logging.info( + logger.info( f"开始处理巨量交易数据: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}" ) data = self.db_market_data.query_market_data_by_symbol_bar( symbol, bar, start, end ) if data is None: - logging.warning( + logger.warning( f"获取行情数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}" ) return None else: if len(data) == 0: - logging.warning( + logger.warning( f"获取行情数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}" ) return None @@ -119,7 +117,7 @@ class HugeVolumeMain: if data is not None and len(data) > 0: self.db_huge_volume_data.insert_data_to_mysql(data) else: - logging.warning( + logger.warning( f"此次处理巨量交易数据为空: {symbol} {bar} {start} {end}" ) return data @@ -162,15 +160,15 @@ class HugeVolumeMain: only_output_huge_volume=False, is_update=True, ) - logging.info( + logger.info( f"更新巨量交易数据: {symbol} {bar} 窗口大小: {window_size} 从 {earliest_date_time} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) if data is not None and len(data) > 0: - logging.info(f"此次更新巨量交易数据: {len(data)}条") + logger.info(f"此次更新巨量交易数据: {len(data)}条") else: - logging.info(f"此次更新巨量交易数据为空") + logger.info(f"此次更新巨量交易数据为空") except Exception as e: - logging.error( + logger.error( f"更新巨量交易数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {earliest_date_time} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {e}" ) @@ -234,7 +232,7 @@ class HugeVolumeMain: if end is None: end = datetime.now().strftime("%Y-%m-%d %H:%M:%S") periods_text = ", ".join([str(period) for period in periods]) - logging.info( + logger.info( f"开始计算巨量出现后,之后{periods_text}个周期,上涨或下跌的比例: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}" ) volume_statistics_data = ( @@ -243,7 +241,7 @@ class HugeVolumeMain: ) ) if volume_statistics_data is None or len(volume_statistics_data) == 0: - logging.warning( + logger.warning( f"获取巨量交易数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}" ) return None @@ -299,12 +297,12 @@ class HugeVolumeMain: start_date_time = timestamp_to_datetime(start_timestamp) end_date_time = timestamp_to_datetime(end_timestamp) - logging.info(f"开始获取巨量交易数据: {start} 到 {end}") + logger.info(f"开始获取巨量交易数据: {start} 到 {end}") huge_volume_data = self.db_huge_volume_data.query_huge_volume_records( start=start_timestamp, end=end_timestamp ) if huge_volume_data is None or len(huge_volume_data) == 0: - logging.warning(f"获取巨量交易数据为空: {start} 到 {end}") + logger.warning(f"获取巨量交易数据为空: {start} 到 {end}") return else: if isinstance(huge_volume_data, list): @@ -325,7 +323,7 @@ class HugeVolumeMain: by=["symbol", "bar", "window_size", "timestamp"], ascending=True ) huge_volume_data = huge_volume_data.reset_index(drop=True) - logging.info(f"获取巨量交易数据: {len(huge_volume_data)}条") + logger.info(f"获取巨量交易数据: {len(huge_volume_data)}条") contents = [] contents.append(f"# 放量交易数据: {start_date_time} 到 {end_date_time}") symbol_list = huge_volume_data["symbol"].unique() @@ -383,7 +381,7 @@ class HugeVolumeMain: # 获得text的字节数 text_length = len(text.encode("utf-8")) - logging.info(f"发送巨量交易数据到微信,字节数: {text_length}") + logger.info(f"发送巨量交易数据到微信,字节数: {text_length}") # with open(os.path.join(self.output_folder, "huge_volume_data.md"), "w", encoding="utf-8") as f: # f.write(text) wechat = Wechat() @@ -438,7 +436,7 @@ class HugeVolumeMain: writer, sheet_name="next_periods_statistics", index=False ) except Exception as e: - logging.error(f"导出Excel文件失败: {e}") + logger.error(f"导出Excel文件失败: {e}") return total_huge_volume_data, total_result_data def plot_huge_volume_data( @@ -493,10 +491,10 @@ def test_send_huge_volume_data_to_wechat(): huge_volume_main = HugeVolumeMain(threshold=2.0) # 获得昨天日期 yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") - logging.info(f"昨天日期: {yesterday}") + logger.info(f"昨天日期: {yesterday}") # 获得今天日期 today = datetime.now().strftime("%Y-%m-%d") - logging.info(f"今天日期: {today}") + logger.info(f"今天日期: {today}") huge_volume_main.send_huge_volume_data_to_wechat(start=yesterday, end=today) diff --git a/market_data_main.py b/market_data_main.py index 0de9d62..2aafc4c 100644 --- a/market_data_main.py +++ b/market_data_main.py @@ -1,4 +1,4 @@ -import logging +import core.logger as logging from datetime import datetime from time import sleep import pandas as pd @@ -21,7 +21,7 @@ from config import ( BAR_THRESHOLD, ) -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger class MarketDataMain: @@ -59,7 +59,7 @@ class MarketDataMain: """ for symbol in self.symbols: for bar in self.bars: - logging.info(f"开始初始化行情数据: {symbol} {bar}") + logger.info(f"开始初始化行情数据: {symbol} {bar}") latest_data = self.db_market_data.query_latest_data(symbol, bar) if latest_data: start = latest_data.get("timestamp") @@ -68,7 +68,7 @@ class MarketDataMain: else: start = datetime_to_timestamp(self.initial_date) start_date_time = self.initial_date - logging.info( + logger.info( f"开始初始化{symbol}, {bar} 行情数据,从 {start_date_time} 开始" ) self.fetch_save_data(symbol, bar, start) @@ -80,12 +80,12 @@ class MarketDataMain: end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") end_time_ts = transform_date_time_to_timestamp(end_time) if end_time_ts is None: - logging.error(f"结束时间格式错误: {end_time}") + logger.error(f"结束时间格式错误: {end_time}") return None start_time_ts = transform_date_time_to_timestamp(start) if start_time_ts is None: - logging.error(f"开始时间格式错误: {start}") + logger.error(f"开始时间格式错误: {start}") return None # 如果bar为5m, 15m, 30m: @@ -110,7 +110,7 @@ class MarketDataMain: current_start_time_ts = start_time_ts start_date_time = timestamp_to_datetime(current_start_time_ts) end_date_time = timestamp_to_datetime(end_time_ts) - logging.info( + logger.info( f"获取行情数据: {symbol} {bar} 从 {start_date_time} 到 {end_date_time}" ) data = self.market_data.get_historical_kline_data( @@ -151,7 +151,7 @@ class MarketDataMain: # data.loc[index, "buy_sz"] = current_buy_sz # data.loc[index, "sell_sz"] = current_sell_sz # except Exception as e: - # logging.error(f"设置buy_sz和sell_sz失败: {e}") + # logger.error(f"设置buy_sz和sell_sz失败: {e}") # continue if data is not None and len(data) > 0: data = data[ @@ -184,7 +184,7 @@ class MarketDataMain: if min_start_time_ts is not None and get_data: # 补充技术指标数据 # 获得min_start_time_ts之前30条数据 - logging.info(f"开始补充技术指标数据: {symbol} {bar}") + logger.info(f"开始补充技术指标数据: {symbol} {bar}") before_data = self.db_market_data.query_data_before_timestamp( symbol, bar, min_start_time_ts, 30 ) @@ -199,7 +199,7 @@ class MarketDataMain: ) if handle_data is not None: if before_data is not None and len(handle_data) <= len(before_data): - logging.error(f"handle_data数据条数小于before_data数据条数: {symbol} {bar}") + logger.error(f"handle_data数据条数小于before_data数据条数: {symbol} {bar}") return None if isinstance(handle_data, list): handle_data = pd.DataFrame(handle_data) @@ -208,14 +208,14 @@ class MarketDataMain: elif isinstance(handle_data, pd.DataFrame): pass else: - logging.error(f"handle_data类型错误: {type(handle_data)}") + logger.error(f"handle_data类型错误: {type(handle_data)}") return None handle_data = self.calculate_metrics(handle_data) if latest_before_timestamp is not None: handle_data = handle_data[handle_data["timestamp"] > latest_before_timestamp] handle_data.reset_index(drop=True, inplace=True) - logging.info(f"开始保存技术指标数据: {symbol} {bar}") + logger.info(f"开始保存技术指标数据: {symbol} {bar}") self.db_market_data.insert_data_to_mysql(handle_data) return data @@ -350,21 +350,21 @@ class MarketDataMain: """ 更新数据 """ - logging.info(f"开始更新行情数据: {symbol} {bar}") + logger.info(f"开始更新行情数据: {symbol} {bar}") latest_data = self.db_market_data.query_latest_data(symbol, bar) if not latest_data: - logging.info(f"{symbol}, {bar} 无数据,开始从{self.initial_date}初始化数据") + logger.info(f"{symbol}, {bar} 无数据,开始从{self.initial_date}初始化数据") data = self.fetch_save_data(symbol, bar, self.initial_date) else: latest_timestamp = latest_data.get("timestamp") if latest_timestamp: latest_timestamp = int(latest_timestamp) latest_date_time = timestamp_to_datetime(latest_timestamp) - logging.info( + logger.info( f"{symbol}, {bar} 上次获取的最新数据时间: {latest_date_time}" ) else: - logging.warning(f"获取{symbol}, {bar} 最新数据失败") + logger.warning(f"获取{symbol}, {bar} 最新数据失败") return data = self.fetch_save_data(symbol, bar, latest_timestamp + 1) return data @@ -373,7 +373,7 @@ class MarketDataMain: """ 批量计算技术指标 """ - logging.info("开始批量计算技术指标") + logger.info("开始批量计算技术指标") start_date_time = MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-15 00:00:00" ) @@ -382,14 +382,14 @@ class MarketDataMain: current_timestamp = transform_date_time_to_timestamp(current_date_time) for symbol in self.symbols: for bar in self.bars: - logging.info(f"开始计算技术指标: {symbol} {bar}") + logger.info(f"开始计算技术指标: {symbol} {bar}") data = self.db_market_data.query_market_data_by_symbol_bar( symbol=symbol, bar=bar, start=start_timestamp - 1, end=current_timestamp ) if data is not None and len(data) > 0: data = pd.DataFrame(data) data = self.calculate_metrics(data) - logging.info(f"开始保存技术指标数据: {symbol} {bar}") + logger.info(f"开始保存技术指标数据: {symbol} {bar}") self.db_market_data.insert_data_to_mysql(data) diff --git a/market_monitor_main.py b/market_monitor_main.py index 36fc570..3782b2f 100644 --- a/market_monitor_main.py +++ b/market_monitor_main.py @@ -6,16 +6,15 @@ from core.db.db_market_monitor import DBMarketMonitor from core.wechat import Wechat from config import MONITOR_CONFIG, MYSQL_CONFIG from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp +import core.logger as logging -import logging import os import pandas as pd from datetime import datetime, timedelta import json import re -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") - +logger = logging.logger class MarketMonitorMain: def __init__(self): @@ -58,7 +57,7 @@ class MarketMonitorMain: json.dump({}, f, ensure_ascii=False, indent=4) return {} except Exception as e: - logging.error(f"获取最后一次报表生成记录失败: {e}") + logger.error(f"获取最后一次报表生成记录失败: {e}") return {} def monitor_realtime_market( @@ -85,7 +84,7 @@ class MarketMonitorMain: ) if real_time_data is None or len(real_time_data) == 0: - logging.error(f"获取最新市场数据失败: {symbol}, {bar}") + logger.error(f"获取最新市场数据失败: {symbol}, {bar}") return latest_realtime_timestamp = real_time_data["timestamp"].iloc[-1] @@ -102,22 +101,22 @@ class MarketMonitorMain: latest_record_timestamp is not None and latest_realtime_timestamp <= latest_record_timestamp ): - logging.info( + logger.info( f"最新市场数据时间戳 {latest_reatime_datetime} 小于等于最新记录时间戳 {latest_record_datetime}, 不进行监控" ) return - logging.info( + logger.info( f"最新市场数据时间 {latest_reatime_datetime}, 上一次记录时间 {latest_record_datetime}" ) else: - logging.info( + logger.info( f"最新市场数据时间 {latest_reatime_datetime}, 上一次记录时间为空" ) real_time_data = self.market_data_main.add_new_columns(real_time_data) - logging.info(f"开始计算技术指标: {symbol} {bar}") + logger.info(f"开始计算技术指标: {symbol} {bar}") real_time_data = self.market_data_main.calculate_metrics(real_time_data) - logging.info(f"开始计算大成交量: {symbol} {bar} 窗口大小: {self.window_size}") + logger.info(f"开始计算大成交量: {symbol} {bar} 窗口大小: {self.window_size}") real_time_data = self.huge_volume_main.huge_volume.detect_huge_volume( data=real_time_data, window_size=self.window_size, @@ -127,7 +126,7 @@ class MarketMonitorMain: output_excel=False, ) if real_time_data is None or len(real_time_data) == 0: - logging.error( + logger.error( f"计算大成交量失败: {symbol} {bar} 窗口大小: {self.window_size}" ) return @@ -135,27 +134,27 @@ class MarketMonitorMain: if only_output_huge_volume: if realtime_row["huge_volume"] == 1: - logging.info(f"监控到巨量: {symbol} {bar} 窗口大小: {self.window_size}") + logger.info(f"监控到巨量: {symbol} {bar} 窗口大小: {self.window_size}") if only_output_over_mean_volume: # 获得huge_volume==1时的volume_ratio的均量 mean_huge_volume_ratio = real_time_data[real_time_data["huge_volume"] == 1]["volume_ratio"].mean() if realtime_row["volume_ratio"] >= mean_huge_volume_ratio: - logging.info(f"监控到巨量且超过均量: {symbol} {bar} 窗口大小: {self.window_size}") + logger.info(f"监控到巨量且超过均量: {symbol} {bar} 窗口大小: {self.window_size}") else: - logging.info( + logger.info( f"监控到巨量但未超过均量: {symbol} {bar} 窗口大小: {self.window_size},退出本次监控" ) return else: - logging.info( + logger.info( f"监控到非巨量: {symbol} {bar} 窗口大小: {self.window_size},退出本次监控" ) return if only_output_rise: if realtime_row["pct_chg"] > 0: - logging.info(f"监控到上涨: {symbol} {bar} 窗口大小: {self.window_size}") + logger.info(f"监控到上涨: {symbol} {bar} 窗口大小: {self.window_size}") else: - logging.info( + logger.info( f"监控到下跌: {symbol} {bar} 窗口大小: {self.window_size},退出本次监控" ) return @@ -179,7 +178,7 @@ class MarketMonitorMain: ) text_length = len(report.encode("utf-8")) - logging.info(f"发送报告到企业微信,字节数: {text_length}") + logger.info(f"发送报告到企业微信,字节数: {text_length}") self.wechat.send_markdown(report) # remove punction in latest_reatime_datetime @@ -201,7 +200,7 @@ class MarketMonitorMain: "report_file_byte_size": report_file_byte_size, } report_data = pd.DataFrame([report_data]) - logging.info(f"插入数据到数据库") + logger.info(f"插入数据到数据库") self.db_market_monitor.insert_data_to_mysql(report_data) if self.latest_record.get(symbol, None) is None: @@ -224,7 +223,7 @@ class MarketMonitorMain: # 获得bar在self.market_data_main.bars中的索引 bar_index = self.market_data_main.bars.index(bar) if bar_index == len(self.market_data_main.bars) - 1: - logging.error(f"已经是最后一个bar: {bar}") + logger.error(f"已经是最后一个bar: {bar}") return None # 获得下一个bar bar = self.market_data_main.bars[bar_index + 1] @@ -233,10 +232,10 @@ class MarketMonitorMain: symbol=symbol, bar=bar, end_time=end_time, limit=100 ) if data is None or len(data) == 0: - logging.error(f"获取实时数据失败: {symbol}, {bar}") + logger.error(f"获取实时数据失败: {symbol}, {bar}") return None data = self.market_data_main.add_new_columns(data) - logging.info(f"开始计算技术指标: {symbol} {bar}") + logger.info(f"开始计算技术指标: {symbol} {bar}") data = self.market_data_main.calculate_metrics(data) row = data.iloc[-1] return row @@ -249,7 +248,7 @@ class MarketMonitorMain: ): for symbol in self.market_data_main.symbols: for bar in self.market_data_main.bars: - logging.info( + logger.info( f"开始监控: {symbol} {bar} 窗口大小: {self.window_size} 行情数据" ) try: @@ -261,7 +260,7 @@ class MarketMonitorMain: only_output_rise, ) except Exception as e: - logging.error( + logger.error( f"监控失败: {symbol} {bar} 窗口大小: {self.window_size} 行情数据: {e}" ) continue diff --git a/monitor_schedule.py b/monitor_schedule.py index f37bcaa..7bff3e9 100644 --- a/monitor_schedule.py +++ b/monitor_schedule.py @@ -1,21 +1,17 @@ from market_monitor_main import MarketMonitorMain -import logging -import time - -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +import core.logger as logging +logger = logging.logger def monitor_schedule(): market_monitor_main = MarketMonitorMain() - logging.info("开始监控") - while True: # 每分钟监控一次 - market_monitor_main.batch_monitor_realtime_market( - only_output_huge_volume=True, - only_output_over_mean_volume=True, - only_output_rise=False, - ) - logging.info("本次循环监控结束,等待30秒") - time.sleep(30) + logger.info("开始监控") + market_monitor_main.batch_monitor_realtime_market( + only_output_huge_volume=True, + only_output_over_mean_volume=True, + only_output_rise=False, + ) + logger.info("本次循环监控结束") if __name__ == "__main__": monitor_schedule() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4e58085..d07f186 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ requests>=2.25.0 sqlalchemy >= 2.0.41 pymysql >= 1.1.1 wechatpy >= 1.8.18 -seaborn >= 0.13.2 \ No newline at end of file +seaborn >= 0.13.2 +schedule >= 1.2.2 \ No newline at end of file diff --git a/statistics_main.py b/statistics_main.py index ff91349..584ca9e 100644 --- a/statistics_main.py +++ b/statistics_main.py @@ -1,7 +1,7 @@ from core.statistics.price_volume_stats import PriceVolumeStats -import logging +import core.logger as logging -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger def main(): diff --git a/trade_data_main.py b/trade_data_main.py index c602570..49b7220 100644 --- a/trade_data_main.py +++ b/trade_data_main.py @@ -1,4 +1,4 @@ -import logging +import core.logger as logging import time from datetime import datetime, timedelta import pandas as pd @@ -13,7 +13,7 @@ from config import ( MYSQL_CONFIG, ) -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") +logger = logging.logger class TradeDataMain: @@ -65,26 +65,26 @@ class TradeDataMain: end_date_time = timestamp_to_datetime(end_time) # 如果db_earliest_time和db_latest_time存在,则需要调整start_time和end_time if db_earliest_time is None or db_latest_time is None: - logging.info(f"数据库无数据:从API获取交易数据: {symbol}, {start_date_time}, {end_date_time}, {limit}") + logger.info(f"数据库无数据:从API获取交易数据: {symbol}, {start_date_time}, {end_date_time}, {limit}") self.trade_data.get_history_trades(symbol, start_time, end_time, limit) else: if db_earliest_time > start_time: db_earliest_date_time = timestamp_to_datetime(db_earliest_time) - logging.info(f"从API补充最早数据:{symbol}, {start_date_time}, {db_earliest_date_time}") + logger.info(f"从API补充最早数据:{symbol}, {start_date_time}, {db_earliest_date_time}") self.trade_data.get_history_trades(symbol, start_time, db_earliest_time + 1, limit) if db_latest_time < end_time: db_latest_date_time = timestamp_to_datetime(db_latest_time) - logging.info(f"从API补充最新数据:{symbol}, {db_latest_date_time}, {end_date_time}") + logger.info(f"从API补充最新数据:{symbol}, {db_latest_date_time}, {end_date_time}") self.trade_data.get_history_trades(symbol, db_latest_time + 1, end_time, limit) final_data = self.trade_data.db_trade_data.query_trade_data_by_symbol(symbol=symbol, start=start_time, end=end_time) if final_data is not None and len(final_data) > 0: - logging.info(f"获取交易数据: {symbol}, {start_date_time}, {end_date_time}") + logger.info(f"获取交易数据: {symbol}, {start_date_time}, {end_date_time}") final_data = pd.DataFrame(final_data) final_data.sort_values(by="ts", inplace=True) final_data.reset_index(drop=True, inplace=True) return final_data else: - logging.info(f"获取交易数据失败: {symbol}, {start_date_time}, {end_date_time}") + logger.info(f"获取交易数据失败: {symbol}, {start_date_time}, {end_date_time}") return None diff --git a/trade_main.py b/trade_main.py index 5fdcb81..fc11a0b 100644 --- a/trade_main.py +++ b/trade_main.py @@ -1,10 +1,10 @@ -import logging +import core.logger as logging from time import sleep from core.biz.quant_trader import QuantTrader from core.biz.strategy import QuantStrategy from config import API_KEY, SECRET_KEY, PASSPHRASE, SANDBOX, TRADING_CONFIG, TIME_CONFIG -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') +logger = logging.logger class BizMain: @@ -39,7 +39,7 @@ class BizMain: quantity = 1 try: # 1. 合约开空单流程 - logging.info("[1] 合约开空单流程:") + logger.info("[1] 合约开空单流程:") # price = self.trader.get_current_price(self.trader.symbol_swap) # logging.info(f"当前合约价格: {price}") slot = 0.01 @@ -49,40 +49,40 @@ class BizMain: # logging.info(f"所需保证金: {margin}, 开仓价格: {entry_price}") order_id, entry_price = self.trader.place_short_order(td_mode, quantity, leverage, slot, buffer_ratio) if order_id: - logging.info(f"开空单成功,订单ID: {order_id}, 开仓价格: {entry_price}") + logger.info(f"开空单成功,订单ID: {order_id}, 开仓价格: {entry_price}") else: - logging.error("开空单失败") + logger.error("开空单失败") except Exception as e: - logging.error(f"合约开空单流程异常: {e}") + logger.error(f"合约开空单流程异常: {e}") sleep(1) try: # 2. 现货卖出比特币流程 - logging.info(f"[2] 现货卖出{self.trader.symbol_prefix}流程:") + logger.info(f"[2] 现货卖出{self.trader.symbol_prefix}流程:") balance = self.trader.get_account_balance() btc_balance = balance.get(self.trader.symbol_prefix, 0) - logging.info(f"当前{self.trader.symbol_prefix}余额: {btc_balance}") + logger.info(f"当前{self.trader.symbol_prefix}余额: {btc_balance}") sell_amount = 0.01 if btc_balance >= sell_amount: order_id = self.trader.place_market_order('sell', sell_amount) if order_id: - logging.info(f"现货卖出{sell_amount}{self.trader.symbol_prefix}成功,订单ID: {order_id}") + logger.info(f"现货卖出{sell_amount}{self.trader.symbol_prefix}成功,订单ID: {order_id}") else: - logging.error(f"现货卖出{self.trader.symbol_prefix}失败") + logger.error(f"现货卖出{self.trader.symbol_prefix}失败") else: - logging.error(f"{self.trader.symbol_prefix}余额不足,无法卖出{sell_amount}{self.trader.symbol_prefix}") + logger.error(f"{self.trader.symbol_prefix}余额不足,无法卖出{sell_amount}{self.trader.symbol_prefix}") except Exception as e: - logging.error(f"现货卖出{self.trader.symbol_prefix}流程异常: {e}") + logger.error(f"现货卖出{self.trader.symbol_prefix}流程异常: {e}") sleep(1) try: # 3. 合约平空单流程 - logging.info("[3] 合约平空单流程:") + logger.info("[3] 合约平空单流程:") result = self.trader.close_short_order(td_mode, quantity) if result: - logging.info("平空单成功") + logger.info("平空单成功") else: - logging.error("平空单失败") + logger.error("平空单失败") except Exception as e: - logging.error(f"合约平空单流程异常: {e}") + logger.error(f"合约平空单流程异常: {e}") if __name__ == "__main__":