From 0a637053fc6b5a95c7801f3f759f66d15507c6be Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sat, 28 Feb 2026 22:59:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/crypto_agent/crypto_agent.py | 2 +- .../crypto_agent/market_signal_analyzer.py | 498 ++++++++++-------- .../crypto_agent/trading_decision_maker.py | 134 ++++- backend/app/services/binance_service.py | 30 +- backend/app/services/bitget_service.py | 38 +- 5 files changed, 442 insertions(+), 260 deletions(-) diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index d9da569..42efb9c 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -1679,7 +1679,7 @@ class CryptoAgent: def _validate_data(self, data: Dict[str, pd.DataFrame]) -> bool: """验证数据完整性""" - required_intervals = ['5m', '15m', '1h', '4h'] + required_intervals = ['1m', '5m', '15m', '30m', '1h'] for interval in required_intervals: if interval not in data or data[interval].empty: return False diff --git a/backend/app/crypto_agent/market_signal_analyzer.py b/backend/app/crypto_agent/market_signal_analyzer.py index e47fad9..152e88d 100644 --- a/backend/app/crypto_agent/market_signal_analyzer.py +++ b/backend/app/crypto_agent/market_signal_analyzer.py @@ -47,147 +47,156 @@ class MarketSignalAnalyzer: - **顺势而为**:在大趋势方向上寻找入场点 - **严控风险**:每次交易风险不超过本金的2% -## 零、趋势方向判断(第一步,最重要!) -**在分析任何信号之前,先判断当前趋势方向和强度。** +## 零、日内交易核心理念(必须遵守!) -### 趋势判断标准(使用 EMA 和 均线系统) -**上升趋势(多头市场)**: -- EMA20 > EMA50 > EMA200(短中长期均线多头排列) -- 价格站稳在 EMA20 之上 -- MA5 > MA10 > MA20 > MA50 -- 最近高点逐步抬高,低点也逐步抬高 +### 🎯 日内交易的本质 +**日内交易 = 当日进出 + 快速获利 + 严控盈亏比** -**下降趋势(空头市场)**: -- EMA20 < EMA50 < EMA200(短中长期均线空头排列) -- 价格持续在 EMA20 之下 -- MA5 < MA10 < MA20 < MA50 -- 最近高点逐步降低,低点也逐步降低 +### ⚠️ 铁律(违反即失败) +1. **盈亏比第一**:所有交易必须满足盈亏比 ≥ 1:2,优选 1:3 + - 盈亏比 = (目标盈利 - 入场价) / (入场价 - 止损价) + - 做多:目标价 > 入场价 > 止损价 + - 做空:目标价 < 入场价 < 止损价 + - 如果盈亏比 < 1:2,**绝对不要开仓** -**震荡市(无明确趋势)**: -- 均线纠缠,无明显排列 -- 价格在 EMA20 上下波动 -- 高点低点无规律 -- 此时可双向交易,但降低仓位 +2. **快进快出**: + - 单笔持仓不超过 4 小时 + - 达到目标立即平仓,不贪心 + - 未达到目标但超过 2 小时,考虑平仓观望 -### 趋势强度判断 -- **强趋势**:均线完美排列 + 价格远离均线 + 成交量配合 -- **中等趋势**:均线有排列 + 价格偶尔回踩均线 -- **弱趋势/震荡**:均线纠缠 + 价格在均线上下反复 +3. **严格止损**: + - 止损幅度:1-2%(最大不超过 2%) + - 触及止损立即离场,不要犹豫 + - 不要移动止损(除非是移动止盈保护利润) -### 顺势交易规则(必须执行) -| 当前趋势 | 允许操作 | 条件 | -|---------|---------|------| -| **强上升趋势** | ✅ 只做多 | 回调到支撑位、RSI超卖区、金叉 | -| **强上升趋势** | ❌ 严禁做空 | 除非出现明确的顶背离+放量反转信号 | -| **强下降趋势** | ✅ 只做空 | 反弹到阻力位、RSI超买区、死叉 | -| **强下降趋势** | ❌ 严禁做多 | 除非出现明确的底背离+放量反转信号 | -| **震荡市** | ✅ 双向交易 | 但降低仓位(轻仓),提高止损要求 | -| **趋势不明确** | ⚠️ 观望为主 | 等待趋势明确后再入场 | +4. **日内平仓**: + - 不建议持仓过夜 + - 收盘前 30 分钟逐步平仓 + - 避免隔夜风险 -### 逆势交易的条件(极其严格) -**只有在满足以下全部条件时,才允许考虑逆势交易:** -1. **多重反转信号**: - - 明确的背离(顶背离或底背离) - - 关键形态反转(头肩顶/底、双顶/底、吞没形态) - - 放量突破关键位 -2. **多周期确认**:4h、1h、15m 三个周期同时出现反转信号 -3. **风险收益比合理**:潜在盈利至少是风险的3倍以上 -4. **降低仓位**:逆势交易必须轻仓(不超过顺势仓位的50%) +### 日内交易时间框架(调整后) +**主周期**:30m(日内趋势) +**入场周期**:15m(寻找入场点) +**精确入场**:5m(确认时机) +**超精确入场**:1m(最后确认,可选) +**趋势参考**:1h(当日大方向) -**如果不符合上述条件,即使有买入/卖出信号,也必须选择 hold(观望)。** +### 日内交易参数 +| 参数 | 设定值 | +|------|--------| +| 止损幅度 | 1-2%(最大2%) | +| 目标盈利 | 2-3%(日内快速获利) | +| 盈亏比要求 | ≥ 1:2(优选1:3) | +| 单笔持仓时长 | 不超过4小时 | +| 仓位大小 | 轻仓为主(light/micro) | -## 零点五、趋势位置判断和左侧交易(短线交易核心) +## 零点一、趋势方向判断(简化版,适配日内) +**日内交易更关注 30m 和 15m,1h 作为大方向参考** -### 🚨 避免盲目追涨杀跌 -**顺势交易不等于追涨杀跌!趋势到了晚期,要警惕反转。** +### 快速趋势判断(30m + 15m) +**看涨日内(做多为主)**: +- 30m: EMA20 > EMA50,价格在 EMA20 之上 +- 15m: 短期均线向上,价格站上 EMA5 +- 30m 和 15m 同向向上 -### 趋势三阶段判断 -**上升趋势的三个阶段**: -1. **早期**(启动阶段): - - 均线刚开始多头排列 - - 价格刚刚站上所有均线 - - 成交量温和放大 - - ✅ 可以积极做多 +**看跌日内(做空为主)**: +- 30m: EMA20 < EMA50,价格在 EMA20 之下 +- 15m: 短期均线向下,价格跌破 EMA5 +- 30m 和 15m 同向向下 -2. **中期**(加速阶段): - - 均线完美排列,价格远离均线 - - 成交量持续放大 - - RSI 在 50-70 之间 - - ✅ 可以顺势做多,但警惕超买 +**震荡日内(观望为主)**: +- 30m: 均线纠缠,价格反复穿越 EMA20 +- 15m: 无明确方向 +- 此时最好观望,或支撑位多、压力位空(轻仓) -3. **晚期**(过度延伸): - - 价格严重偏离均线(> 5%) - - RSI > 75(超买区) - - 布林带开口极大,价格在上轨之外 - - 出现顶背离信号 - - ❌ 不要追多,警惕反转 +### 日内顺势规则 +| 30m 趋势 | 15m 趋势 | 允许操作 | 盈亏比要求 | +|---------|----------|---------|-----------| +| **上升** | 上升 | ✅ 做多 | ≥ 1:2 | +| **上升** | 下跌 | ⚠️ 回调做多 | ≥ 1:3 | +| **下降** | 下降 | ✅ 做空 | ≥ 1:2 | +| **下降** | 上升 | ⚠️ 反弹做空 | ≥ 1:3 | +| **震荡** | 任意 | ⚠️ 观望或轻仓 | ≥ 1:3 | -**下降趋势的三个阶段**: -1. **早期**:均线刚开始空头排列,刚刚破位 → ✅ 可以积极做空 -2. **中期**:均线完美空头排列,加速下跌 → ✅ 可以顺势做空,警惕超卖 -3. **晚期**:价格严重偏离均线,RSI < 25 → ❌ 不要追空,警惕反弹 +## 零点五、日内交易实战策略 -### 过度延伸的信号(必须警惕) +### 🎯 三种日内入场方式 -**上涨过度延伸的信号**: -- RSI > 75 且出现顶背离 -- 价格偏离 MA5 > 5% -- 布林带上轨之外,且开口极大 -- 连续 3 根以上大阳线 -- 极端放量后价格滞涨 -- ⚠️ 这时不要追多,考虑减仓或做空 +#### 策略1:突破追入(适合强势行情) +**什么时候追?** +- 30m 和 15m 同向,趋势明确 +- 放量突破关键位(阻力/支撑) +- 15m 或 5m 级别正在加速 -**下跌过度延伸的信号**: -- RSI < 25 且出现底背离 -- 价格偏离 MA5 > 5% -- 布林带下轨之外,且开口极大 -- 连续 3 根以上大阴线 -- 极端放量后价格企稳 -- ⚠️ 这时不要追空,考虑平仓或做多 +**追入必须满足**: +- ✅ 盈亏比 ≥ 1:2 +- ✅ 止损:1-2% +- ✅ 目标:2-3% +- ✅ 仓位:light 或 micro -### 左侧交易规则(特定条件下可尝试) +**❌ 追入的危险区**: +- 15m RSI > 70(多)或 < 30(空) +- 价格偏离均线 > 3% +- 连续 3 根以上大阳/大阴 -**什么时候可以尝试左侧交易?** +#### 策略2:回调/反弹入场(稳健策略) +**回调做多**(30m 上升,15m 回调): +- 回调到 EMA20 或支撑位 +- RSI 回落到 40-50 +- 缩量后放量反弹 -1. **上升趋势晚期,出现明确反转信号**: - - RSI 顶背离 + 价格触及布林带上轨外 - - 放量滞涨 + 出现大阴线 - - 关键阻力位出现明显反转形态(十字星、吞没) - - ✅ 可以尝试做空,小仓位(light 或 micro) +**反弹做空**(30m 下降,15m 反弹): +- 反弹到 EMA20 或压力位 +- RSI 反弹到 50-60 +- 缩量后放量下跌 -2. **下降趋势晚期,出现明确反转信号**: - - RSI 底背离 + 价格触及布林带下轨外 - - 地量后企稳 + 出现大阳线 - - 关键支撑位出现明显反转形态(锤子线、早晨之星) - - ✅ 可以尝试做多,小仓位(light 或 micro) +**回调入场要求**: +- ✅ 盈亏比 ≥ 1:3(更严格要求) +- ✅ 止损:支撑/压力位外侧 1% +- ✅ 目标:2-3% -3. **小级别反转信号**: - - 5m/15m 周期出现明显的背离信号 - - 4h/1h 大周期仍在趋势中,但小级别开始反转 - - ✅ 可以尝试小仓位反向,但严格止损 +#### 策略3:震荡双向交易(仅限震荡市) +- 支撑位做多,压力位做空 +- 严格止损 1% +- 目标 1.5-2% +- 盈亏比 ≥ 1:2 -**左侧交易必须满足的条件**: -- 至少 2 个反转信号同时出现 -- 有明确的关键点位支撑/阻力 -- 严格止损(不超过保证金的 1%) -- 小仓位(light 或 micro) +### 🚨 盈亏比检查清单(必须执行!) -### 实战示例 +**在输出任何交易信号前,必须计算盈亏比**: -**❌ 错误:盲目追涨** ``` -场景:BTC 多头排列,连续上涨 5% -MA 完美多头排列,RSI = 68 -错误分析:趋势仍在延续,追多 -正确分析:价格已偏离 MA5 > 3%,接近过度延伸,观望或小仓位做空 +做多盈亏比 = (目标价 - 入场价) / (入场价 - 止损价) +做空盈亏比 = (入场价 - 目标价) / (止损价 - 入场价) + +示例: +- BTC 入场 65000,止损 64300(-1%),目标 66300(+2%) +- 盈亏比 = (66300 - 65000) / (65000 - 64300) = 1300 / 700 ≈ 1.86 ✅ 可行 ``` -**✅ 正确:等待反转信号** +**如果盈亏比 < 1:2,不要输出信号!** + +### 日内交易决策流程 + ``` -场景:BTC 多头排列,但出现顶背离 -4h 多头排列,但 RSI 出现顶背离 -15m 价格创新高但 RSI 未创新高 -正确分析:趋势可能反转,小仓位尝试做空 +第一步:检查盈亏比 +├── 盈亏比 < 1:2 → ❌ 不开仓 +└── 盈亏比 ≥ 1:2 → 继续检查 + +第二步:判断趋势方向 +├── 30m 上升 + 15m 上升 → 做多(策略1或2) +├── 30m 下降 + 15m 下降 → 做空(策略1或2) +├── 30m 震荡 → 观望或双向轻仓(策略3) +└── 趋势不明确 → 观望 + +第三步:选择入场方式 +├── 放量突破 → market 立即入场 +└── 等待回调 → limit 挂单入场 + +第四步:设置止损止盈 +├── 止损:1-2%(最大不超过 2%) +├── 目标:2-3%(快速获利) +└── 验证盈亏比 ≥ 1:2 ``` ## 一、量价分析(重要) @@ -247,19 +256,19 @@ MA 完美多头排列,RSI = 68 - 布林带收口:即将变盘 - 布林带开口:趋势启动 -### 均线系统(趋势判断核心) -- **多头排列**(MA5 > MA10 > MA20 > MA50):强势上涨趋势,回调做多 -- **空头排列**(MA5 < MA10 < MA20 < MA50):强势下跌趋势,反弹做空 -- **价格与 MA 的关系**: - - 价格站稳 MA5/MA10 上方:短线上涨 - - 价格突破 MA20:中线转多 - - 价格跌破 MA20:中线转空 - - MA50 是中期趋势的分水岭 +### 均线系统(趋势判断核心 - 使用 EMA) +- **多头排列**(EMA5 > EMA10 > EMA20 > EMA50):强势上涨趋势,回调做多 +- **空头排列**(EMA5 < EMA10 < EMA20 < EMA50):强势下跌趋势,反弹做空 +- **价格与 EMA 的关系**: + - 价格站稳 EMA5/EMA10 上方:短线上涨 + - 价格突破 EMA20:中线转多 + - 价格跌破 EMA20:中线转空 + - EMA50 是中期趋势的分水岭 - **均线金叉死叉**: - - MA5 上穿 MA10:短线买入信号 - - MA5 下穿 MA10:短线卖出信号 - - MA10 上穿 MA20:中线买入信号 - - MA10 下穿 MA20:中线卖出信号 + - EMA5 上穿 EMA10:短线买入信号 + - EMA5 下穿 EMA10:短线卖出信号 + - EMA10 上穿 EMA20:中线买入信号 + - EMA10 下穿 EMA20:中线卖出信号 ## 四、新闻舆情分析 结合最新市场新闻判断: @@ -272,28 +281,29 @@ MA 完美多头排列,RSI = 68 **多周期共振是提高信号质量的核心方法:** ### 周期层级关系 -- **4h(趋势层)**:决定中期大方向 -- **1h(主周期)**:主要交易周期 +- **1h(当日大方向)**:判断当日的主要趋势方向 +- **30m(日内趋势层)**:决定日内主趋势 - **15m(入场层)**:寻找入场时机 - **5m(精确入场)**:确认最佳入场点 +- **1m(超精确)**:最后确认(可选) ### 共振判断标准 **强共振(A级信号)**: -- 所有周期趋势同向(如 4h多 + 1h多 + 15m多) +- 30m + 15m + 5m 趋势同向(如 30m多 + 15m多 + 5m多) - 多周期 RSI 同时超买/超卖后出现背离 -- 多周期 MA 同时金叉/死叉 +- 多周期 EMA 同时金叉/死叉 **中等共振(B级信号)**: -- 大周期(4h+1h)同向 -- 主周期(1h)技术指标明确 +- 30m + 15m 同向 +- 主周期(15m)技术指标明确 **弱共振(C级信号)**: - 只有单一周期信号 - 多周期方向不一致 ### 实战策略 -- **顺势交易**:4h 和 1h 同向时,在 15m/5m 寻找入场点 -- **逆势谨慎**:只有 1h 信号但 4h 反向时,降低置信度 +- **顺势交易**:30m 和 15m 同向时,在 5m/1m 寻找入场点 +- **逆势谨慎**:只有 15m 信号但 30m 反向时,降低置信度 - **突破交易**:多周期同时突破关键位,信号最强 ## 六、入场方式 @@ -380,7 +390,7 @@ MA 完美多头排列,RSI = 68 ### ✅ 允许输出新信号的情况 只有在以下情况之一时,才输出新的交易信号: 1. **趋势反转**:上一轮判断的趋势发生了明确反转 - - 例如:上一轮看多(MA多头排列),现在转为空头排列 + - 例如:上一轮看多(EMA多头排列),现在转为空头排列 2. **从观望到机会**:上一轮是观望(无信号),现在出现了明确的交易机会 3. **上一轮信号已失效**: - 价格已触及上一轮的止损或止盈价位 @@ -501,15 +511,15 @@ MA 完美多头排列,RSI = 68 bb_lower = df['bb_lower'].iloc[-1] context_parts.append(f"布林带: 上轨 {bb_upper:.2f}, 下轨 {bb_lower:.2f}") - # 均线系统 - context_parts.append(f"\n## 均线系统") - df_1h = data.get('1h') - if df_1h is not None and len(df_1h) > 0: - latest = df_1h.iloc[-1] - context_parts.append(f"MA5: {latest.get('ma5', 'N/A')}") - context_parts.append(f"MA10: {latest.get('ma10', 'N/A')}") - context_parts.append(f"MA20: {latest.get('ma20', 'N/A')}") - context_parts.append(f"MA50: {latest.get('ma50', 'N/A')}") + # 均线系统(使用 30m 作为日内主周期) + context_parts.append(f"\n## 均线系统 (30m 日内主趋势)") + df_30m = data.get('30m') + if df_30m is not None and len(df_30m) > 0: + latest = df_30m.iloc[-1] + context_parts.append(f"EMA5: {latest.get('ma5', 'N/A')}") + context_parts.append(f"EMA10: {latest.get('ma10', 'N/A')}") + context_parts.append(f"EMA20: {latest.get('ma20', 'N/A')}") + context_parts.append(f"EMA50: {latest.get('ma50', 'N/A')}") # 判断均线排列 ma5 = latest.get('ma5', 0) @@ -519,9 +529,9 @@ MA 完美多头排列,RSI = 68 if all([ma5, ma10, ma20, ma50]): if ma5 > ma10 > ma20 > ma50: - context_parts.append("均线排列: 多头排列 📈") + context_parts.append("均线排列: 多头排列 📈 (EMA5 > EMA10 > EMA20 > EMA50)") elif ma5 < ma10 < ma20 < ma50: - context_parts.append("均线排列: 空头排列 📉") + context_parts.append("均线排列: 空头排列 📉 (EMA5 < EMA10 < EMA20 < EMA50)") else: context_parts.append("均线排列: 交织,方向不明") @@ -583,96 +593,118 @@ MA 完美多头排列,RSI = 68 return "新闻获取失败" def _analyze_trend_position(self, data: Dict[str, pd.DataFrame]) -> str: - """分析趋势所处的位置(早期、中期、晚期)""" + """分析趋势位置和日内交易机会(使用 EMA)""" try: - df_1h = data.get('1h') - if df_1h is None or len(df_1h) < 50: + df_30m = data.get('30m') + df_15m = data.get('15m') + + if df_30m is None or len(df_30m) < 50: return "" - latest = df_1h.iloc[-1] - current_price = float(latest['close']) + latest_30m = df_30m.iloc[-1] + current_price = float(latest_30m['close']) - # 获取均线 - ma5 = latest.get('ma5') - ma10 = latest.get('ma10') - ma20 = latest.get('ma20') + # 获取日内级别 EMA(30m) + ema5_30m = latest_30m.get('ma5') # 实际是 ema5 + ema10_30m = latest_30m.get('ma10') # 实际是 ema10 + ema20_30m = latest_30m.get('ma20') # 实际是 ema20 - if not all([ma5, ma10, ma20]): + if not all([ema5_30m, ema10_30m, ema20_30m]): return "" - # 计算价格偏离度 - deviation_ma5 = abs(current_price - ma5) / ma5 * 100 - deviation_ma20 = abs(current_price - ma20) / ma20 * 100 - - # 获取 RSI - rsi = latest.get('rsi', 50) - - # 获取布林带 - bb_upper = latest.get('bb_upper') - bb_lower = latest.get('bb_lower') - - analysis = [] - - # 判断趋势方向 - if ma5 > ma10 > ma20: - trend = "上涨" - # 判断是否过度延伸 - overextended_signals = [] - - if deviation_ma5 > 5: - overextended_signals.append(f"价格偏离 MA5 > 5% ({deviation_ma5:.1f}%)") - - if rsi > 75: - overextended_signals.append(f"RSI 超买 ({rsi:.0f})") - - if bb_upper and current_price > bb_upper: - overextended_signals.append("价格突破布林带上轨") - - if overextended_signals: - analysis.append(f"⚠️ 警告:{trend}趋势可能过度延伸") - for signal in overextended_signals: - analysis.append(f" - {signal}") - analysis.append(f" → 不要追{trend},警惕反转") - - elif deviation_ma5 > 3 or rsi > 65: - analysis.append(f"📍 位置:{trend}趋势中期") - analysis.append(f" → 价格偏离 MA5 {deviation_ma5:.1f}%,RSI {rsi:.0f}") - analysis.append(f" → 可以顺势{trend},但警惕超买") - - else: - analysis.append(f"✅ 位置:{trend}趋势早期") - analysis.append(f" → 可以积极{trend}") - - elif ma5 < ma10 < ma20: - trend = "下跌" - overextended_signals = [] - - if deviation_ma5 > 5: - overextended_signals.append(f"价格偏离 MA5 > 5% ({deviation_ma5:.1f}%)") - - if rsi < 25: - overextended_signals.append(f"RSI 超卖 ({rsi:.0f})") - - if bb_lower and current_price < bb_lower: - overextended_signals.append("价格跌破布林带下轨") - - if overextended_signals: - analysis.append(f"⚠️ 警告:{trend}趋势可能过度延伸") - for signal in overextended_signals: - analysis.append(f" - {signal}") - analysis.append(f" → 不要追{trend},警惕反弹") - - elif deviation_ma5 > 3 or rsi < 35: - analysis.append(f"📍 位置:{trend}趋势中期") - analysis.append(f" → 价格偏离 MA5 {deviation_ma5:.1f}%,RSI {rsi:.0f}") - analysis.append(f" → 可以顺势{trend},但警惕超卖") - - else: - analysis.append(f"✅ 位置:{trend}趋势早期") - analysis.append(f" → 可以积极{trend}") - + # 判断日内趋势(30m EMA 为主) + if ema5_30m > ema10_30m > ema20_30m: + intraday_trend = "上升" + intraday_emoji = "📈" + elif ema5_30m < ema10_30m < ema20_30m: + intraday_trend = "下跌" + intraday_emoji = "📉" else: - analysis.append("➖ 位置:震荡市") + intraday_trend = "震荡" + intraday_emoji = "➖" + + analysis = [f"日内趋势(30m EMA): {intraday_emoji} {intraday_trend}"] + + # 检查15分钟级别入场时机 + if df_15m is not None and len(df_15m) >= 20: + latest_15m = df_15m.iloc[-1] + rsi_15m = latest_15m.get('rsi', 50) + ema5_15m = latest_15m.get('ma5') # 实际是 ema5 + ema20_15m = latest_15m.get('ma20') # 实际是 ema20 + + # 检查短期动能 + if len(df_15m) >= 5: + recent_closes = df_15m['close'].iloc[-5:].values + is_accelerating = all(recent_closes[i] > recent_closes[i-1] for i in range(1, 5)) + else: + is_accelerating = False + + # 计算价格偏离 + if ema5_15m and ema20_15m: + deviation_ema5_15m = abs(current_price - ema5_15m) / ema5_15m * 100 + distance_to_ema20 = abs(current_price - ema20_15m) / ema20_15m * 100 + else: + deviation_ema5_15m = 0 + distance_to_ema20 = 0 + + # 检查成交量 + df_5m = data.get('5m') + volume_ratio = 1 + if df_5m is not None and len(df_5m) >= 20: + vol_latest = df_5m['volume'].iloc[-1] + vol_ma20 = df_5m['volume'].iloc[-20:-1].mean() + volume_ratio = vol_latest / vol_ma20 if vol_ma20 > 0 else 1 + + # 日内过度延伸检查(EMA 反应更快,阈值更严格) + is_overextended = ( + (rsi_15m > 70 and intraday_trend == "上升") or + (rsi_15m < 30 and intraday_trend == "下跌") or + deviation_ema5_15m > 3 + ) + + if intraday_trend == "上升": + if is_accelerating and volume_ratio > 1.3 and not is_overextended: + analysis.append(f"15m: 正在加速上涨,放量突破") + analysis.append(f" → 日内追多(止损1-2%,目标2-3%)") + analysis.append(f" → 盈亏比要求 >= 1:2") + elif distance_to_ema20 < 1 and deviation_ema5_15m > 1.5: + analysis.append(f"15m: 回调到 EMA20 支撑位") + analysis.append(f" → 支撑位做多反弹(EMA20: ${ema20_15m:.0f})") + analysis.append(f" → 止损1%,目标2-3%,盈亏比 >= 1:3") + elif is_overextended: + analysis.append(f"⚠️ 15m 过度延伸: RSI {rsi_15m:.0f},偏离 EMA5 {deviation_ema5_15m:.1f}%") + analysis.append(f" → 不要追多,等待回调") + else: + analysis.append(f"15m: 上涨中,可以轻仓做多") + analysis.append(f" → RSI {rsi_15m:.0f},偏离 EMA5 {deviation_ema5_15m:.1f}%") + + elif intraday_trend == "下跌": + if is_accelerating and volume_ratio > 1.3 and not is_overextended: + analysis.append(f"15m: 正在加速下跌,放量跌破") + analysis.append(f" → 日内追空(止损1-2%,目标2-3%)") + analysis.append(f" → 盈亏比要求 >= 1:2") + elif distance_to_ema20 < 1 and deviation_ema5_15m > 1.5: + analysis.append(f"15m: 反弹到 EMA20 压力位") + analysis.append(f" → 压力位做空回调(EMA20: ${ema20_15m:.0f})") + analysis.append(f" → 止损1%,目标2-3%,盈亏比 >= 1:3") + elif is_overextended: + analysis.append(f"⚠️ 15m 过度延伸: RSI {rsi_15m:.0f},偏离 EMA5 {deviation_ema5_15m:.1f}%") + analysis.append(f" → 不要追空,等待反弹") + else: + analysis.append(f"15m: 下跌中,可以轻仓做空") + analysis.append(f" → RSI {rsi_15m:.0f},偏离 EMA5 {deviation_ema5_15m:.1f}%") + + else: + analysis.append(f"15m: 震荡,观望或双向轻仓") + analysis.append(f" → 支撑位多,压力位空,盈亏比 >= 1:3") + + # 日内交易要点 + analysis.append(f"\n💡 日内交易要点:") + analysis.append(f"- 使用 EMA(指数移动平均)反应更快") + analysis.append(f"- 盈亏比第一: 必须 >= 1:2,优选 1:3") + analysis.append(f"- 快进快出: 持仓不超过4小时") + analysis.append(f"- 严格止损: 1-2%(最大2%)") + analysis.append(f"- 目标盈利: 2-3%(快速获利)") return "\n".join(analysis) if analysis else "" @@ -1008,15 +1040,15 @@ MA 完美多头排列,RSI = 68 } def _analyze_volatility(self, data: Dict[str, pd.DataFrame]) -> str: - """分析波动率变化""" - df = data.get('1h') + """分析波动率变化(使用 30m 作为日内主周期)""" + df = data.get('30m') if df is None or len(df) < 24 or 'atr' not in df.columns: return "" lines = [] # ATR 变化趋势 - recent_atr = df['atr'].iloc[-6:].mean() # 最近 6 根 + recent_atr = df['atr'].iloc[-6:].mean() # 最近 6 根(3小时) older_atr = df['atr'].iloc[-12:-6].mean() # 之前 6 根 if pd.isna(recent_atr) or pd.isna(older_atr) or older_atr == 0: @@ -1028,12 +1060,12 @@ MA 完美多头排列,RSI = 68 current_price = float(df['close'].iloc[-1]) atr_percent = current_atr / current_price * 100 - lines.append(f"当前 ATR: ${current_atr:.2f} ({atr_percent:.2f}%)") + lines.append(f"当前 ATR (30m): ${current_atr:.2f} ({atr_percent:.2f}%)") if atr_change > 20: - lines.append(f"**波动率扩张**: ATR 上升 {atr_change:.0f}%,趋势可能启动") + lines.append(f"**波动率扩张**: ATR 上升 {atr_change:.0f}%,日内趋势可能启动") elif atr_change < -20: - lines.append(f"**波动率收缩**: ATR 下降 {abs(atr_change):.0f}%,可能即将突破") + lines.append(f"**波动率收缩**: ATR 下降 {abs(atr_change):.0f}%,可能即将变盘") else: lines.append(f"波动率稳定: ATR 变化 {atr_change:+.0f}%") diff --git a/backend/app/crypto_agent/trading_decision_maker.py b/backend/app/crypto_agent/trading_decision_maker.py index 93348ae..24c413a 100644 --- a/backend/app/crypto_agent/trading_decision_maker.py +++ b/backend/app/crypto_agent/trading_decision_maker.py @@ -21,7 +21,32 @@ class TradingDecisionMaker: TRADING_DECISION_PROMPT = """你是一位专业的加密货币交易员。你的核心职责是**仓位管理和风险控制**,而不是盲目开仓。 ## 🎯 核心理念 -**仓位管理优先于开新仓。你的首要任务是管理好现有仓位,而不是不断增加新仓位。** +**日内交易:快进快出 + 盈亏比第一 + 严控风险** + +### 🚨 盈亏比铁律(违反即拒绝) +**所有交易必须满足盈亏比 ≥ 1:2,优选 1:3** + +``` +盈亏比 = (目标盈利 - 入场价) / (入场价 - 止损价) + +做多:盈亏比 = (止盈价 - 入场价) / (入场价 - 止损价) +做空:盈亏比 = (入场价 - 止盈价) / (止损价 - 入场价) + +示例: +- BTC 做多:入场 65000,止损 64300(-1%),止盈 66300(+2%) +- 盈亏比 = (66300 - 65000) / (65000 - 64300) = 1300 / 700 ≈ 1.86 ✅ + +如果盈亏比 < 1:2,绝对不要开仓! +``` + +### 日内交易参数 +| 参数 | 设定值 | +|------|--------| +| 止损幅度 | 1-2%(最大2%) | +| 目标盈利 | 2-3%(日内快速获利) | +| 盈亏比要求 | ≥ 1:2(优选1:3) | +| 单笔持仓时长 | 不超过4小时 | +| 仓位大小 | 轻仓为主(light/micro) | ## 决策流程(必须按顺序执行) @@ -704,6 +729,57 @@ class TradingDecisionMaker: except (ValueError, TypeError): pass + # 盈亏比检查规则(新增) + prompt_parts.append(f"\n## 盈亏比检查(日内交易铁律)") + prompt_parts.append(f"⚠️ 所有交易必须满足盈亏比 >= 1:2,优选 1:3") + prompt_parts.append(f"") + prompt_parts.append(f"盈亏比计算公式:") + prompt_parts.append(f" 做多盈亏比 = (止盈价 - 入场价) / (入场价 - 止损价)") + prompt_parts.append(f" 做空盈亏比 = (入场价 - 止盈价) / (止损价 - 入场价)") + prompt_parts.append(f"") + prompt_parts.append(f"⚠️ 如果盈亏比 < 1:2,**不要开仓(返回 HOLD)**") + + # 计算并显示盈亏比 + signals = context.get('signals', []) + if signals: + for sig in signals: + action = sig.get('action') + entry = sig.get('entry_zone') + stop_loss = sig.get('stop_loss') + take_profit = sig.get('take_profit') + + if action and entry and stop_loss and take_profit: + try: + entry = float(entry) + stop_loss = float(stop_loss) + take_profit = float(take_profit) + + if action == 'buy': + risk_ratio = entry - stop_loss + reward_ratio = take_profit - entry + elif action == 'sell': + risk_ratio = stop_loss - entry + reward_ratio = entry - take_profit + else: + continue + + if risk_ratio > 0 and reward_ratio > 0: + rr_ratio = reward_ratio / risk_ratio + rr_percent = (risk_ratio / entry) * 100 + + if rr_ratio >= 2.0: + status = f"✅ 通过 (1:{rr_ratio:.1f})" + else: + status = f"❌ 拒绝 (1:{rr_ratio:.1f} < 1:2)" + + prompt_parts.append(f"\n信号 {action} @ ${entry:,.2f}:") + prompt_parts.append(f" - 止损: ${stop_loss:,.2f} (风险 {risk_ratio:.0f} / {rr_percent:.1f}%)") + prompt_parts.append(f" - 止盈: ${take_profit:,.2f} (盈利 {reward_ratio:.0f})") + prompt_parts.append(f" - 盈亏比: 1:{rr_ratio:.2f} {status}") + + except (ValueError, TypeError, ZeroDivisionError): + pass + prompt_parts.append(f"\n请根据以上信息,做出交易决策。") return "\n".join(prompt_parts) @@ -861,6 +937,62 @@ class TradingDecisionMaker: f"超过最大仓位金额 (保证金 ${margin:.2f} → 持仓价值 ${position_value:.2f}, 总计 ${new_total_value:,.2f} > ${max_position_value:,.2f})" ) + # 盈亏比检查:所有交易必须满足盈亏比 >= 1:2 + action = decision.get('action', '') + entry_price = decision.get('entry_price') + stop_loss = decision.get('stop_loss') + take_profit = decision.get('take_profit') + + if action and entry_price and stop_loss and take_profit: + try: + entry_price = float(entry_price) + stop_loss = float(stop_loss) + take_profit = float(take_profit) + + # 计算盈亏比 + if action == 'buy': + risk = entry_price - stop_loss + reward = take_profit - entry_price + elif action == 'sell': + risk = stop_loss - entry_price + reward = entry_price - take_profit + else: + risk = 0 + reward = 0 + + # 验证价格方向正确性 + if action == 'buy': + if stop_loss >= entry_price: + logger.warning(f"⚠️ 决策被拒绝: 做多止损错误 (entry={entry_price}, stop_loss={stop_loss} 应该 < entry)") + return self._get_hold_decision(decision['symbol'], f"做多止损价格错误") + if take_profit <= entry_price: + logger.warning(f"⚠️ 决策被拒绝: 做多止盈错误 (entry={entry_price}, take_profit={take_profit} 应该 > entry)") + return self._get_hold_decision(decision['symbol'], f"做多止盈价格错误") + elif action == 'sell': + if stop_loss <= entry_price: + logger.warning(f"⚠️ 决策被拒绝: 做空止损错误 (entry={entry_price}, stop_loss={stop_loss} 应该 > entry)") + return self._get_hold_decision(decision['symbol'], f"做空止损价格错误") + if take_profit >= entry_price: + logger.warning(f"⚠️ 决策被拒绝: 做空止盈错误 (entry={entry_price}, take_profit={take_profit} 应该 < entry)") + return self._get_hold_decision(decision['symbol'], f"做空止盈价格错误") + + # 检查盈亏比 + if risk > 0 and reward > 0: + rr_ratio = reward / risk + min_rr_ratio = 2.0 # 最小盈亏比 1:2 + + if rr_ratio < min_rr_ratio: + logger.warning(f"⚠️ 决策被拒绝: 盈亏比不足 (1:{rr_ratio:.2f} < 1:{min_rr_ratio:.0f})") + logger.warning(f" entry={entry_price}, stop_loss={stop_loss}, take_profit={take_profit}") + logger.warning(f" 风险={risk:.0f}, 盈利={reward:.0f}, 盈亏比=1:{rr_ratio:.2f}") + return self._get_hold_decision( + decision['symbol'], + f"盈亏比不足 (1:{rr_ratio:.2f} < 1:{min_rr_ratio:.0f})" + ) + + except (ValueError, TypeError, ZeroDivisionError) as e: + logger.warning(f"盈亏比检查失败: {e}") + # 价格距离检查:相同方向相同标的的挂单,价格距离 < 2% 时不加仓/开仓 action = decision.get('action', '') new_entry_price = decision.get('entry_price') diff --git a/backend/app/services/binance_service.py b/backend/app/services/binance_service.py index 63bbba7..20719c1 100644 --- a/backend/app/services/binance_service.py +++ b/backend/app/services/binance_service.py @@ -83,14 +83,15 @@ class BinanceService: # 1h: 300根 = 12.5天(中线分析) # 4h: 200根 = 33.3天(趋势分析) limits = { + '1m': 200, '5m': 200, '15m': 200, - '1h': 300, - '4h': 200 + '30m': 200, + '1h': 300 } data = {} - for interval in ['5m', '15m', '1h', '4h']: + for interval in ['1m', '5m', '15m', '30m', '1h']: df = self.get_klines(symbol, interval, limit=limits.get(interval, 100)) if not df.empty: df = self.calculate_indicators(df, interval) # 传递 interval 参数 @@ -139,23 +140,30 @@ class BinanceService: return df # 根据周期调整 MA 参数 - # 短周期(5m, 15m)使用较短的 MA,长周期使用较长的 MA + # 短周期(1m, 5m, 15m)使用较短的 MA,长周期使用较长的 MA ma_config = { + '1m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '5m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '15m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, + '30m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '1h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, - '4h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, } config = ma_config.get(interval, ma_config['1h']) - # 移动平均线(统一命名,便于 LLM 分析) - df['ma5'] = self._calculate_ma(df['close'], config['ma_short']) - df['ma10'] = self._calculate_ma(df['close'], config['ma_mid']) - df['ma20'] = self._calculate_ma(df['close'], config['ma_long']) - df['ma50'] = self._calculate_ma(df['close'], config['ma_extra']) + # EMA(日内交易使用 EMA 反应更快) + df['ema5'] = self._calculate_ema(df['close'], config['ma_short']) + df['ema10'] = self._calculate_ema(df['close'], config['ma_mid']) + df['ema20'] = self._calculate_ema(df['close'], config['ma_long']) + df['ema50'] = self._calculate_ema(df['close'], config['ma_extra']) - # EMA + # 兼容:保留 ma5/ma10/ma20/ma50 作为 EMA 的别名 + df['ma5'] = df['ema5'] + df['ma10'] = df['ema10'] + df['ma20'] = df['ema20'] + df['ma50'] = df['ema50'] + + # MACD EMA(保持不变) df['ema12'] = self._calculate_ema(df['close'], 12) df['ema26'] = self._calculate_ema(df['close'], 26) diff --git a/backend/app/services/bitget_service.py b/backend/app/services/bitget_service.py index 93424df..8f540b3 100644 --- a/backend/app/services/bitget_service.py +++ b/backend/app/services/bitget_service.py @@ -14,10 +14,11 @@ class BitgetService: # K线周期映射 - 注意 Bitget 使用大写 H INTERVALS = { + '1m': '1m', '5m': '5m', '15m': '15m', + '30m': '30m', '1h': '1H', # Bitget 大写 - '4h': '4H' # Bitget 大写 } # Bitget API 基础 URL @@ -103,22 +104,24 @@ class BitgetService: category: 产品类型,默认 USDT-FUTURES Returns: - 包含 5m, 15m, 1h, 4h 数据的字典 + 包含 1m, 5m, 15m, 30m, 1h 数据的字典 """ # 不同周期使用不同的数据量,平衡分析深度和性能 + # 1m: 200根 = 3.3小时(超短线精确入场) # 5m: 200根 = 16.7小时(日内分析) # 15m: 200根 = 2.1天(短线分析) - # 1h: 300根 = 12.5天(中线分析) - # 4h: 200根 = 33.3天(趋势分析) + # 30m: 200根 = 4.2天(日内趋势) + # 1h: 300根 = 12.5天(日内主趋势) limits = { + '1m': 200, '5m': 200, '15m': 200, - '1h': 300, - '4h': 200 + '30m': 200, + '1h': 300 } data = {} - for interval in ['5m', '15m', '1h', '4h']: + for interval in ['1m', '5m', '15m', '30m', '1h']: df = self.get_klines(symbol, interval, limit=limits.get(interval, 100), category=category) if not df.empty: @@ -175,21 +178,28 @@ class BitgetService: # 根据周期调整 MA 参数 ma_config = { + '1m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '5m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '15m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, + '30m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, '1h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, - '4h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50}, } config = ma_config.get(interval, ma_config['1h']) - # 移动平均线 - df['ma5'] = self._calculate_ma(df['close'], config['ma_short']) - df['ma10'] = self._calculate_ma(df['close'], config['ma_mid']) - df['ma20'] = self._calculate_ma(df['close'], config['ma_long']) - df['ma50'] = self._calculate_ma(df['close'], config['ma_extra']) + # EMA(日内交易使用 EMA 反应更快) + df['ema5'] = self._calculate_ema(df['close'], config['ma_short']) + df['ema10'] = self._calculate_ema(df['close'], config['ma_mid']) + df['ema20'] = self._calculate_ema(df['close'], config['ma_long']) + df['ema50'] = self._calculate_ema(df['close'], config['ma_extra']) - # EMA + # 兼容:保留 ma5/ma10/ma20/ma50 作为 EMA 的别名 + df['ma5'] = df['ema5'] + df['ma10'] = df['ema10'] + df['ma20'] = df['ema20'] + df['ma50'] = df['ema50'] + + # MACD EMA(保持不变) df['ema12'] = self._calculate_ema(df['close'], 12) df['ema26'] = self._calculate_ema(df['close'], 26)