diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 7a2d06d..c287d2a 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -30,6 +30,11 @@ from app.crypto_agent.market_signal_analyzer import MarketSignalAnalyzer from app.crypto_agent.execution_guardian import ExecutionGuardian from app.crypto_agent.execution_targets import ExecutionTarget, build_default_execution_targets from app.utils.system_status import get_system_monitor, AgentStatus +from app.utils.signal_text import ( + humanize_entry_basis, + humanize_setup_basis, + humanize_setup_type, +) class CryptoAgent: @@ -2707,6 +2712,9 @@ class CryptoAgent: setup_type = best_signal.get('setup_type', 'unknown') setup_basis = best_signal.get('setup_basis', '') entry_basis = best_signal.get('entry_basis', '') + setup_type_text = humanize_setup_type(setup_type) + setup_basis_text = humanize_setup_basis(setup_basis) + entry_basis_text = humanize_entry_basis(entry_basis) # 等级(基于信心度映射)- 与 market_signal_analyzer.py 保持一致 # A级(80-100): 量价配合 + 多指标共振 + 多周期确认 @@ -2771,11 +2779,11 @@ class CryptoAgent: f"", ] if setup_type and setup_type != 'unknown': - content_parts.append(f"🧩 **Setup**: `{setup_type}`") + content_parts.append(f"🧩 **交易形态**: {setup_type_text}") if setup_basis: - content_parts.append(f"📌 **Setup依据**: {setup_basis}") + content_parts.append(f"📌 **形态依据**: {setup_basis_text}") if entry_basis: - content_parts.append(f"🎯 **入场依据**: {entry_basis}") + content_parts.append(f"🎯 **入场依据**: {entry_basis_text}") content_parts.append("") # 入场价格显示 @@ -2886,6 +2894,9 @@ class CryptoAgent: setup_type = best_signal.get('setup_type', 'unknown') if best_signal else 'unknown' setup_basis = best_signal.get('setup_basis', '') if best_signal else '' entry_basis = best_signal.get('entry_basis', '') if best_signal else '' + setup_type_text = humanize_setup_type(setup_type) + setup_basis_text = humanize_setup_basis(setup_basis) + entry_basis_text = humanize_entry_basis(entry_basis) # 对限价单:用实际订单状态决定显示 # resting=真的在挂单中, filled=已立即成交, None=市价单或未知 @@ -2971,9 +2982,9 @@ class CryptoAgent: ] if setup_type and setup_type != 'unknown': content_parts.extend([ - f"🧩 **Setup**: `{setup_type}`", - f"📌 **Setup依据**: {setup_basis}" if setup_basis else None, - f"🎯 **入场依据**: {entry_basis}" if entry_basis else None, + f"🧩 **交易形态**: {setup_type_text}", + f"📌 **形态依据**: {setup_basis_text}" if setup_basis else None, + f"🎯 **入场依据**: {entry_basis_text}" if entry_basis else None, f"", ]) content_parts.extend([ diff --git a/backend/app/crypto_agent/executor/base_executor.py b/backend/app/crypto_agent/executor/base_executor.py index ff2467a..1da166a 100644 --- a/backend/app/crypto_agent/executor/base_executor.py +++ b/backend/app/crypto_agent/executor/base_executor.py @@ -548,6 +548,27 @@ class BaseExecutor(ABC): return content_parts.append(f"**{label}**: {value}") + def _append_signal_context(self, content_parts: List[str], details: Optional[Dict[str, Any]] = None): + if not details: + return + + from app.utils.signal_text import ( + humanize_entry_basis, + humanize_setup_basis, + humanize_setup_type, + ) + + setup_type = details.get('setup_type') + setup_basis = details.get('setup_basis') + entry_basis = details.get('entry_basis') + + if setup_type and str(setup_type) != "unknown": + self._append_notification_detail(content_parts, "交易形态", humanize_setup_type(setup_type)) + if setup_basis: + self._append_notification_detail(content_parts, "形态依据", humanize_setup_basis(setup_basis)) + if entry_basis: + self._append_notification_detail(content_parts, "入场依据", humanize_entry_basis(entry_basis)) + def _build_notification_header(self, symbol: str, account_id: str, @@ -621,6 +642,7 @@ class BaseExecutor(ABC): # 添加详情 if details: + self._append_signal_context(content_parts, details) self._append_notification_detail(content_parts, "数量", details.get('size')) if details.get('price') is not None: self._append_notification_detail(content_parts, "价格", f"${details['price']:,.2f}") @@ -821,8 +843,9 @@ class BaseExecutor(ABC): self._append_notification_detail(content_parts, "信息", message) if details: + self._append_signal_context(content_parts, details) for key, value in details.items(): - if key in {'account_id', 'target_key'}: + if key in {'account_id', 'target_key', 'setup_type', 'setup_basis', 'entry_basis'}: continue self._append_notification_detail(content_parts, key, value) diff --git a/backend/app/utils/signal_text.py b/backend/app/utils/signal_text.py new file mode 100644 index 0000000..c6ea483 --- /dev/null +++ b/backend/app/utils/signal_text.py @@ -0,0 +1,127 @@ +""" +信号文本翻译工具 + +将内部策略枚举、setup/entry basis 等英文编码,转换为更适合通知展示的中文描述。 +""" +from __future__ import annotations + +from typing import Dict, Optional + + +SETUP_TYPE_LABELS: Dict[str, str] = { + "range_reversal": "区间反转", + "trend_reversal": "趋势反转", + "breakout_confirmation": "突破确认", + "breakout_pullback": "突破后回踩", + "trend_continuation_pullback": "趋势延续回踩", + "deep_pullback_continuation": "深度回踩延续", + "unknown": "未分类", +} + +LOCATION_LABELS: Dict[str, str] = { + "near_long_zone": "临近做多区", + "near_short_zone": "临近做空区", + "near_range_support": "临近区间支撑", + "near_range_resistance": "临近区间阻力", + "middle_of_range": "位于区间中部", + "far_from_trade_zone": "远离优先交易区", + "between_trade_zones": "位于交易区之间", + "range_edge": "区间边缘", + "unknown": "未知位置", +} + +FEATURE_VALUE_LABELS: Dict[str, str] = { + "bullish_acceptance": "多头承接有效", + "bearish_acceptance": "空头承接有效", + "bullish_rejection": "多头拒绝信号", + "bearish_rejection": "空头拒绝信号", + "bullish_continuation": "多头延续", + "bearish_continuation": "空头延续", + "pullback_on_light_volume": "缩量回调", + "counter_pressure_expanding": "逆向压力放大", + "acceptance_breakout_up": "向上突破并站稳", + "acceptance_breakout_down": "向下突破并站稳", + "weak_breakout_up": "向上弱突破", + "weak_breakout_down": "向下弱突破", + "healthy_pullback": "健康回调", + "heavy_sell_pullback": "放量下压回调", + "heavy_buy_pullback": "放量上冲回调", + "none": "无", + "neutral": "中性", + "unknown": "未知", + "early": "趋势早期", +} + +FIELD_LABELS: Dict[str, str] = { + "setup": "形态", + "location": "位置", + "volume_price_state": "量价状态", + "breakout_quality": "突破质量", + "pullback_quality": "回调质量", + "rejection_signal": "拒绝信号", +} + +ENTRY_BASIS_LABELS: Dict[str, str] = { + "breakout_acceptance_follow_through": "突破站稳后顺势跟进", + "pullback_into_trade_zone": "回踩交易区后挂单介入", + "pullback_confirmed": "回调确认后介入", + "rejection_or_structure_shift": "出现拒绝信号或结构切换后介入", + "generic_entry": "按通用入场条件执行", +} + + +def humanize_setup_type(value: Optional[str]) -> str: + code = str(value or "").strip() + if not code: + return "-" + return SETUP_TYPE_LABELS.get(code, code) + + +def _humanize_value(raw: str) -> str: + text = str(raw or "").strip() + if not text: + return "-" + if text in SETUP_TYPE_LABELS: + return SETUP_TYPE_LABELS[text] + if text in LOCATION_LABELS: + return LOCATION_LABELS[text] + if text in FEATURE_VALUE_LABELS: + return FEATURE_VALUE_LABELS[text] + return text + + +def humanize_setup_basis(value: Optional[str]) -> str: + text = str(value or "").strip() + if not text: + return "-" + + parts = [] + for chunk in text.split("|"): + piece = chunk.strip() + if not piece: + continue + if "=" not in piece: + parts.append(piece) + continue + key, raw_value = [item.strip() for item in piece.split("=", 1)] + key_label = FIELD_LABELS.get(key, key) + value_label = _humanize_value(raw_value) + parts.append(f"{key_label}={value_label}") + + return ";".join(parts) if parts else text + + +def humanize_entry_basis(value: Optional[str]) -> str: + text = str(value or "").strip() + if not text: + return "-" + + if text in ENTRY_BASIS_LABELS: + return ENTRY_BASIS_LABELS[text] + + reversal_prefix = "reversal_from_" + if text.startswith(reversal_prefix): + location = text[len(reversal_prefix):] + return f"在{LOCATION_LABELS.get(location, location)}出现反转信号后介入" + + return text diff --git a/backend/tests/test_signal_text.py b/backend/tests/test_signal_text.py new file mode 100644 index 0000000..9fba5a2 --- /dev/null +++ b/backend/tests/test_signal_text.py @@ -0,0 +1,18 @@ +from app.utils.signal_text import ( + humanize_entry_basis, + humanize_setup_basis, + humanize_setup_type, +) + + +def test_humanize_setup_type_returns_chinese_label(): + assert humanize_setup_type("range_reversal") == "区间反转" + + +def test_humanize_setup_basis_formats_composite_basis(): + text = "setup=range_reversal | location=near_long_zone | rejection_signal=bullish_rejection" + assert humanize_setup_basis(text) == "形态=区间反转;位置=临近做多区;拒绝信号=多头拒绝信号" + + +def test_humanize_entry_basis_formats_reversal_pattern(): + assert humanize_entry_basis("reversal_from_near_long_zone") == "在临近做多区出现反转信号后介入"