From 098ba1ad78ea514baf89ce593c2f7a080b215001 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 27 Apr 2025 14:12:27 +0800 Subject: [PATCH] update --- .gitignore | 56 +++ .../__pycache__/crypto_agent.cpython-313.pyc | Bin 17511 -> 20972 bytes cryptoai/agents/crypto_agent.py | 85 +++- .../__pycache__/deepseek_api.cpython-313.pyc | Bin 10265 -> 10614 bytes cryptoai/api/deepseek_api.py | 24 +- cryptoai/config/config.example.yaml | 49 +++ cryptoai/config/config.yaml | 12 +- cryptoai/data/.gitkeep | 0 .../__pycache__/config_loader.cpython-313.pyc | Bin 4056 -> 4301 bytes cryptoai/utils/config_loader.py | 6 +- cryptoai/utils/dingtalk_bot.py | 398 ++++++++++++++++++ 11 files changed, 613 insertions(+), 17 deletions(-) create mode 100644 .gitignore create mode 100644 cryptoai/config/config.example.yaml create mode 100644 cryptoai/data/.gitkeep create mode 100644 cryptoai/utils/dingtalk_bot.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac6ea7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Python缓存文件 +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# 虚拟环境 +venv/ +ENV/ +env/ +.env +.venv + +# IDE相关文件 +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# 日志和数据文件 +*.log +*.csv +*.json +*.pkl +logs/ +*.db + +# 项目特定忽略 +# 忽略数据目录,但保留目录结构 +cryptoai/data/* +!cryptoai/data/.gitkeep + +# 忽略配置文件(包含敏感信息) +cryptoai/config/config.yaml +# 但保留示例配置 +!cryptoai/config/config.example.yaml + +# 忽略生成的分析结果 +cryptoai/data/analysis_results/ \ No newline at end of file diff --git a/cryptoai/agents/__pycache__/crypto_agent.cpython-313.pyc b/cryptoai/agents/__pycache__/crypto_agent.cpython-313.pyc index 3d5158701c38a513c172412167818fd34a4d42c1..fae8180800fdc243cb29205fa4aecaf99c1f43be 100644 GIT binary patch delta 5794 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 diff --git a/cryptoai/agents/crypto_agent.py b/cryptoai/agents/crypto_agent.py index a7af5b1..4fbb173 100644 --- a/cryptoai/agents/crypto_agent.py +++ b/cryptoai/agents/crypto_agent.py @@ -15,6 +15,7 @@ from api.binance_api import BinanceAPI from api.deepseek_api import DeepSeekAPI from models.data_processor import DataProcessor from utils.config_loader import ConfigLoader +from utils.dingtalk_bot import DingTalkBot class CryptoAgent: @@ -36,6 +37,7 @@ class CryptoAgent: self.crypto_config = self.config_loader.get_crypto_config() self.data_config = self.config_loader.get_data_config() self.agent_config = self.config_loader.get_agent_config() + self.dingtalk_config = self.config_loader.get_dingtalk_config() # 初始化API客户端 self.binance_api = BinanceAPI( @@ -52,6 +54,15 @@ class CryptoAgent: # 初始化数据处理器 self.data_processor = DataProcessor(storage_path=self.data_config['storage_path']) + # 初始化钉钉机器人(如果启用) + self.dingtalk_bot = None + if self.dingtalk_config.get('enabled', False): + self.dingtalk_bot = DingTalkBot( + webhook_url=self.dingtalk_config['webhook_url'], + secret=self.dingtalk_config.get('secret') + ) + print("钉钉机器人已启用") + # 设置支持的加密货币 self.base_currencies = self.crypto_config['base_currencies'] self.quote_currency = self.crypto_config['quote_currency'] @@ -306,6 +317,18 @@ class CryptoAgent: "timestamp": datetime.now().isoformat() } + # 如果钉钉机器人已启用,发送分析报告 + if self.dingtalk_bot: + try: + print(f"发送{symbol}分析报告到钉钉...") + response = self.dingtalk_bot.send_analysis_report(symbol, results[symbol]) + if response.get('errcode') == 0: + print(f"{symbol}分析报告发送成功") + else: + print(f"{symbol}分析报告发送失败: {response}") + except Exception as e: + print(f"发送{symbol}分析报告时出错: {e}") + print(f"{symbol}分析完成") else: print(f"跳过{symbol}分析,无法获取数据") @@ -349,7 +372,7 @@ class CryptoAgent: } # 简单策略执行逻辑 - if position == 'BUY': + if position == 'BUY' or '买' in position: # 示例:下单买入 # 在实际应用中,这里应该有更复杂的仓位管理和风险管理逻辑 quantity = 0.01 # 示例数量,实际应用中应该基于资金和风险计算 @@ -364,7 +387,11 @@ class CryptoAgent: result["action"] = "BUY" result["order_result"] = order_result - elif position == 'SELL': + # 如果钉钉机器人已启用,发送交易通知 + if self.dingtalk_bot: + self.send_trade_notification(symbol, "买入", quantity, current_price, strategy) + + elif position == 'SELL' or '卖' in position: # 示例:下单卖出 quantity = 0.01 # 示例数量 @@ -378,6 +405,10 @@ class CryptoAgent: result["action"] = "SELL" result["order_result"] = order_result + # 如果钉钉机器人已启用,发送交易通知 + if self.dingtalk_bot: + self.send_trade_notification(symbol, "卖出", quantity, current_price, strategy) + else: # 持有或其他策略 result["action"] = "HOLD" @@ -386,6 +417,56 @@ class CryptoAgent: print(f"执行{symbol}策略完成:{result['action']}") return result + def send_trade_notification(self, symbol: str, action: str, quantity: float, price: float, strategy: Dict[str, Any]) -> None: + """ + 发送交易通知到钉钉 + + Args: + symbol: 交易对符号 + action: 交易操作(买入或卖出) + quantity: 交易数量 + price: 交易价格 + strategy: 交易策略 + """ + if not self.dingtalk_bot: + return + + try: + # 设置图标 + if action == "买入": + icon = "🟢" + else: + icon = "🔴" + + # 获取策略理由 + reasoning = strategy.get('reasoning', '无理由') + + # 构建通知内容 + title = f"{icon} {symbol} {action}交易执行通知" + text = f"""### {symbol} {action}交易已执行 + +**交易详情**: +- 操作: {icon} **{action}** +- 数量: {quantity} +- 价格: {price} +- 总额: {quantity * price} {self.quote_currency} + +**交易理由**: +{reasoning} + +*交易时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}* + """ + + # 发送通知 + at_mobiles = self.dingtalk_config.get('at_mobiles', []) + at_all = self.dingtalk_config.get('at_all', False) + self.dingtalk_bot.send_markdown(title, text, at_mobiles, at_all) + + print(f"{symbol} {action}交易通知已发送") + + except Exception as e: + print(f"发送交易通知时出错: {e}") + def run_analysis_cycle(self) -> Dict[str, Any]: """ 运行一个完整的分析周期 diff --git a/cryptoai/api/__pycache__/deepseek_api.cpython-313.pyc b/cryptoai/api/__pycache__/deepseek_api.cpython-313.pyc index 1cf5ca1dccbdf07c8ae9a5a67cb01cb4f21ebfa8..0905f9738193a5fe49bd4e11400ac24e31bcc7cc 100644 GIT binary patch delta 870 zcmbOk@GXe%GcPX}0}wnv#+$*YzL9ScFXN8M8+j`nUaa5#bb0sl-5pQ%?teaI#giRt zpG|9jI(y#JCB5(W^gP|U?fJrWPxjA!v2XIz_Fc~w?0LGU|Hbl_7d_3-XSH|*`}=KH z;|pNq$YuV>z#y8(yt$7512bdpkK0v{NJ1l<`wu!{2768z=()`ua+d6j7d9VKWoAtEV%*{UfkB?ti}8T- z7cl+7iBTHJ-4OhN0YslrhR_#+Lcf3oK3FM2r9t!wlM4aC7eXU01Vo&)`@jGax{#g) PW#kuq0gFtQQ~wPBHQ}dh delta 528 zcmewsG&6wjGcPX}0}%XO&YQ7LbtB&*UdGbN8+j`>=kWP5a%3@mWMB}@X4-s^{{u5) z=;Tepemq=U&!%p8*1OJ%tCVrFipUSiq|%(6%;F3kg{0KPA}}K_wX~!tF=w)o=!eM; zVnIrBKnofeK8P@jFn(ZTU=p~%An<`fNKo?wyQqN92L@42oyqeAr6*q!+sha}d4jl? z5Es|ezP6|Ro8UG{Zf23-WE9p>C@D(KOVLqC&d*EBOi9g4PMvHmX^2&PmZULO@!yig zlY69M7}Y1gl#*8mxtV&L0@$S#=ILe*sY+oEW89bv6WlU;t7ll)==6pwKTM r?guMHh!l`IVR9iL_(EvJg@A~Yb{`mkA{WxLAas7w7Z87PwZ?A%b``Fr diff --git a/cryptoai/api/deepseek_api.py b/cryptoai/api/deepseek_api.py index b6501b9..06d66a2 100644 --- a/cryptoai/api/deepseek_api.py +++ b/cryptoai/api/deepseek_api.py @@ -106,7 +106,7 @@ class DeepSeekAPI: payload = { "model": self.model, "messages": [ - {"role": "system", "content": "你是一个专业的加密货币分析助手,擅长分析市场趋势、预测价格走向和提供交易建议。"}, + {"role": "system", "content": "你是一个专业的加密货币分析助手,擅长分析市场趋势、预测价格走向和提供交易建议。请始终使用中文回复,并确保输出格式规范的JSON。"}, {"role": "user", "content": prompt} ], "temperature": 0.2, # 低温度使输出更加确定性 @@ -159,7 +159,7 @@ class DeepSeekAPI: Returns: 提示词 """ - return f"""请分析以下加密货币市场数据,并提供详细的市场分析。 + return f"""请分析以下加密货币市场数据,并提供详细的市场分析。请使用中文回复。 数据: {formatted_data} @@ -172,7 +172,7 @@ class DeepSeekAPI: 5. 关键技术指标解读(如RSI、MACD等) 请以JSON格式回复,包含以下字段: -- market_trend: 市场趋势 (bullish, bearish, neutral) +- market_trend: 市场趋势 (牛市, 熊市, 震荡) - support_levels: 支撑位列表 - resistance_levels: 阻力位列表 - volume_analysis: 交易量分析 @@ -180,7 +180,7 @@ class DeepSeekAPI: - technical_indicators: 技术指标分析 - summary: 总结 -请确保回复为有效的JSON格式。""" +请确保回复为有效的JSON格式,并使用中文进行分析。""" def _build_price_prediction_prompt(self, symbol: str, formatted_data: str) -> str: """ @@ -193,7 +193,7 @@ class DeepSeekAPI: Returns: 提示词 """ - return f"""请基于以下{symbol}的历史数据,预测未来24小时、7天和30天的价格走势。 + return f"""请基于以下{symbol}的历史数据,预测未来24小时、7天和30天的价格走势。请使用中文回复。 历史数据: {formatted_data} @@ -203,13 +203,13 @@ class DeepSeekAPI: 请以JSON格式回复,包含以下字段: - symbol: 交易对符号 - current_price: 当前价格 -- prediction_24h: 24小时预测 (包含 price_range, trend, confidence) -- prediction_7d: 7天预测 (包含 price_range, trend, confidence) -- prediction_30d: 30天预测 (包含 price_range, trend, confidence) +- prediction_24h: 24小时预测 (包含 price_range价格区间, trend趋势, confidence置信度) +- prediction_7d: 7天预测 (包含 price_range价格区间, trend趋势, confidence置信度) +- prediction_30d: 30天预测 (包含 price_range价格区间, trend趋势, confidence置信度) - key_factors: 影响预测的关键因素 - risk_assessment: 风险评估 -请确保回复为有效的JSON格式。""" +请确保回复为有效的JSON格式,并使用中文进行分析。""" def _build_trading_strategy_prompt(self, symbol: str, analysis_result: Dict[str, Any], risk_level: str) -> str: """ @@ -225,7 +225,7 @@ class DeepSeekAPI: """ analysis_json = json.dumps(analysis_result, indent=2) - return f"""请基于以下{symbol}的市场分析结果,生成一个风险等级为{risk_level}的交易策略。 + return f"""请基于以下{symbol}的市场分析结果,生成一个风险等级为{risk_level}的交易策略。请使用中文回复。 分析结果: {analysis_json} @@ -234,7 +234,7 @@ class DeepSeekAPI: 请以JSON格式回复,包含以下字段: - symbol: 交易对符号 -- risk_level: 风险等级 ({risk_level}) +- risk_level: 风险等级 (low低风险, medium中风险, high高风险) - position: 建议仓位 (买入、卖出、持有) - entry_points: 入场点列表 - exit_points: 出场点列表 @@ -244,7 +244,7 @@ class DeepSeekAPI: - strategy_type: 策略类型 (例如:趋势跟踪、反转、突破等) - reasoning: 策略推理过程 -请确保回复为有效的JSON格式。""" +请确保回复为有效的JSON格式,并使用中文进行分析。""" def _parse_analysis_response(self, response: Dict[str, Any]) -> Dict[str, Any]: """ diff --git a/cryptoai/config/config.example.yaml b/cryptoai/config/config.example.yaml new file mode 100644 index 0000000..494b89e --- /dev/null +++ b/cryptoai/config/config.example.yaml @@ -0,0 +1,49 @@ +# Binance API设置 +binance: + api_key: "your_binance_api_key_here" + api_secret: "your_binance_api_secret_here" + test_mode: true # 设置为false将使用实盘交易 + +# DeepSeek AI设置 +deepseek: + api_key: "your_deepseek_api_key_here" + model: "deepseek-chat" # 使用的模型 + +# 加密货币设置 +crypto: + base_currencies: + - "BTC" + - "ETH" + - "BNB" + - "SOL" + - "ADA" + quote_currency: "USDT" + time_interval: "4h" # 可选: 1m, 5m, 15m, 30m, 1h, 4h, 1d + +# 数据设置 +data: + storage_path: "./cryptoai/data" + historical_days: 30 + update_interval: 60 # 数据更新间隔(分钟) + +# Agent设置 +agent: + analysis_interval: 120 # 分析间隔(分钟) + strategies: + - "trend_following" + - "momentum" + - "sentiment" + risk_level: "medium" # 可选: low, medium, high + +# 日志设置 +logging: + level: "INFO" # 可选: DEBUG, INFO, WARNING, ERROR + file_path: "./cryptoai/logs" + +# 钉钉机器人设置 +dingtalk: + enabled: true # 是否启用钉钉机器人 + webhook_url: "https://oapi.dingtalk.com/robot/send?access_token=your_dingtalk_token_here" + secret: "your_dingtalk_secret_here" # 如果没有设置安全设置,可以为空 + at_mobiles: [] # 需要@的手机号列表 + at_all: false # 是否@所有人 \ No newline at end of file diff --git a/cryptoai/config/config.yaml b/cryptoai/config/config.yaml index 2dc4b0e..e7d0fb3 100644 --- a/cryptoai/config/config.yaml +++ b/cryptoai/config/config.yaml @@ -18,7 +18,7 @@ crypto: # - "SOL" # - "ADA" quote_currency: "USDT" - time_interval: "1d" # 可选: 1m, 5m, 15m, 30m, 1h, 4h, 1d + time_interval: "4h" # 可选: 1m, 5m, 15m, 30m, 1h, 4h, 1d # 数据设置 data: @@ -38,4 +38,12 @@ agent: # 日志设置 logging: level: "INFO" # 可选: DEBUG, INFO, WARNING, ERROR - file_path: "./cryptoai/logs" \ No newline at end of file + file_path: "./cryptoai/logs" + +# 钉钉机器人设置 +dingtalk: + enabled: true # 是否启用钉钉机器人 + webhook_url: "https://oapi.dingtalk.com/robot/send?access_token=2278b723cd363bb6f85592c743b59b166e70b9e02a275bb5cedbc33b53a5cbdc" + secret: "your_secret" # 如果没有设置安全设置,可以为空 + at_mobiles: [] # 需要@的手机号列表 + at_all: false # 是否@所有人 \ No newline at end of file diff --git a/cryptoai/data/.gitkeep b/cryptoai/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cryptoai/utils/__pycache__/config_loader.cpython-313.pyc b/cryptoai/utils/__pycache__/config_loader.cpython-313.pyc index 124b0021ad0dfc9b7d55d0dd57776383432f4601..2cb0df3497e79efc5d97c4f6197e975bfa0b4267 100644 GIT binary patch delta 291 zcmca1e^!zAGcPX}0}#|6$?V;mxg#Zk&vtH=p5BV4nPp%TZGrsO&}G_NV>RUQX%+qGxk< zJ)ODY$*x^5TYI1HU3ZHkB{MI*Brzv@GCz+Pk8pZwNjy|!@?0(%U8rPya(-S~X1X8H zup(0sAps;bnTmu!Yz`2c(N9x&@?YL`Q45fe4Tx|65iTIYV{!?fDdU;RtN6A}{=+B2 z`kjG^ReN$Ozn)YeNE&2VktmS3#bJ}1pHiBWYF89Ic`d&TzZWB;_9q4q304FE?uJz; delta 174 zcmX@Bctf7|GcPX}0}%XO&YPhzk+*^A6T`%QQAWXuOErPS`St7qMS|(fnnIiBGP-dx z7H@vSrNBJ-1fKw-(BzwZ=>q0Jtwq)#!X898PY&fbW!y2jn|~YYcLpX_t;taWdagbo zDG3lE3M6iE*yQG?l;)(`75M?Vj6hs01tdN&Gcq#XX5hTdz F3jibbD+~Yt diff --git a/cryptoai/utils/config_loader.py b/cryptoai/utils/config_loader.py index 3b99db4..ba3bd76 100644 --- a/cryptoai/utils/config_loader.py +++ b/cryptoai/utils/config_loader.py @@ -73,4 +73,8 @@ class ConfigLoader: def get_logging_config(self) -> Dict[str, Any]: """获取日志配置""" - return self.get_config('logging') \ No newline at end of file + return self.get_config('logging') + + def get_dingtalk_config(self) -> Dict[str, Any]: + """获取钉钉机器人配置""" + return self.get_config('dingtalk') \ No newline at end of file diff --git a/cryptoai/utils/dingtalk_bot.py b/cryptoai/utils/dingtalk_bot.py new file mode 100644 index 0000000..7b4d696 --- /dev/null +++ b/cryptoai/utils/dingtalk_bot.py @@ -0,0 +1,398 @@ +import json +import requests +import time +import hmac +import hashlib +import base64 +import urllib.parse +import traceback +from typing import Dict, Any, List, Optional, Union + + +class DingTalkBot: + """钉钉机器人工具类,用于发送分析结果到钉钉群""" + + def __init__(self, webhook_url: str, secret: Optional[str] = None): + """ + 初始化钉钉机器人 + + Args: + webhook_url: 钉钉机器人的webhook地址 + secret: 钉钉机器人的签名密钥(如果启用了安全设置) + """ + self.webhook_url = webhook_url + self.secret = secret + + def _sign(self) -> str: + """ + 生成钉钉机器人签名 + + Returns: + 签名后的URL + """ + if not self.secret: + return self.webhook_url + + timestamp = str(round(time.time() * 1000)) + string_to_sign = f"{timestamp}\n{self.secret}" + hmac_code = hmac.new( + self.secret.encode(), + string_to_sign.encode(), + digestmod=hashlib.sha256 + ).digest() + + sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) + return f"{self.webhook_url}×tamp={timestamp}&sign={sign}" + + def send_text(self, content: str, at_mobiles: List[str] = None, at_all: bool = False) -> Dict[str, Any]: + """ + 发送文本消息 + + Args: + content: 消息内容 + at_mobiles: 要@的手机号列表 + at_all: 是否@所有人 + + Returns: + 接口返回结果 + """ + data = { + "msgtype": "text", + "text": {"content": content}, + "at": { + "atMobiles": at_mobiles if at_mobiles else [], + "isAtAll": at_all + } + } + + return self._post(data) + + def send_markdown(self, title: str, text: str, at_mobiles: List[str] = None, at_all: bool = False) -> Dict[str, Any]: + """ + 发送Markdown消息 + + Args: + title: 消息标题 + text: Markdown格式的消息内容 + at_mobiles: 要@的手机号列表 + at_all: 是否@所有人 + + Returns: + 接口返回结果 + """ + data = { + "msgtype": "markdown", + "markdown": { + "title": title, + "text": text + }, + "at": { + "atMobiles": at_mobiles if at_mobiles else [], + "isAtAll": at_all + } + } + + return self._post(data) + + def _post(self, data: Dict[str, Any]) -> Dict[str, Any]: + """ + 发送消息到钉钉 + + Args: + data: 消息数据 + + Returns: + 接口返回结果 + """ + try: + webhook = self._sign() + headers = {'Content-Type': 'application/json; charset=utf-8'} + response = requests.post(webhook, json=data, headers=headers) + return response.json() + except Exception as e: + print(f"发送钉钉消息时出错: {e}") + traceback.print_exc() + return {"errcode": -1, "errmsg": str(e)} + + def format_analysis_result(self, symbol: str, analysis_result: Dict[str, Any]) -> str: + """ + 格式化分析结果为Markdown格式 + + Args: + symbol: 交易对符号 + analysis_result: 分析结果 + + Returns: + 格式化后的Markdown文本 + """ + try: + if not analysis_result or 'error' in analysis_result: + return f"### {symbol} 分析结果错误\n\n获取分析结果时出错: {analysis_result.get('error', '未知错误')}" + + # 提取关键信息 + market_trend = analysis_result.get('market_trend', '未知') + support_levels = analysis_result.get('support_levels', []) + if isinstance(support_levels, list): + support_levels_str = '、'.join([str(level) for level in support_levels]) + else: + support_levels_str = str(support_levels) + + resistance_levels = analysis_result.get('resistance_levels', []) + if isinstance(resistance_levels, list): + resistance_levels_str = '、'.join([str(level) for level in resistance_levels]) + else: + resistance_levels_str = str(resistance_levels) + + volume_analysis = analysis_result.get('volume_analysis', '未知') + market_sentiment = analysis_result.get('market_sentiment', '未知') + summary = analysis_result.get('summary', '无摘要') + + # 根据市场趋势设置颜色标志 + if '牛' in market_trend or 'bull' in str(market_trend).lower(): + trend_icon = "🟢" + elif '熊' in market_trend or 'bear' in str(market_trend).lower(): + trend_icon = "🔴" + else: + trend_icon = "🟡" + + # 构建Markdown文本 + markdown = f"""### {trend_icon} {symbol} 市场分析 + +**市场趋势**: {market_trend} + +**支撑位**: {support_levels_str} + +**阻力位**: {resistance_levels_str} + +**交易量分析**: {volume_analysis} + +**市场情绪**: {market_sentiment} + +**总结**: {summary} + +*分析时间: {time.strftime('%Y-%m-%d %H:%M:%S')}* +""" + return markdown + + except Exception as e: + print(f"格式化分析结果时出错: {e}") + traceback.print_exc() + return f"### {symbol} 格式化分析结果出错\n\n{str(e)}" + + def format_prediction_result(self, symbol: str, prediction_result: Dict[str, Any]) -> str: + """ + 格式化预测结果为Markdown格式 + + Args: + symbol: 交易对符号 + prediction_result: 预测结果 + + Returns: + 格式化后的Markdown文本 + """ + try: + if not prediction_result or 'error' in prediction_result: + return f"### {symbol} 预测结果错误\n\n获取预测结果时出错: {prediction_result.get('error', '未知错误')}" + + # 提取关键信息 + current_price = prediction_result.get('current_price', '未知') + prediction_24h = prediction_result.get('prediction_24h', {}) + prediction_7d = prediction_result.get('prediction_7d', {}) + prediction_30d = prediction_result.get('prediction_30d', {}) + key_factors = prediction_result.get('key_factors', []) + if isinstance(key_factors, list): + key_factors_str = '\n'.join([f"- {factor}" for factor in key_factors]) + else: + key_factors_str = str(key_factors) + + risk_assessment = prediction_result.get('risk_assessment', '未知') + + # 格式化预测数据 + def format_prediction(pred_data): + if not pred_data: + return "无数据" + + price_range = pred_data.get('price_range', '未知') + trend = pred_data.get('trend', '未知') + confidence = pred_data.get('confidence', '未知') + + # 根据趋势设置图标 + if '上升' in str(trend) or '增长' in str(trend) or 'up' in str(trend).lower(): + trend_icon = "📈" + elif '下降' in str(trend) or '下跌' in str(trend) or 'down' in str(trend).lower(): + trend_icon = "📉" + else: + trend_icon = "📊" + + return f"{trend_icon} **{trend}** (价格区间: {price_range}, 置信度: {confidence})" + + # 构建Markdown文本 + markdown = f"""### {symbol} 价格预测 + +**当前价格**: {current_price} + +**24小时预测**: {format_prediction(prediction_24h)} + +**7天预测**: {format_prediction(prediction_7d)} + +**30天预测**: {format_prediction(prediction_30d)} + +**关键影响因素**: +{key_factors_str} + +**风险评估**: {risk_assessment} + +*预测时间: {time.strftime('%Y-%m-%d %H:%M:%S')}* +""" + return markdown + + except Exception as e: + print(f"格式化预测结果时出错: {e}") + traceback.print_exc() + return f"### {symbol} 格式化预测结果出错\n\n{str(e)}" + + def format_strategy_result(self, symbol: str, strategy_result: Dict[str, Any]) -> str: + """ + 格式化策略结果为Markdown格式 + + Args: + symbol: 交易对符号 + strategy_result: 策略结果 + + Returns: + 格式化后的Markdown文本 + """ + try: + if not strategy_result or 'error' in strategy_result: + return f"### {symbol} 策略结果错误\n\n获取策略结果时出错: {strategy_result.get('error', '未知错误')}" + + # 提取关键信息 + risk_level = strategy_result.get('risk_level', '未知') + position = strategy_result.get('position', '未知') + + entry_points = strategy_result.get('entry_points', []) + if isinstance(entry_points, list): + entry_points_str = '、'.join([str(point) for point in entry_points]) + else: + entry_points_str = str(entry_points) + + exit_points = strategy_result.get('exit_points', []) + if isinstance(exit_points, list): + exit_points_str = '、'.join([str(point) for point in exit_points]) + else: + exit_points_str = str(exit_points) + + stop_loss = strategy_result.get('stop_loss', '未知') + take_profit = strategy_result.get('take_profit', '未知') + time_frame = strategy_result.get('time_frame', '未知') + strategy_type = strategy_result.get('strategy_type', '未知') + reasoning = strategy_result.get('reasoning', '无理由') + + # 根据建议仓位设置图标 + if '买' in position or 'buy' in str(position).lower(): + position_icon = "🟢" + elif '卖' in position or 'sell' in str(position).lower(): + position_icon = "🔴" + else: + position_icon = "⚪" + + # 根据风险等级设置图标 + if 'low' in str(risk_level).lower() or '低' in str(risk_level): + risk_icon = "🟢" + elif 'medium' in str(risk_level).lower() or '中' in str(risk_level): + risk_icon = "🟡" + else: + risk_icon = "🔴" + + # 构建Markdown文本 + markdown = f"""### {symbol} 交易策略 + +**建议操作**: {position_icon} {position} + +**风险等级**: {risk_icon} {risk_level} + +**策略类型**: {strategy_type} + +**时间框架**: {time_frame} + +**入场点**: {entry_points_str} + +**出场点**: {exit_points_str} + +**止损位**: {stop_loss} + +**止盈位**: {take_profit} + +**策略理由**: +{reasoning} + +*策略生成时间: {time.strftime('%Y-%m-%d %H:%M:%S')}* +""" + return markdown + + except Exception as e: + print(f"格式化策略结果时出错: {e}") + traceback.print_exc() + return f"### {symbol} 格式化策略结果出错\n\n{str(e)}" + + def send_analysis_report(self, symbol: str, analysis_data: Dict[str, Any]) -> Dict[str, Any]: + """ + 发送完整分析报告(分析+预测+策略) + + Args: + symbol: 交易对符号 + analysis_data: 包含分析、预测和策略的数据 + + Returns: + 接口返回结果 + """ + try: + analysis_result = analysis_data.get('analysis', {}) + prediction_result = analysis_data.get('prediction', {}) + strategy_result = analysis_data.get('strategy', {}) + + # 获取市场趋势,用于设置标题图标 + market_trend = '' + if analysis_result and 'market_trend' in analysis_result: + market_trend = analysis_result['market_trend'] + + if '牛' in str(market_trend) or 'bull' in str(market_trend).lower(): + title_icon = "🟢" + elif '熊' in str(market_trend) or 'bear' in str(market_trend).lower(): + title_icon = "🔴" + else: + title_icon = "🟡" + + # 获取建议操作 + position = '未知' + if strategy_result and 'position' in strategy_result: + position = strategy_result['position'] + + # 构建标题 + title = f"{title_icon} {symbol} 加密货币分析报告 | 建议: {position}" + + # 构建完整报告内容 + markdown_text = f"# {symbol} 加密货币AI分析报告\n\n" + + # 添加分析结果 + markdown_text += "## 一、市场分析\n\n" + markdown_text += self.format_analysis_result(symbol, analysis_result) + markdown_text += "\n\n" + + # 添加预测结果 + markdown_text += "## 二、价格预测\n\n" + markdown_text += self.format_prediction_result(symbol, prediction_result) + markdown_text += "\n\n" + + # 添加策略结果 + markdown_text += "## 三、交易策略\n\n" + markdown_text += self.format_strategy_result(symbol, strategy_result) + + # 发送Markdown消息 + return self.send_markdown(title, markdown_text) + + except Exception as e: + print(f"发送分析报告时出错: {e}") + traceback.print_exc() + error_msg = f"### {symbol} 分析报告生成错误\n\n{str(e)}" + return self.send_markdown(f"⚠️ {symbol} 分析报告错误", error_msg) \ No newline at end of file