This commit is contained in:
aaron 2026-02-27 23:35:38 +08:00
parent 020b0f4eb3
commit b26f7713b4
3 changed files with 80 additions and 8 deletions

View File

@ -1488,6 +1488,7 @@ class CryptoAgent:
""" """
try: try:
symbol = decision.get('symbol') symbol = decision.get('symbol')
decision_action = decision.get('action', '') # buy/sell
orders_to_cancel = decision.get('orders_to_cancel', []) orders_to_cancel = decision.get('orders_to_cancel', [])
if not orders_to_cancel: if not orders_to_cancel:
@ -1501,10 +1502,11 @@ class CryptoAgent:
logger.warning(f" {trading_type}交易服务未启用") logger.warning(f" {trading_type}交易服务未启用")
return return
# 安全检查验证要取消的订单是否属于当前symbol # 安全检查验证要取消的订单是否属于当前symbol且方向相反
active_orders = trading_service.get_active_orders() active_orders = trading_service.get_active_orders()
valid_orders = [] valid_orders = []
invalid_orders = [] invalid_orders = []
wrong_direction_orders = []
for order_id in orders_to_cancel: for order_id in orders_to_cancel:
# 查找订单 # 查找订单
@ -1520,10 +1522,32 @@ class CryptoAgent:
invalid_orders.append(order_id) invalid_orders.append(order_id)
continue continue
# 检查订单方向是否与决策相反
order_side = order.get('side') # long/short
# 决策是buy时应该取消short做空决策是sell时应该取消long做多
should_cancel = False
if decision_action == 'buy' and order_side == 'short':
should_cancel = True
elif decision_action == 'sell' and order_side == 'long':
should_cancel = True
elif decision_action in ['open_long', 'close_short']:
should_cancel = (order_side == 'short')
elif decision_action in ['open_short', 'close_long']:
should_cancel = (order_side == 'long')
if not should_cancel:
logger.error(f" ❌ 安全拦截:订单 {order_id} 方向为 {order_side},与决策 {decision_action} 同向,不应取消!")
wrong_direction_orders.append(order_id)
invalid_orders.append(order_id)
continue
valid_orders.append(order_id) valid_orders.append(order_id)
if invalid_orders: if invalid_orders:
logger.error(f" 🚫 拒绝取消 {len(invalid_orders)} 个不属于 {symbol} 的订单") logger.error(f" 🚫 拒绝取消 {len(invalid_orders)} 个不符合条件的订单")
if wrong_direction_orders:
logger.error(f" ⚠️ {len(wrong_direction_orders)} 个同向订单被拦截(不应取消同向订单)")
if not valid_orders: if not valid_orders:
logger.warning(f" ⚠️ 没有有效的订单可以取消") logger.warning(f" ⚠️ 没有有效的订单可以取消")

View File

@ -484,10 +484,26 @@ class TradingDecisionMaker:
prompt_parts.append(f"\n## 当前挂单(仅 {context['symbol']} 的挂单)") prompt_parts.append(f"\n## 当前挂单(仅 {context['symbol']} 的挂单)")
if pending_orders: if pending_orders:
prompt_parts.append(f"⚠️ 重要:以下挂单都属于当前交易对 {context['symbol']}取消订单时只能选择这些订单ID") prompt_parts.append(f"⚠️ 重要:以下挂单都属于当前交易对 {context['symbol']}取消订单时只能选择这些订单ID")
prompt_parts.append(f"⚠️ 取消规则:做空信号时只能取消做多(🟢long)挂单,做多信号时只能取消做空(🔴short)挂单")
# 分类统计挂单方向
long_orders = [o for o in pending_orders if o.get('side') == 'long']
short_orders = [o for o in pending_orders if o.get('side') == 'short']
# 如果只有同向挂单明确提示LLM
signals = context.get('signals', [])
if signals:
main_action = signals[0].get('action', 'hold') if signals else 'hold'
if main_action == 'sell' and not long_orders:
prompt_parts.append(f"📌 注意:当前只有做空挂单,与做空信号同向,无需取消!")
elif main_action == 'buy' and not short_orders:
prompt_parts.append(f"📌 注意:当前只有做多挂单,与做多信号同向,无需取消!")
for order in pending_orders: for order in pending_orders:
side_icon = "🟢" if order.get('side') == 'long' else "🔴" side_icon = "🟢" if order.get('side') == 'long' else "🔴"
entry_type = "现价单" if order.get('entry_type') == 'market' else "挂单" entry_type = "现价单" if order.get('entry_type') == 'market' else "挂单"
prompt_parts.append(f"- {side_icon} {order.get('symbol')}: {order.get('side')} | {entry_type}") side_text = "做多" if order.get('side') == 'long' else "做空"
prompt_parts.append(f"- {side_icon} {order.get('symbol')}: {side_text}({order.get('side')}) | {entry_type}")
prompt_parts.append(f" 挂单价: ${order.get('entry_price')} | 数量: {order.get('quantity')} USDT") prompt_parts.append(f" 挂单价: ${order.get('entry_price')} | 数量: {order.get('quantity')} USDT")
prompt_parts.append(f" 订单ID: {order.get('order_id')}") prompt_parts.append(f" 订单ID: {order.get('order_id')}")
else: else:

View File

@ -10,17 +10,31 @@ from app.config import get_settings
class TelegramService: class TelegramService:
"""Telegram 机器人通知服务""" """Telegram 机器人通知服务"""
def __init__(self, bot_token: str = "", channel_id: str = ""): def __init__(self, bot_token: str = "", channel_id: str = "", service_type: str = "default"):
""" """
初始化 Telegram 服务 初始化 Telegram 服务
Args: Args:
bot_token: Telegram Bot Token ( @BotFather 获取) bot_token: Telegram Bot Token ( @BotFather 获取)
channel_id: 频道 ID ( @your_channel -1001234567890) channel_id: 频道 ID ( @your_channel -1001234567890)
service_type: 服务类型 (crypto/stock/news/default)
""" """
settings = get_settings() settings = get_settings()
self.bot_token = bot_token or getattr(settings, 'telegram_bot_token', '') self.bot_token = bot_token or getattr(settings, 'telegram_bot_token', '')
self.channel_id = channel_id or getattr(settings, 'telegram_channel_id', '') self.service_type = service_type
# 根据服务类型选择频道ID
if channel_id:
self.channel_id = channel_id
else:
# 根据service_type选择对应的频道
if service_type == "crypto":
self.channel_id = getattr(settings, 'telegram_crypto_channel_id', '') or getattr(settings, 'telegram_channel_id', '')
elif service_type == "stock":
self.channel_id = getattr(settings, 'telegram_stock_channel_id', '') or getattr(settings, 'telegram_channel_id', '')
else:
self.channel_id = getattr(settings, 'telegram_channel_id', '')
# 检查配置开关和必要参数是否都有效 # 检查配置开关和必要参数是否都有效
config_enabled = getattr(settings, 'telegram_enabled', True) config_enabled = getattr(settings, 'telegram_enabled', True)
self.enabled = config_enabled and bool(self.bot_token and self.channel_id) self.enabled = config_enabled and bool(self.bot_token and self.channel_id)
@ -30,10 +44,10 @@ class TelegramService:
logger.info("Telegram 通知已通过配置禁用") logger.info("Telegram 通知已通过配置禁用")
elif self.enabled: elif self.enabled:
self.api_base = f"https://api.telegram.org/bot{self.bot_token}" self.api_base = f"https://api.telegram.org/bot{self.bot_token}"
logger.info(f"Telegram 通知服务初始化完成,频道: {self.channel_id}") logger.info(f"Telegram 通知服务初始化完成 [{service_type}],频道: {self.channel_id}")
else: else:
self.api_base = "" self.api_base = ""
logger.warning("Telegram Bot Token 或 Channel ID 未配置,通知功能已禁用") logger.warning(f"Telegram Bot Token 或 Channel ID 未配置 [{service_type}],通知功能已禁用")
async def send_message(self, text: str, parse_mode: str = "HTML") -> bool: async def send_message(self, text: str, parse_mode: str = "HTML") -> bool:
""" """
@ -245,11 +259,29 @@ class TelegramService:
# 全局实例(延迟初始化) # 全局实例(延迟初始化)
_telegram_service: Optional[TelegramService] = None _telegram_service: Optional[TelegramService] = None
_telegram_crypto_service: Optional[TelegramService] = None
_telegram_stock_service: Optional[TelegramService] = None
def get_telegram_service() -> TelegramService: def get_telegram_service() -> TelegramService:
"""获取 Telegram 服务实例""" """获取 Telegram 服务实例(默认)"""
global _telegram_service global _telegram_service
if _telegram_service is None: if _telegram_service is None:
_telegram_service = TelegramService() _telegram_service = TelegramService()
return _telegram_service return _telegram_service
def get_telegram_crypto_service() -> TelegramService:
"""获取加密货币 Telegram 服务实例"""
global _telegram_crypto_service
if _telegram_crypto_service is None:
_telegram_crypto_service = TelegramService(service_type="crypto")
return _telegram_crypto_service
def get_telegram_stock_service() -> TelegramService:
"""获取股票 Telegram 服务实例"""
global _telegram_stock_service
if _telegram_stock_service is None:
_telegram_stock_service = TelegramService(service_type="stock")
return _telegram_stock_service