From cb5a2df89f9a8173744be2ad8f11504a75b876fe Mon Sep 17 00:00:00 2001 From: lzybetter Date: Thu, 6 Aug 2026 20:44:18 +0800 Subject: [PATCH] first commit --- .gitignore | 2 + .idea/.gitignore | 5 + .idea/inspectionProfiles/Project_Default.xml | 12 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 ++ .idea/stock_agent.iml | 14 +++ .idea/vcs.xml | 6 + README.md | 0 __pycache__/adata_test.cpython-314.pyc | Bin 0 -> 2967 bytes adata_test.py | 70 +++++++++++ main.py | 111 ++++++++++++++++++ 12 files changed, 238 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/stock_agent.iml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 __pycache__/adata_test.cpython-314.pyc create mode 100644 adata_test.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c4ffc4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +.venv/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..710d32f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1b72668 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8387865 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/stock_agent.iml b/.idea/stock_agent.iml new file mode 100644 index 0000000..5df0f6f --- /dev/null +++ b/.idea/stock_agent.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__pycache__/adata_test.cpython-314.pyc b/__pycache__/adata_test.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d152267f7c2c111b1f8e71bf9d6abd8fbb08b670 GIT binary patch literal 2967 zcma)8X>1$E6`tiKp3+K`Eb6u_Q8Gmxjx5Fk z)A~uXCSD#p0n|g?h^~Nifd4CH_)1d7uOwxHRb`LR4$X}qM5)GU6SR!I4nUv|fL1VF z)d{AJI6f=19i|HLjTFZYyr!=F>v3$8DG!B=iYodqPDGpmUXemRRjR+aEGg-HI;g8eCacP`8Z}ZR&VfovN&niu z-A>!5A3Fh_k(|uulxD3k!oEsdla6O+2?uK+U5eh|GfdX(B16GisDpJlznstj>!A@g zKoi^pt6(EELl?BbCVaNd&<0y@4zxlCY=hOXeW)5+?>Jn_$Uz_(v4`6K^`Nbt2FHI6 zz-6o<&tPfulv9(%di5Gz9(|?k!K~WB}W41#qv= zGLDe|uC7+#v%*dXfcu7ZlI=8&xh@C5lx?a_v*@#GSuELn=E)8XFONQp9l&m%%?^h3 znrsJn`85D6pdI*(yXippWM|oeM)}N|zBXbzv4S1g{iJl$6Qte$UY;Bio(pY@(eE^D^V}f zwm0T@|3yY-RbE66Ubw`lbFx5q4mnxBU&SH&E5I|j#TWvzD9zdNHsluWWEU55zqph8 z)vXHP(QhASZ@x80s$uK;oz3MNx#+{((x=%U-I*gD<2fvQ{l~fcOWF5tZol)zqn}*O zt7Sj`S?>PZ*{jj){Jq?#ck^n`VY{j2u6?lm-pyy#p2MD3BPpI;i09t>0an}k-48cE z`w)lv=-mY^l|% zh^Nn%u=TA6cXJ>8p)}*sp{insMpe9kySAU3BW)FHMf8KiK}5MDhJH-s(SQH`2BNHU z7A&y!#p34jZL;~TnA==_xOMmYTMypPF8n(8&L_FIKhEBLb94DEtYD4ktX5C8Oj@T* z?mwBR%cSec_!0#&S%FMioLo&lprns{+yV!~4^ zlUPwzkQZ3N&!OrWoL+uW<``ZGa92>3Dl#EnQS*M0fe(p(QYZ?RDhQZlPQdpIGV>}Y zlUoVtBfKQ~Fc6&ZsekyVi%+e0Q+k~krN3>QRR@Hk|0{Uco?+QJTVAtpa(UTi=eY(M&YH%l-QVqjv z^uaVemZHa&>(}U`Y5G)(K9!t2lcZ0r&}Xr*!I3u9rwsM6*~Gbw%N_q-#?_VV9?iJ= zlHQjyuKwh}(TvNR?0+TW8b}U)J>wclj-Ji9I+Od3WL!PT-m#3UJLx_|RE3PIH@Sa2 zFZj)!mg@HAomSAjf2R!?8=it{qi1I?Xl#nUnyG6^*LA1ry4UM^u9@bkOhe=Y_p~*)^%Hd`)usH>6-q()bwvSU9n?JM>4&AiGy)I@kadg z;^`d|*zZk@#4jwKerf`p-SJ}|n6EoF?0eGowv@ds?u;Ab-%hk8YLe|^>-O=-Eud)x z2V!xh&Fv|3drZ9*{$)7PywWkUZXSK?2F~7{KCq`F7LHv^dX6WZ-}+ zso^k>Uh(UtnP}RSp|I?Kv!V*#=BQRZ;9=W2UdZ%#Cj5=tKfyF lrV+}uBT&F#pO38SoBnFBTpNgwty=rn4Fiw$z|foz=D*kZKmGs! literal 0 HcmV?d00001 diff --git a/adata_test.py b/adata_test.py new file mode 100644 index 0000000..f51754b --- /dev/null +++ b/adata_test.py @@ -0,0 +1,70 @@ +from adata import stock +import pandas as pd + +# 显示所有列 +pd.set_option('display.max_columns', None) +# 显示所有行 +pd.set_option('display.max_rows', None) +# 显示单元格完整内容(不截断长文本) +pd.set_option('display.max_colwidth', None) +# 不自动折行 +pd.set_option('display.expand_frame_repr', False) + + +def get_fina_info(stock_code="600699"): + + df = stock.finance.get_core_index(stock_code=stock_code) + + data = df[['report_date', 'report_type', 'total_rev', 'total_rev_yoy_gr','net_profit_attr_sh', 'net_profit_yoy_gr', + 'non_gaap_net_profit', 'non_gaap_net_profit_yoy_gr', 'roe_wtd', 'gross_margin', 'net_margin', 'quick_ratio', + 'inv_turn_rate', 'acct_recv_turn_rate']] + + last_type = data['report_type'].to_list()[0] + + recent_df = data[data.report_type == last_type].iloc[:2,:].reset_index(drop=True) + + recent_df.rename(columns={"report_date": "报告日期", + "report_type": "报告类型", + "total_rev":"营业总收入", + "total_rev_yoy_gr":"营业总收入同比增长率", + "net_profit_attr_sh":"归母净利润", + "net_profit_yoy_gr":"归母净利润同比增长率", + "non_gaap_net_profit":"扣非净利润", + "non_gaap_net_profit_yoy_gr":"扣非净利润同比增长率", + "roe_wtd":"加权净资产收益率", + "gross_margin":"销售毛利率", + "net_margin":"销售净利率", + "quick_ratio":"速动比率", + "inv_turn_rate":"存货周转率", + "acct_recv_turn_rate":"应收账款周转率" + }, inplace=True) + + formatted_lines = [] + for _, row in recent_df.iterrows(): + line_items = [] + for col, val in zip(recent_df.columns, row): + # 判断是否为数字类型 + if isinstance(val, (int, float)): + # 如果列名包含"率",则格式化为保留两位小数并加上 % + if "率" in col: + formatted_val = f"{val:.2f}%" + else: + # 其他数字:格式化为带千分位、保留两位小数 + formatted_val = f"{val:,.2f}" + else: + # 非数字类型(如日期、文本)直接转为字符串 + formatted_val = str(val) + + line_items.append(f"{col}:{formatted_val}") + + # 将这一期的所有字段用换行符拼接 + formatted_lines.append("\n".join(line_items)) + + # 3. 组装最终报告文本 + final_report = f"近两期{last_type}主要财务数据如下:\n" + "\n\n".join(formatted_lines) + + return final_report + + +if __name__ == "__main__": + print(get_fina_info(stock_code="600105")) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..c71866e --- /dev/null +++ b/main.py @@ -0,0 +1,111 @@ +import os +from langchain_classic.agents import AgentExecutor, create_tool_calling_agent +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain_openai import ChatOpenAI +from dotenv import load_dotenv +from adata import stock +from langchain_core.tools import tool +from telegram import Update +from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters + +load_dotenv() + + +# ================= 你的原有逻辑(完全保留) ================= +@tool +def get_fina_info(stock_code: str) -> str: + """获取指定股票代码的财务数据。当需要获取财务数据的时使用此工具""" + df = stock.finance.get_core_index(stock_code=stock_code) + + data = df[['report_date', 'report_type', 'total_rev', 'total_rev_yoy_gr', 'net_profit_attr_sh', 'net_profit_yoy_gr', + 'non_gaap_net_profit', 'non_gaap_net_profit_yoy_gr', 'roe_wtd', 'gross_margin', 'net_margin', + 'quick_ratio', + 'inv_turn_rate', 'acct_recv_turn_rate']] + + last_type = data['report_type'].to_list()[0] + recent_df = data[data.report_type == last_type].iloc[:2, :].reset_index(drop=True) + + recent_df.rename(columns={"report_date": "报告日期", "report_type": "报告类型", "total_rev": "营业总收入", + "total_rev_yoy_gr": "营业总收入同比增长率", "net_profit_attr_sh": "归母净利润", + "net_profit_yoy_gr": "归母净利润同比增长率", "non_gaap_net_profit": "扣非净利润", + "non_gaap_net_profit_yoy_gr": "扣非净利润同比增长率", "roe_wtd": "加权净资产收益率", + "gross_margin": "销售毛利率", "net_margin": "销售净利率", "quick_ratio": "速动比率", + "inv_turn_rate": "存货周转率", "acct_recv_turn_rate": "应收账款周转率" + }, inplace=True) + + formatted_lines = [] + for _, row in recent_df.iterrows(): + line_items = [] + for col, val in zip(recent_df.columns, row): + if isinstance(val, (int, float)): + if "率" in col: + formatted_val = f"{val:.2f}%" + else: + formatted_val = f"{val:,.2f}" + else: + formatted_val = str(val) + line_items.append(f"{col}:{formatted_val}") + formatted_lines.append("\n".join(line_items)) + + final_report = f"近两期{last_type}主要财务数据如下:\n" + "\n\n".join(formatted_lines) + return final_report + + +class MyOutputParser(StrOutputParser): + def parse(self, text) -> str: + return text.strip() + + +llm = ChatOpenAI(model="deepseek-v4-flash") +tools = [get_fina_info] +template = """ +你是一个专业的股票分析助手。用户会输入一个股票代码,你可以使用工具来获取其财务数据,之后根据获取到的财务数据来分析这家公司的基本面情况。 +""" +chat_prompt = ChatPromptTemplate.from_messages([ + ("system", template), + ("human", "{input}"), + ("placeholder", "{agent_scratchpad}"), +]) + +agent = create_tool_calling_agent(llm, tools, chat_prompt) +agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) + + +# ================= 新增:Telegram 机器人交互逻辑 ================= +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /start 命令""" + await update.message.reply_text("你好!我是你的股票分析助手,请直接发送类似“分析一下600699的基本面”的消息。") + + +async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理普通文本消息,调用你的 Agent""" + user_input = update.message.text + # 提示用户正在处理,因为 Agent 调用工具可能需要几秒钟 + await update.message.reply_text("🤔 正在分析,请稍候...") + + try: + # 调用你原有的 Agent 逻辑 + res = agent_executor.invoke({"input": user_input}) + output = res["output"] + except Exception as e: + output = f"分析出错:{str(e)}" + + await update.message.reply_text(output) + + +if __name__ == "__main__": + # 从环境变量读取 Token + token = os.getenv("TELEGRAM_BOT_TOKEN") + if not token: + raise ValueError("未在 .env 文件中找到 TELEGRAM_BOT_TOKEN") + + proxy_url = "http://127.0.0.1:8889" + + # 启动机器人 + application = ApplicationBuilder().token(token).proxy(proxy_url).get_updates_proxy(proxy_url).build() + application.add_handler(CommandHandler("start", start)) + application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) + + print("Telegram Bot 正在运行...") + application.run_polling() \ No newline at end of file