From 4958b031c280a35329fe75eee0ded80eb03daf7f Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 27 Apr 2025 15:18:17 +0800 Subject: [PATCH] add token log --- .../__pycache__/crypto_agent.cpython-313.pyc | Bin 22051 -> 24368 bytes cryptoai/agents/crypto_agent.py | 43 ++++ .../__pycache__/deepseek_api.cpython-313.pyc | Bin 10614 -> 17149 bytes cryptoai/api/deepseek_api.py | 186 +++++++++++++++-- cryptoai/config/config.yaml | 10 +- cryptoai/utils/token_usage.py | 194 ++++++++++++++++++ 6 files changed, 415 insertions(+), 18 deletions(-) create mode 100644 cryptoai/utils/token_usage.py diff --git a/cryptoai/agents/__pycache__/crypto_agent.cpython-313.pyc b/cryptoai/agents/__pycache__/crypto_agent.cpython-313.pyc index 57a923d375c0da289edf152ded8255428a1aa4cf..6900ef4ab2e4d6f7c0324ae46210f95de4664408 100644 GIT binary patch delta 2543 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 diff --git a/cryptoai/agents/crypto_agent.py b/cryptoai/agents/crypto_agent.py index f0394b4..209918d 100644 --- a/cryptoai/agents/crypto_agent.py +++ b/cryptoai/agents/crypto_agent.py @@ -490,8 +490,45 @@ class CryptoAgent: print(f"分析结果已保存到:{results_file}") + # 导出 DeepSeek API token 使用情况 + self._export_token_usage() + return results + def _export_token_usage(self) -> None: + """ + 导出 DeepSeek API token 使用情况 + """ + try: + # 每天导出一次详细的JSON数据 + today = datetime.now().strftime("%Y%m%d") + token_usage_dir = os.path.join(self.data_config['storage_path'], 'token_usage') + os.makedirs(token_usage_dir, exist_ok=True) + + json_file = os.path.join(token_usage_dir, f"deepseek_token_usage_{today}.json") + + # 如果文件不存在,导出一次 + if not os.path.exists(json_file): + exported_file = self.deepseek_api.export_token_usage(json_file) + if exported_file: + print(f"DeepSeek API token 使用情况已导出到:{exported_file}") + + # 每次都导出CSV格式的统计数据 + csv_file = os.path.join(token_usage_dir, f"deepseek_token_usage_{today}.csv") + self.deepseek_api.export_token_usage(csv_file, "csv") + + # 输出当前使用情况统计 + stats = self.deepseek_api.get_token_usage_stats() + print(f"DeepSeek API token 使用统计:") + print(f"- 总调用次数: {stats['total_calls']}") + print(f"- 总token数: {stats['total_tokens']}") + print(f"- 输入token: {stats['total_prompt_tokens']}") + print(f"- 输出token: {stats['total_completion_tokens']}") + print(f"- 平均每次调用: {stats['average_tokens_per_call']:.2f} tokens") + + except Exception as e: + print(f"导出 token 使用情况时出错: {e}") + def start_agent(self, run_once: bool = False) -> None: """ 启动智能体 @@ -504,6 +541,8 @@ class CryptoAgent: try: if run_once: self.run_analysis_cycle() + # 导出最终的token使用情况 + self._export_token_usage() else: while True: self.run_analysis_cycle() @@ -515,11 +554,15 @@ class CryptoAgent: except KeyboardInterrupt: print("\n智能体已停止") + # 导出最终的token使用情况 + self._export_token_usage() except Exception as e: print(f"智能体运行时出错: {e}") import traceback traceback.print_exc() + # 发生错误时也尝试导出token使用情况 + self._export_token_usage() def send_notifications(self, symbol: str, analysis_data: Dict[str, Any]) -> bool: """ diff --git a/cryptoai/api/__pycache__/deepseek_api.cpython-313.pyc b/cryptoai/api/__pycache__/deepseek_api.cpython-313.pyc index 0905f9738193a5fe49bd4e11400ac24e31bcc7cc..dcf42c5f12202da87988fae19b25f4b655b01c98 100644 GIT binary patch literal 17149 zcmcJ1X>=1;wr**bW!aYTBpVO1v12R21K40}rhrKpLJYW2;sg?1Vav9NEjcAA3{Coy z0Wec=AOr~MU~UFXZW5Hf>7ZcfkX~=CcGh}7N)i36SIv5lwc4`HuNEEBd8_Y_etVyz zl1fH~bl=?aqRzC>Q0MHu&v*9O;`_8T9RtswLTNpU6%b-Vd^FL>_x}eXCmEJuWlu01 z4P~5+q~)9(LirPlbxKaTPQ|I#sX6sJ4X0VB<+LVBo5H0KS{Mmx!@4Wm6>7pxw~#ak!keUO~0l+uv*7-B3|wb9ZZE1UDAjB#q0|vdeF9|~)J<$x%cc|R z)M@#0HiP8r**R<`)S5+Vsv6X67Usiv)7Wx08*+2tAC5`0HC@`5G)}*$LCfYw>t@{Z ztvC~Tq(?*2FT|dWq&$<&kB&vj7Lc4-q=uNQW@nS!tQo&w1tTdWG}&yCbez-XK*<&p zs+wqoPj+gY&U-X?g7nI^~`zw*KOmE)72 zkNo-Sk;%7)$FICG_4V1Q^GCw|SHtgK2@mX_cyBO#{K)Npdwt?JhXswT-(h{hzDrQ` zxjOB=9f~LqVt@>V>Fm<`|=!K9z?WW$SSOU+!v8^pcL1C&*FXqKV5N3_20-%rC)OKe5>)B?w>IfBX1{q?~+tvuXU=*Dzm`@4P-Sd_Mf%;qc(m$qPQ+ z?%6#aPrtjlwicSH>5PhaO^2(m_N95d4YvONUPp%w^+)Y?x68R(v(j$k?3~Fg->q+P zIX!l#r+V|Qe!Gyi!t3dAagN=Dn8OJ(D}^kN%VX=c_H(Yjeh*a~ZXu6mLLZ=wuxYVC zPYZ~CaSN&rTW_zM!%1s3Yq@j?xeRy-+HE$s-Rk9f1x=6L)(Lt>NR{X$K?6#u+u`gM zQo8M)^$@pnf*SMe9Ox>g+uqw1Z_SMh2(u_GtF^eHsal(jgO7E)yj+Lf+5ucs+rjPX z_qc41cm(FER-7vL6DiNoUF{LX}ma!lF=4-klUxA!Ogs8t)pI zyyCIk%I|V3htq<&3kQ@(Q@@9%86maL#=T(oSUYVVo1o_G9xvzYh%qlvXyG4W`ca6S zWGqZujL{(@`dDNlOFDtY+hQy(mNSe+9-~kI1&Y$N$D#nik+sDbI$n$-B<@|TvPt2E z@%29jt)#?h#F!?c~UbiTB>sd68eG%1BVVclB*^_5PrSf{AxOo;>&AMY)jTvAJO< z1in>8Gw5v~+_PNyAt?441rp~5O?MQTAswt8eIx3|~h#w7)T!G|lt z7P3eOyY1q*2=kRlg&+yRJgDjB;05xK%M=uk<p|31DW-{ z)?jAKfEsdUmz=9PQ!||FU&gmRH`ZngwAuJ=+xcEku+4kAhA(*OV8(zpTC*mQSu?yd zn7L>`{Z}=k&kR*OJh&{NFT0hIGmyrapj$fa89jiIKx@Iok*`nKAaKEmtPDeBCRu5p zCGxTpB_WBdv}eUAU}H!sD@v3VRvwjwr+_Mn+*njGnbTO*39``5Du^s-Ko*qV!!TSO z-2f>IPknhfeCTVbEKrt&O@VNB_|jLCpL`TPb~(oSrplF@TP*CV&6R}Tr_B{No3}4v{A^`m{cT;wZEC!|K?9#qFJdYjLxn{K-86 z^@V~ci%W_{wuLQ&Fgc1BhbWq>!B;K5>hOge$jyhBTMIAz2z;U}9c>Ug!`?ld-PtK5 zuyhiFC$X&#cmYcr2}>^suZ16o5%v^sDOSdBrKO^DcxX zo{@)z45?^?YzjKg0cwSy@eU=7M0{ebJODW}v$938Aj-f_WYr|hY`K?JlPUk7%BsJE z;piscIyW&eNLg|6)3+y2e)wNt)oC*VtV%Y8C@+!~j!jXs>Gu0?+&*&=)cWL=3&8x9 zy{;XVb4`_fz--<=jP*FWd#DT~a(KdMiO!eiNH|;(V`L9!>jYF{bwk8s@7|Tb$fB-} z6((ilB`{swQhfb1HqP(1JMGvTaByPhLWNX6&q%f%`f{`I0+ucyI+-%?==NadGY@9r z2dm)@Df~bHfIRp8Ly$|j6cW=PR)!1*+&R4f(kwY8&QPYndRt;VJp+{zsYFG4Kbn{V zhR6(b|3r_~sSLA0#;Rs$k&KR2vl=DCYHJYsqJuL{Gbs5u(~R2hfWY4}r+o zJZ_;hk!6#R?1MJk@576L?DDFyIrV`#^?bwHz?^lV%16d38v~V%{Gv^P%C=CUX{>N= zpm6T+^MS%8H)j{0_KcO(21;sunn1~-;OwSQZ~s{DoXY>+^xu=Xq;aptU==uIIbT9;mQh{-iuqTsBr*6DY13-VrEn^eg;I zzIf@KG>t(QVKgZ^fn%f!1)(B&YL3e;Lz@4nNNg-#P z$_Jf&fy?n&H|>EvCWY0psZB}~oWAi-1W+bswQR$l2aHOOqgpMq!JAD3lb_b9nl`?4 z7-2?}5=UrJwIQ=ldlHTv>NXUDX%C$94Vz-eBok_YBwxa2qBhB5*1XFwP5^DmTC!-` z@}@NoNp=>SE`Ie46j=|X$bKM2&I2iO*|Z0YBkzF}#s^a5$0=&HqV_d0Prk=6sSM0+ z(wVD`>FJp$*n%zj;@sYEg~T3#`)AiH*mV4xfxZw|bcV8``;-~6j7^t*-E`jGkg;X9 zI9j$CrI0PDSBPV?gB>cF5xK2ItP>Ae6SYx^bzy#F~O!73=&3bkpaPmO1ssy?oO+CrlPN+(^|2n;>ikD$mvXCN4E;H8bOIY2(lf!RbEe5 z^&+!O(Au3~fdbHY8@J%c&2mBQaCU;tP0+*k>gC{U@9uEGaUX_6c4>hD8jaJwqt^kz zZzEJPYr)n4rheE$`}#RNC6bdbwWOsb8s;+ZG071SAQ(Em03z}1<+gXYoSkk83(%T1 ztwj!$F2g;8F9IK)g_svjUlV&r0FIcdrhKn~6>{Q}^RR-=CP8M7;|VX;822Bg(~X8o zvH;B{FIpM!XgacwmYL{Um|!tXWHr1f1NXAW2P+rgp|j2cG$@cji9n)p;<={uIsQSZmb+m zv>@vuU2xk-RfJK3%r2ymD%L)Cw;S!uc)pevtAobcG2^0uago0yXk0t6HdIjSOBw!!ztsP@e-Uq7 zHLy0KtV_uaW#k;&b8ye$Uk>F4Gb(T9o)QmH}S@{kg?$O${~5kSU6^^3K*++^Rj?(xg@zVU~Gvchl^ohph_f7gOj1j z$@_60le^nf_M;Bl`a{m@|Gc|i#u)OyXB4o|1xx&!{4Kok zQQ!r!siJ_s=(OX8zUpRH&VNQUK=wEH8epv}C2JMTH~D32N)_KMu7&vT9$vM4&3wi0 z&5e*4E!C~5)r>x(!T5aLT1GRv1moW-bZZ+G-=>+?&R2XpUj=b`0!Dq7VCcw%3<8-Y?q3P8tx-DFTcBeI zB0zR(wxHpKJZ)Mr77xpq_Kj^Qw$mPqnoVg^IJFjSn|Ns|p%7b#Ou z$>Y?qI!nr8Xa`_3OsNInH^P-RoXu$uo671%K$^ zw5KLnnJsukB~M!{Cg#AHbK-F^CeBDQpe>fgfnxiGYFHCCL;OnCP@^U0uw*y;nM>tM zsnH>|8rI6KaLbV=VdTlEXT_-XKY= zCIB>t90_wRsUR(R7CpR;mNaSZ6vr+mcRz zW5x{Qst{|$LrX@y>O<03ZvU=_}I9qoD*q7J1)+x;hFw#ElQ{k)@E4q9DMF z#|9@qxH5I=C_#g<8yYZ^kBo#5org@icq@? zHO!cGyEAJ$dTgHBIP!Aig^}B1@ALX$etQB?6GdrL7haz_ z@*0e79lN0wngAH8q zgL_N6pzE{kj9QYwV1$Qc0=lT-o&ZhM4idDv36fy;pN2tE9^;sofGG}fX&iYHI7?8b zltU=bfP)NOeL#??s^^;MRS6^wB#E=I??YQD`vSb& zINMQd8w&`?r8hx2;?j{zcZA1y5&FNdW;L)0xsu5>4(KAvg(+pBg5q=4XR1$^@KsHG zUh}{jxHg2Wf-?oTiYmZ-SKp_&!!S8cMW|?wPaVi^jHnbvvm#o?GzYADcQUkj=>x0p zW@DZFck<899$0rXujJIr$6p?5zr5%3J)@O@ytaWgA$|6kz9OKnxS_8M6_%fS^30QG zHw-*^GjH~(UB`Ew_yy_KkaxQJMn+}GR6X1sFfAN2H3v-1{^g@9f~MBNv{3mxUuB@Y zd8~X{pnMs>yp7*%36?*_8%jeZbBCV}lq?u4X$q7y`D;c?gC*++_3$AN)rZW@q4K&= z>BFJo@=#GJ0_B^f=CRWGfztV7r3(Y43xlPL#!6QPN>>I;SKX|t9_|U$v|Qg5tXh31 zGo^UepbqY@GIPcZrhvi31GS;izcgT217XOJEoL_d4U75Z8v}-o5Q*gv6$cG#Jp})y(hJ zy4A}Rzcf$C83 z@sTgav}A7Kru<&jAH#@OT<9}TpmE5jr)JC z<4}8ElyPWPz9^^1TNNH}-JpC6A(OZZxbcgkx1^jI;)%DS;$^O6P=#P=aw90)%iPUn z4B5waAKHETkzrdPqmI|s5i59t(fc2rOmHBKP3DKtjV|_)(o5=}IGOj`*QZ@jKkyPf zHjbGlo7)G&<@UqNte`Vojv0tZMXw~P;2I%En3JI3sM5Ah87gp=1R2!HlLrf%ZB*TW-Mq+Ts?s3h@3qk|GXNdQj~ z0}ik?%)o;W2c|B(KXnNl90$N+*~YE~?Dph}mQ`>|{pYI($v9zzh_j;e8v|y? zf=m;1)5ESwxd#SSO1xIVtO8_fd>!CymP~Ii@M+UPb z_xJsY_w@rBCs3-+T>&>;%9maj~iSq`%1R7b&&4?QNE}4zBYB{O{$7w=OQU5L7&mO;3RwX81x;!b|QT6 zXjFT>wlBQ@H!$338i$TPrg8pZ5uIrzs!bhU4qT5s)C~zFgA9gF14xF%>uG*NPqRs! zIXWIw6}{*OGW22MY|d^wtr|XZ<@TvB;5>?MO>`dSn&6z)<$$||4*TRaINhBOU-<|X zfuxVd&Sn#?$bVDw^>xrbS?Zs&191MfcG)_p(g{1aHuN6C?1ihmo%bY3`D&cV(LW+b;JkG;}iz0g2+z*u~FRRR8og{a2r790hL7w zZ*@I-B5J&6p(#-vk?oMWcg0nN1U$?n1TZV6f8MDpiA5&fJAu3ug&l*Ddh6kZY$5q6 z0reXLk&im55krjBYYes&Z!@hY^#HrSdXl)LfqHuRA5l-zzJH>6nl`SVQ9Z@I4HI4I zOjU4GHh%35I@q};>Q6WB|5VICx4PT^ewrqUnsK=uc*6)fj##0_M@|5aGF6R#I&mJ{ z&%cBwVorSM-`#AYOi!(c@I}P8{Lfe4p^A6v^0_Jh$M}KAj!s?s1R|3kzcM-W1)x3r zM$T@7OJTSH7Mq!PV|a3K|J2t9CWjCIT=<5bK~$16$P(aa;%F$O#bgg}r`>ouLy3%P zs^ySOs;Q>?8%CWSy{M^Lpb{JgjZA*gIo&(Dq2>O00prqvv|CwasJZxupW~nJ;h%GW z3fnFtIxJ&vMuagK5Fup|6_zEcFk($gIL@F>BgdK7p|6unF*(rS-G;3n9F-uCK6nVn zj}fiOti1Uxpm`o__t6sSOvn-^K9)G%!LK3Y$sDZMxVx(YD6rXS537##qp5~6xKB{9 z+U6b5qX~M1AF`1++!?gbu_%=cxGX*3<|ytB4p#3Ji=5TffI)#xh0*IV2KIKeF_H)D z?Xaad)ohwYP3-L&u(#{Q(s-EcHpvqEd6J3Q61xww#J-32M{MxvPW1|KNn}YVvB76} zVOGHgf8l-nZ5Gje)FLi5V~F;^W;D)6i;>R~4H(fj2H!w6EzzPnurv3#0Oq)<8-ylm zQRZHRo(0)21lh~Cap`Y^6XV~#Xw8^7H8k-bC&x$LpnZXEf_(v8DDhYTXdg|TLMmt5 z0Y3pE_G_;Za$-ZHCognX`1E}M1!4s~RuI308guXY=mcYK3Uv)ocEQmGEU*|vEiY&` zbyKq|It$PNDL}J`e(iObOFl1!8dn{iI|U8eQ8A)LX+>9KE^a}^8%Uf|d??UBRusw; zfW{0z899xDi`G;;xMZa)4`monuL)+9f08xa?sEjH9}SvUTwf71KNcz|8)^y^RD*%1 zpw5>cC|K-Y8z^{uPyfOu=p(_7Qf|xBk1YX6?H3?GT+oI@x6$=m+Dq7SNu*> zy|P*Hy9OD?n^o{mP&lY;h{7p{$V|TV@t;%yFp^0Hf}+u%Lm;BKmi`V2EP1%P7o!JP zxhcqqxzO8~#f$(LdAzo#ZV_^l3DfHUR>GUg7q2QwD(+J*F_igrQq`7h20 z-7kNR8Hw(^bVj(3V0_#KcqL54qmYqQ24cIoI1T>;rh&VNjhNsCuFz;Oqqi~Sa*D_) zo_Wb7idnWh=IT}5=dxGNTez(n@A}O0RwCnOIr-fH{Yebgw5eXaJV6I z2yHpUsoV;Dp(h5{i!X%z6UN67y9&Ne4^P8Hxn2ao%4J30S3z$+Z{6=5mSLud4!!5q7;Wg)fd6$9Y9n*5?x+TIua2=Y2fZw$F2Mq75l&_Mh%J6G2#11hW z?VurV>*Bj#;Co&CiyruuTA;$)RNcBJfsco{= z@*hY;k@fOa)ja9UK=S&Wy9@-80=;U%F!Th$^;M%;qaCBhZ(2!`fBMnrBQLU5t zTOtgG*IVwAFtSoM8&*#Mv29ohX-Nj^NS*ohh*8pDdtY7;%GzCe?(%?5W zMhcKd(hSnXzU@aOp$vw+WzH1juIQyEz$kFN1F#J^S;F&(!#IRdl*wPh7`g*gJ+ z!mo%O9W7|}=oXY~TOZ#bq&^P5_-p9@T_B{fU>>pcMU#Y;r}4O;T|#AZ8Rv%(5owg{bErFZIboE~}8kzjZS$ov7#=kK5CiC$B0WqrOrT_o{ delta 2718 zcmZ`*du&tJ89(>w>vtSGj>*F@iSxL`aYG$QLgKtZFeqi*!m=h1OyUHGV0-5}0xESB zq;?$}>d@KLYW-vMuNmzhvq)3-km_X8mjC7`TWK$>L)xkeY0}&!d$p^y?;JaY(C$e7 zo%4O?JLi1&_dSx&UgrLJymiBFw;&iF{lC7z1hpwF0Gop z2$GLUm1SuMliVfAU&ADiR*S3V>QujB9gD})T0DM!_}HO`3i&`dZ4uxb=x<|;91!=5 zbCN2PKZ?J!0E?K?gRSIa$?H@FL)7C7vs=1B`Mhj}ZM~i3WCW{zEDn}IMIRBzhW;yW4GpX#^UoBesk>y>v??IA*Jp~f$BG`!{vXD_WgdkRVq_K;6)9i(40bMwrxXY4JO z&nc70RePXB);sp!lD8a9hil+&!#9I^TdaYf>7Pg5`OZ zn?+Hf0nKu%ZSffrZ0sQ|T@Eu8#)ss_hrjs#QofPnI4fCcQY}un+7RdURFeEI7`$S} z^mRY_N$(eKtw1z1iyS$K!&9L+`xcIgVL#P8`mIYOh6?1rMI<~Md6-y^h!Cm@~03e_q zkJ(GQTMqKR0Qqi9{mBRJ+Sj8uqhzpPYy9_LoW2djo4xmKu7YQGZfDM2sNC~=+urxw zRW}ZOT9jbx6P+gbwc(aw6)mfl5xcM)agHd$3eUlMMUeofd9qvdkk{IN)%}ph|2AjH ziR4T?nYqoEK3keuypYyazFgeYKWa$p8Yb)`SJYtBi&R|&@DkeeLOs9s!u1#4XwSyh zoSg-wlhreMO7y}ztwh-Mv+yWAI!56DfGRvuqdHAJD9X|kLGsbAHvT#yuJ(GFwgz0c z)YaZCRI_$N%XyneJIJ+gn12J2pN3V=vvfavR|p&=t7@iq6rA8Z3G=LSE~Y0J7c+&LUY%Mp=c55uTs zq(fLS)sD0YD~%jow@HBH+tDNB*Zt5;;25MF3n}Z%k0)6R>5H~=)nqQ}=RD+<=#MMU zfjLe9d>(qz*H=#_2VBiT=veqX-KX4d{T*aAPs^JX{!#MgKn>?5?+y$eIZKt56lgwR zkwOCn_;&{3Fa=t$*he8qVF!g~3Ly&AbqoPTxPznzYp>8`V{ex@Rj5dT{`3NP_X>Jn z9Q{~yNS0ed5y6xl{Rf?j0gu$3&E!VkcIOV>{$>$@m=EUl)zSQ=ETnKDbFq-RxHf-jliC({`6Vy)4yLS_)pAYQ@!O6~Di()0@?a?2R3{DQJh|z& zBe`>T{6z#}er(mfI=$*!KDtT8Vn6xG@BuHY0Q&8!#o$w=W{P`>X{6zKL(C*D#2Gnw zJ8Op0e<3xqxDX$}7vU(7hV&+Yq5y6E9x~rU)_chLClp#oAz1%Qo)fw5zaxr_`u_nt Crgp0U diff --git a/cryptoai/api/deepseek_api.py b/cryptoai/api/deepseek_api.py index 06d66a2..ea5f75a 100644 --- a/cryptoai/api/deepseek_api.py +++ b/cryptoai/api/deepseek_api.py @@ -1,9 +1,20 @@ import os import json import requests -from typing import Dict, Any, List, Optional +from typing import Dict, Any, List, Optional, Tuple import time +import logging +import datetime +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler("deepseek_token_usage.log"), + logging.StreamHandler() + ] +) class DeepSeekAPI: """DeepSeek API交互类,用于进行市场分析和预测""" @@ -23,6 +34,17 @@ class DeepSeekAPI: "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } + + # Token 使用统计 + self.token_usage = { + "total_prompt_tokens": 0, + "total_completion_tokens": 0, + "total_tokens": 0, + "calls": [] + } + + # 创建日志记录器 + self.logger = logging.getLogger("DeepSeekAPI") def analyze_market_data(self, market_data: Dict[str, Any]) -> Dict[str, Any]: """ @@ -41,10 +63,16 @@ class DeepSeekAPI: prompt = self._build_market_analysis_prompt(formatted_data) # 调用API获取分析 - response = self._call_api(prompt) + response, usage = self._call_api(prompt, task_type="市场分析", symbol=market_data.get("symbol", "未知")) # 解析响应 - return self._parse_analysis_response(response) + result = self._parse_analysis_response(response) + + # 添加token使用信息 + if usage: + result["_token_usage"] = usage + + return result def predict_price_trend(self, symbol: str, historical_data: Dict[str, Any]) -> Dict[str, Any]: """ @@ -64,10 +92,16 @@ class DeepSeekAPI: prompt = self._build_price_prediction_prompt(symbol, formatted_data) # 调用API获取预测 - response = self._call_api(prompt) + response, usage = self._call_api(prompt, task_type="价格预测", symbol=symbol) # 解析响应 - return self._parse_prediction_response(response) + result = self._parse_prediction_response(response) + + # 添加token使用信息 + if usage: + result["_token_usage"] = usage + + return result def generate_trading_strategy(self, symbol: str, analysis_result: Dict[str, Any], risk_level: str) -> Dict[str, Any]: """ @@ -85,21 +119,112 @@ class DeepSeekAPI: prompt = self._build_trading_strategy_prompt(symbol, analysis_result, risk_level) # 调用API获取策略 - response = self._call_api(prompt) + response, usage = self._call_api(prompt, task_type="交易策略", symbol=symbol) # 解析响应 - return self._parse_strategy_response(response) + result = self._parse_strategy_response(response) + + # 添加token使用信息 + if usage: + result["_token_usage"] = usage + + return result - def _call_api(self, prompt: str) -> Dict[str, Any]: + def get_token_usage_stats(self) -> Dict[str, Any]: + """ + 获取Token使用统计信息 + + Returns: + 包含使用统计的字典 + """ + return { + "total_prompt_tokens": self.token_usage["total_prompt_tokens"], + "total_completion_tokens": self.token_usage["total_completion_tokens"], + "total_tokens": self.token_usage["total_tokens"], + "total_calls": len(self.token_usage["calls"]), + "average_tokens_per_call": self.token_usage["total_tokens"] / len(self.token_usage["calls"]) if self.token_usage["calls"] else 0, + "detailed_calls": self.token_usage["calls"][-10:] # 仅返回最近10次调用详情 + } + + def export_token_usage(self, file_path: str = None, format: str = "json") -> str: + """ + 导出Token使用数据到文件 + + Args: + file_path: 文件路径,如果为None则自动生成 + format: 导出格式,支持'json'或'csv' + + Returns: + 导出文件的路径 + """ + if file_path is None: + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + file_path = f"deepseek_token_usage_{timestamp}.{format}" + + try: + if format.lower() == "json": + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(self.token_usage, f, indent=2, ensure_ascii=False) + elif format.lower() == "csv": + import csv + + with open(file_path, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + # 写入表头 + writer.writerow([ + "timestamp", "task_type", "symbol", "model", + "prompt_tokens", "completion_tokens", "total_tokens", + "duration_seconds" + ]) + + # 写入数据 + for call in self.token_usage["calls"]: + writer.writerow([ + call.get("timestamp", ""), + call.get("task_type", ""), + call.get("symbol", ""), + call.get("model", ""), + call.get("prompt_tokens", 0), + call.get("completion_tokens", 0), + call.get("total_tokens", 0), + call.get("duration_seconds", 0) + ]) + + # 写入总计 + writer.writerow([]) + writer.writerow([ + f"总计 (调用次数: {len(self.token_usage['calls'])})", + "", "", "", + self.token_usage["total_prompt_tokens"], + self.token_usage["total_completion_tokens"], + self.token_usage["total_tokens"], + "" + ]) + else: + raise ValueError(f"不支持的格式: {format},仅支持 'json' 或 'csv'") + + self.logger.info(f"Token使用数据已导出到: {file_path}") + return file_path + + except Exception as e: + error_msg = f"导出Token使用数据时出错: {e}" + self.logger.error(error_msg) + return "" + + def _call_api(self, prompt: str, task_type: str = "未知任务", symbol: str = "未知") -> Tuple[Dict[str, Any], Dict[str, Any]]: """ 调用DeepSeek API Args: prompt: 提示词 + task_type: 任务类型 + symbol: 交易对符号 Returns: - API响应 + (API响应, token使用信息) """ + usage_info = {} + try: endpoint = f"{self.base_url}/chat/completions" @@ -113,14 +238,48 @@ class DeepSeekAPI: "max_tokens": 2000 } + start_time = time.time() response = requests.post(endpoint, headers=self.headers, json=payload) response.raise_for_status() + response_data = response.json() + end_time = time.time() - return response.json() + # 记录token使用情况 + if 'usage' in response_data: + prompt_tokens = response_data['usage'].get('prompt_tokens', 0) + completion_tokens = response_data['usage'].get('completion_tokens', 0) + total_tokens = response_data['usage'].get('total_tokens', 0) + + usage_info = { + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": total_tokens, + "task_type": task_type, + "symbol": symbol, + "model": self.model, + "timestamp": datetime.datetime.now().isoformat(), + "duration_seconds": round(end_time - start_time, 2) + } + + # 更新总计 + self.token_usage["total_prompt_tokens"] += prompt_tokens + self.token_usage["total_completion_tokens"] += completion_tokens + self.token_usage["total_tokens"] += total_tokens + self.token_usage["calls"].append(usage_info) + + # 记录到日志 + self.logger.info( + f"DeepSeek API调用 - 任务: {task_type}, 符号: {symbol}, " + f"输入tokens: {prompt_tokens}, 输出tokens: {completion_tokens}, " + f"总tokens: {total_tokens}, 耗时: {round(end_time - start_time, 2)}秒" + ) + + return response_data, usage_info except Exception as e: - print(f"调用DeepSeek API时出错: {e}") - return {} + error_msg = f"调用DeepSeek API时出错: {e}" + self.logger.error(error_msg) + return {}, usage_info def _format_market_data(self, market_data: Dict[str, Any]) -> str: """ @@ -273,7 +432,8 @@ class DeepSeekAPI: return {"error": "API响应格式不正确", "raw_response": response} except Exception as e: - print(f"解析分析响应时出错: {e}") + error_msg = f"解析分析响应时出错: {e}" + self.logger.error(error_msg) return {"error": str(e), "raw_response": response} def _parse_prediction_response(self, response: Dict[str, Any]) -> Dict[str, Any]: diff --git a/cryptoai/config/config.yaml b/cryptoai/config/config.yaml index 24163a6..278a98b 100644 --- a/cryptoai/config/config.yaml +++ b/cryptoai/config/config.yaml @@ -13,17 +13,17 @@ deepseek: crypto: base_currencies: # - "BTC" - - "ETH" - - "BNB" - - "SOL" - # - "ADA" + # - "ETH" + # - "BNB" + # - "SOL" + - "SUI" quote_currency: "USDT" time_interval: "4h" # 可选: 1m, 5m, 15m, 30m, 1h, 4h, 1d # 数据设置 data: storage_path: "./cryptoai/data" - historical_days: 30 + historical_days: 180 update_interval: 60 # 数据更新间隔(分钟) # Agent设置 diff --git a/cryptoai/utils/token_usage.py b/cryptoai/utils/token_usage.py new file mode 100644 index 0000000..99dd061 --- /dev/null +++ b/cryptoai/utils/token_usage.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +DeepSeek API Token使用情况分析工具 +""" + +import os +import sys +import argparse +import json +from typing import Dict, Any +import pandas as pd +from datetime import datetime + +# 添加项目根目录到Python路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from api.deepseek_api import DeepSeekAPI +from utils.config_loader import ConfigLoader + + +def get_deepseek_api() -> DeepSeekAPI: + """ + 获取已配置的DeepSeekAPI实例 + """ + config_loader = ConfigLoader() + deepseek_config = config_loader.get_deepseek_config() + + if not deepseek_config or 'api_key' not in deepseek_config: + print("错误: 未找到DeepSeek API配置或API密钥") + sys.exit(1) + + return DeepSeekAPI( + api_key=deepseek_config['api_key'], + model=deepseek_config.get('model', 'deepseek-moe-16b-chat') + ) + + +def show_token_usage_stats(): + """ + 显示Token使用统计信息 + """ + api = get_deepseek_api() + stats = api.get_token_usage_stats() + + print("\n===== DeepSeek API Token使用统计 =====") + print(f"总输入Tokens: {stats['total_prompt_tokens']:,}") + print(f"总输出Tokens: {stats['total_completion_tokens']:,}") + print(f"总Tokens: {stats['total_tokens']:,}") + print(f"API调用次数: {stats['total_calls']}") + print(f"平均每次调用Tokens: {stats['average_tokens_per_call']:.2f}") + + if stats['total_calls'] > 0: + print("\n最近调用记录:") + for call in stats['detailed_calls']: + print(f" - {call['timestamp']} | {call['symbol']} | {call['task_type']} | " + f"输入: {call['prompt_tokens']} | 输出: {call['completion_tokens']} | " + f"总计: {call['total_tokens']} | 耗时: {call['duration_seconds']}秒") + + print("\n注意: 这些统计数据仅反映当前程序运行期间的使用情况") + + +def export_token_usage(format_type: str = "json", output_path: str = None): + """ + 导出Token使用数据 + + Args: + format_type: 导出格式,'json'或'csv' + output_path: 输出文件路径 + """ + api = get_deepseek_api() + + if output_path is None: + # 如果未指定路径,则使用默认路径 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_path = f"deepseek_token_usage_{timestamp}.{format_type}" + + file_path = api.export_token_usage(output_path, format_type) + + if file_path: + print(f"\nToken使用数据已导出到: {file_path}") + else: + print("\n导出Token使用数据失败") + + +def analyze_token_usage(json_file: str = None): + """ + 分析Token使用数据并显示统计 + + Args: + json_file: JSON格式的Token使用数据文件 + """ + try: + # 加载数据 + if json_file and os.path.exists(json_file): + with open(json_file, 'r', encoding='utf-8') as f: + data = json.load(f) + else: + # 使用当前内存中的数据 + api = get_deepseek_api() + data = api.token_usage + + if not data or not data.get('calls'): + print("没有可用的Token使用数据进行分析") + return + + # 转换为DataFrame进行分析 + df = pd.DataFrame(data['calls']) + + # 按任务类型分析 + task_analysis = df.groupby('task_type').agg({ + 'prompt_tokens': ['sum', 'mean'], + 'completion_tokens': ['sum', 'mean'], + 'total_tokens': ['sum', 'mean', 'count'] + }) + + # 按符号(交易对)分析 + symbol_analysis = df.groupby('symbol').agg({ + 'total_tokens': ['sum', 'mean', 'count'] + }) + + # 计算总体统计 + total_tokens = data['total_tokens'] + total_calls = len(data['calls']) + + # 显示分析结果 + print("\n===== DeepSeek API Token使用分析 =====") + print(f"总调用次数: {total_calls}") + print(f"总Token使用: {total_tokens:,}") + print(f"平均每次调用Token: {total_tokens / total_calls if total_calls else 0:.2f}") + + print("\n--- 按任务类型分析 ---") + for task, stats in task_analysis.iterrows(): + calls = stats[('total_tokens', 'count')] + total = stats[('total_tokens', 'sum')] + avg = stats[('total_tokens', 'mean')] + print(f"{task}: {calls}次调用, 共{total:,}tokens, 平均{avg:.2f}tokens/次") + + print("\n--- 按交易对分析 ---") + for symbol, stats in symbol_analysis.iterrows(): + calls = stats[('total_tokens', 'count')] + total = stats[('total_tokens', 'sum')] + avg = stats[('total_tokens', 'mean')] + print(f"{symbol}: {calls}次调用, 共{total:,}tokens, 平均{avg:.2f}tokens/次") + + except Exception as e: + print(f"分析Token使用数据时出错: {e}") + + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="DeepSeek API Token使用情况分析工具") + + subparsers = parser.add_subparsers(dest="command", help="命令") + + # 显示统计信息 + show_parser = subparsers.add_parser("show", help="显示Token使用统计") + + # 导出使用数据 + export_parser = subparsers.add_parser("export", help="导出Token使用数据") + export_parser.add_argument( + "--format", "-f", + choices=["json", "csv"], + default="json", + help="导出格式 (默认: json)" + ) + export_parser.add_argument( + "--output", "-o", + help="输出文件路径" + ) + + # 分析使用数据 + analyze_parser = subparsers.add_parser("analyze", help="分析Token使用数据") + analyze_parser.add_argument( + "--input", "-i", + help="输入JSON文件路径,如果未指定则使用当前内存中的数据" + ) + + args = parser.parse_args() + + if args.command == "show": + show_token_usage_stats() + elif args.command == "export": + export_token_usage(args.format, args.output) + elif args.command == "analyze": + analyze_token_usage(args.input) + else: + # 默认显示统计信息 + show_token_usage_stats() + + +if __name__ == "__main__": + main() \ No newline at end of file