diff --git a/CURRENT_STRATEGY.md b/CURRENT_STRATEGY.md new file mode 100644 index 0000000..c5f160f --- /dev/null +++ b/CURRENT_STRATEGY.md @@ -0,0 +1,231 @@ +# 当前交易策略方案文档 + +## 一、策略概述 + +当前系统采用基于技术指标的自适应交易策略,结合均值回归和趋势跟踪两种模式,根据市场状态自动切换。 + +### 核心特点 +- **自适应策略**:根据市场状态(趋势/震荡)自动选择策略 +- **技术指标过滤**:使用RSI、MACD、布林带、EMA等多指标确认 +- **信号强度要求**:信号强度 >= 5/10 才执行交易 +- **时间段差异化**:晚间激进,白天平衡 + +## 二、当前配置参数 + +### 仓位控制 +- **单笔最大仓位**:5% (MAX_POSITION_PERCENT: 0.05) +- **总仓位上限**:30% (MAX_TOTAL_POSITION_PERCENT: 0.30) +- **单笔最小仓位**:1% (MIN_POSITION_PERCENT: 0.01) + +### 市场扫描 +- **扫描间隔**:1小时 (SCAN_INTERVAL: 3600秒) +- **扫描交易对数量**:500个 (MAX_SCAN_SYMBOLS: 500) +- **处理交易对数量**:10个 (TOP_N_SYMBOLS: 10) +- **最小涨跌幅阈值**:2% (MIN_CHANGE_PERCENT: 2.0) +- **最小24小时成交量**:1000万USDT (MIN_VOLUME_24H: 10000000) + +### 风险控制 +- **止损**:3% (STOP_LOSS_PERCENT: 0.03) +- **止盈**:5% (TAKE_PROFIT_PERCENT: 0.05) +- **杠杆倍数**:10倍 (LEVERAGE: 10) +- **移动止损**:启用 (USE_TRAILING_STOP: True) +- **移动止损激活阈值**:1% (TRAILING_STOP_ACTIVATION: 0.01) +- **移动止损保护利润**:1% (TRAILING_STOP_PROTECT: 0.01) + +### 策略参数 +- **最小信号强度**:5/10 (MIN_SIGNAL_STRENGTH: 5) +- **K线周期**:1小时 (KLINE_INTERVAL: 1h) +- **主周期**:1小时 (PRIMARY_INTERVAL: 1h) +- **确认周期**:4小时 (CONFIRM_INTERVAL: 4h) +- **入场周期**:15分钟 (ENTRY_INTERVAL: 15m) + +## 三、策略逻辑 + +### 1. 市场扫描 +- 每小时扫描500个USDT交易对 +- 筛选涨跌幅 >= 2% 且成交量 >= 1000万USDT的交易对 +- 计算技术指标(RSI、MACD、布林带、EMA等) +- 按信号得分排序,选择前10个进行详细分析 + +### 2. 市场状态判断 +- **震荡市场 (ranging)**:使用均值回归策略 +- **趋势市场 (trending)**:使用趋势跟踪策略 + +### 3. 交易信号生成 + +#### 均值回归策略(震荡市场) +- **做多信号**: + - RSI < 30(超卖) + - 价格触及布林带下轨 +- **做空信号**: + - RSI > 70(超买) + - 价格触及布林带上轨 + +#### 趋势跟踪策略(趋势市场) +- **做多信号**: + - MACD金叉(MACD > Signal 且 Histogram > 0) + - 价格 > EMA20 > EMA50(上升趋势) +- **做空信号**: + - MACD死叉(MACD < Signal 且 Histogram < 0) + - 价格 < EMA20 < EMA50(下降趋势) + +#### 信号强度计算 +- 基础信号:2-4分 +- 多指标确认:+2分 +- 要求信号强度 >= 5/10 才执行交易 + +### 4. 风险控制 +- 仓位大小:根据账户余额和涨跌幅动态计算 +- 止损止盈:固定百分比(止损3%,止盈5%) +- 移动止损:盈利1%后激活,保护1%利润 +- 持仓监控:WebSocket实时监控 + 定时检查(5分钟) + +## 四、时间段策略 + +### 晚间激进策略(20:00-02:00 UTC+8) +- **特点**:市场波动大,机会多 +- **建议配置**: + - MAX_SCAN_SYMBOLS: 350-400(增加扫描范围) + - TOP_N_SYMBOLS: 15-20(处理更多交易对) + - MIN_SIGNAL_STRENGTH: 3-4(降低信号强度要求,捕捉更多机会) + - SCAN_INTERVAL: 1800-3600秒(30分钟-1小时) + +### 白天平衡策略(02:00-20:00 UTC+8) +- **特点**:市场相对平稳,注重信号质量 +- **建议配置**: + - MAX_SCAN_SYMBOLS: 250-300(平衡扫描范围) + - TOP_N_SYMBOLS: 10-12(选择优质机会) + - MIN_SIGNAL_STRENGTH: 5-6(提高信号强度要求) + - SCAN_INTERVAL: 3600秒(1小时) + +## 五、运行情况分析 + +### 统计数据 +- **总交易单数**:53单 +- **当前胜率**:40% +- **目标胜率**:50-60% + +### 胜率分析 + +#### 当前胜率偏低的原因 +1. **信号强度要求可能偏低** + - 当前MIN_SIGNAL_STRENGTH: 5 + - 可能接受了过多中等强度的信号 + +2. **时间段策略未完全实现** + - 晚间激进策略可能增加了低质量交易 + - 白天平衡策略的信号强度要求可能还不够严格 + +3. **市场环境判断可能不准确** + - 震荡市场和趋势市场的判断可能不够准确 + - 导致使用了错误的策略 + +4. **止损止盈比例** + - 止损3%,止盈5%,盈亏比约1.67:1 + - 如果胜率低于37.5%,整体会亏损 + +## 六、改进建议 + +### 1. 提高信号强度要求 +**建议**:将MIN_SIGNAL_STRENGTH从5提高到6-7 +- **理由**:减少假信号,提高入场质量 +- **预期效果**:胜率提升5-10%,但交易频率可能下降 + +### 2. 优化时间段策略 +**建议**:实现自动时间段切换 +- **晚间(20:00-02:00)**: + - MIN_SIGNAL_STRENGTH: 4(激进) + - TOP_N_SYMBOLS: 15-18 +- **白天(02:00-20:00)**: + - MIN_SIGNAL_STRENGTH: 6-7(保守) + - TOP_N_SYMBOLS: 8-10 + +### 3. 优化止损止盈比例 +**建议**:调整止损止盈比例,提高盈亏比 +- **方案A**:止损2.5%,止盈5%(盈亏比2:1) +- **方案B**:止损3%,止盈6%(盈亏比2:1) +- **预期效果**:即使胜率40%,盈亏比2:1也能盈利 + +### 4. 加强市场状态判断 +**建议**: +- 使用更多指标判断市场状态 +- 增加市场状态的确认机制 +- 在不确定的市场状态下降低仓位或暂停交易 + +### 5. 增加过滤条件 +**建议**: +- 增加成交量确认(确保有足够的流动性) +- 增加波动率过滤(避免在极端波动时交易) +- 增加相关性检查(避免同时持有高度相关的币种) + +### 6. 优化移动止损 +**建议**: +- 提高移动止损激活阈值到2% +- 增加移动止损保护利润到1.5-2% +- **预期效果**:更好地保护利润,减少盈利变亏损 + +## 七、预期改进效果 + +### 短期目标(1-2周) +- **胜率目标**:45-50% +- **改进措施**: + - 提高MIN_SIGNAL_STRENGTH到6 + - 优化止损止盈比例到2:1 + - 加强市场状态判断 + +### 中期目标(1个月) +- **胜率目标**:50-55% +- **改进措施**: + - 实现时间段自动切换 + - 优化移动止损参数 + - 增加更多过滤条件 + +### 长期目标(3个月) +- **胜率目标**:55-60% +- **改进措施**: + - 建立回测系统 + - 使用机器学习优化信号权重 + - 多时间周期确认 + +## 八、监控指标 + +### 关键指标 +1. **胜率**:目标 > 50% +2. **盈亏比**:目标 > 1.5:1 +3. **平均盈利/亏损**:确保平均盈利 > 平均亏损 +4. **交易频率**:每天3-10单(根据市场情况) +5. **持仓数量**:同时持仓3-8个 + +### 需要关注的日志 +- 信号强度分布 +- 市场状态分布(趋势/震荡) +- 移动止损激活情况 +- 止损/止盈触发比例 + +## 九、风险提示 + +1. **胜率40%偏低**:如果盈亏比不够高,整体可能亏损 +2. **时间段策略**:需要根据实际市场情况调整,不能盲目激进 +3. **市场环境变化**:策略需要适应不同的市场环境 +4. **参数调整**:不要频繁大幅调整参数,建议渐进式优化 + +## 十、下一步行动 + +1. **立即执行**: + - 提高MIN_SIGNAL_STRENGTH到6 + - 调整止损止盈比例到2:1(止损2.5%,止盈5%) + +2. **本周完成**: + - 实现时间段自动切换功能 + - 优化移动止损参数 + +3. **本月完成**: + - 加强市场状态判断 + - 增加更多过滤条件 + - 建立数据分析和回测系统 + +--- + +**文档更新时间**:2024年 +**策略版本**:v1.0 +**下次评估时间**:运行100单后重新评估 diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index 4eb6ef3..93e6030 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -65,7 +65,7 @@ async def get_trades( 如果同时提供了 period 和 start_date/end_date,period 优先级更高 """ try: - logger.info(f"获取交易记录请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, status={status}, limit={limit}") + logger.info(f"获取交易记录请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, status={status}, limit={limit}, trade_type={trade_type}, exit_reason={exit_reason}") # 如果提供了 period,使用快速时间段筛选 if period: period_start, period_end = get_date_range(period) @@ -80,7 +80,7 @@ async def get_trades( if end_date and len(end_date) == 10: # YYYY-MM-DD end_date = f"{end_date} 23:59:59" - trades = Trade.get_all(start_date, end_date, symbol, status) + trades = Trade.get_all(start_date, end_date, symbol, status, trade_type, exit_reason) logger.info(f"查询到 {len(trades)} 条交易记录") # 格式化交易记录,添加平仓类型的中文显示 diff --git a/backend/database/init.sql b/backend/database/init.sql index c45e0e3..92aefe0 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -80,6 +80,8 @@ CREATE TABLE IF NOT EXISTS `trading_signals` ( INDEX `idx_executed` (`executed`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易信号表'; + + -- 初始化默认配置 INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `category`, `description`) VALUES -- 仓位控制 @@ -108,7 +110,7 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate ('MIN_VOLATILITY', '0.02', 'number', 'scan', '最小波动率:2%'), -- 高胜率策略参数 -('MIN_SIGNAL_STRENGTH', '5', 'number', 'strategy', '最小信号强度(0-10)'), +('MIN_SIGNAL_STRENGTH', '7', 'number', 'strategy', '最小信号强度(0-10),已提升至7以提高入场质量'), ('LEVERAGE', '10', 'number', 'strategy', '杠杆倍数'), ('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'), ('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值(盈利1%后激活)'), diff --git a/newplan20260115.md b/newplan20260115.md new file mode 100644 index 0000000..e9e7069 --- /dev/null +++ b/newplan20260115.md @@ -0,0 +1,138 @@ +核心诊断:盈利公式与当前瓶颈 +盈利的核心公式是:期望收益 = 胜率 × 平均盈利 - (1-胜率) × 平均亏损。 +您当前配置是:胜率40%,盈亏比约1.67:1(止损3%,止盈5%)。 +代入公式:0.4*5 - 0.6*3 = 2 - 1.8 = 0.2%(每笔交易的期望收益为正,但非常微薄,易被滑点、手续费侵蚀)。 + +主要瓶颈在于: + +胜率偏低:40%的胜率对策略的容错率要求极高。 + +盈亏比不具优势:1.67:1的盈亏比在40%胜率下仅能勉强保本。 + +“激进”与“平衡”时段的定义可能过于时间驱动,而非市场驱动。 + +系统性优化方案(从高优先级到低优先级) +第一阶段:立即实施(提高单笔交易质量) +严格强化入场信号(提升胜率最直接的方法) + +多时间周期共振:您已有主周期(1h)和确认周期(4h)。应严格执行 “大周期定方向,小周期找点位”。 + +做多:4H趋势向上(如价格 > 4H EMA20)时,仅在1H和15min上寻找做多信号。绝对禁止在4H下跌趋势中做1H级别的“反弹多”。 + +做空:同理。这能极大过滤逆势交易,提升胜率。 + +增加量价确认: + +买入信号时,要求入场K线(或前一根)的成交量显著高于近期均量,确认买盘力量。 + +突破布林带时,要求K线实体饱满,而非长影线触及。 + +将 MIN_SIGNAL_STRENGTH 从5提升至7。这看似激进,但结合多周期共振,能确保只有最强、最清晰的信号才被执行。预期牺牲20-30%的交易机会,但目标是提升单笔交易胜率10%以上。 + +优化风险回报结构(提升盈亏比) + +放弃固定止盈止损,采用 基于技术结构的动态风控。 + +止损:设置在关键支撑/阻力位、布林带外轨或前高/前低之外 + 一定缓冲。例如,做多时,止损放在近期波段低点下方0.5-1%。这通常比固定的3%更合理。 + +止盈:采用 分步止盈。 + +第一目标(保守):近期阻力位或盈亏比1:1的位置,了结50%仓位。 + +第二目标(激进):更远的技术位(如趋势线、前高),让剩余仓位配合移动止损博取更大利润。 + +此举目标:将平均盈亏比提升至 2.5:1 甚至 3:1。 + +第二阶段:近期优化(提升系统适应性与仓位效率) +重构“时间段策略”为“市场波动率策略” + +时间本身不产生机会,波动率才是。建议根据平均真实波幅(ATR)或布林带带宽来定义“激进”与“保守”模式。 + +高波动率模式(激进):当波动率高于近期均值时。 + +仓位系数 = 0.5 (降低标准仓位的一半,以应对更大风险)。 + +MIN_SIGNAL_STRENGTH 可适当降低至6,但必须配合更宽的止损(如基于ATR的2倍)。 + +低波动率模式(保守):当波动率低于近期均值时。 + +仓位系数 = 1.0 (标准仓位)。 + +MIN_SIGNAL_STRENGTH = 7 (信号要求最高)。 + +极度波动模式(暂停):当波动率突破极端阈值(如+3标准差),暂停开新仓,仅管理现有持仓。 + +实施基于凯利公式或固定分数法的动态仓位管理 + +当前固定的5%仓位过于僵化。应根据交易信号的强度和账户当前状态动态调整。 + +简化版:单笔风险 = 账户总资金的0.5% - 1%。 + +假设止损距离是2%,那么仓位大小就是 (账户资金 * 0.5%) / (入场价 * 2%)。 + +优势:无论交易什么币种,每笔交易的最大亏损是固定的,真正控制了风险。 + +第三阶段:中长期建设(构建护城河) +增加“策略失效”监控与熔断机制 + +设定一个连续亏损次数(如5次) 或单日最大回撤比例(如3%) 的阈值。一旦触发,系统自动切换至“观察模式”或“极小仓位模式”,防止情绪化决策下的恶性循环。 + +建立简易但必须的回测与统计分析框架 + +记录每一笔交易的:入场理由(趋势/震荡)、信号强度、使用指标、持仓时间、结果。 + +定期(如每周)分析: + +哪种市场状态下胜率高?(趋势强?震荡市?) + +哪个技术指标组合的预测力最强?(是MACD金叉+放量,还是RSI超卖+布林带下轨?) + +据此动态调整信号权重,让策略自我进化。 + +优化后的预期与监控重点 +预期效果: + +短期(1-2周):通过强化信号(多周期共振,强度7) 和动态风控,目标将胜率稳定至45%-50%,盈亏比提升至2.5:1。 + +中期(1个月):引入波动率仓位管理和分步止盈,目标使策略在不同市场环境下回撤可控,月化收益率曲线更加平滑。 + +长期:通过数据分析驱动策略微调,形成稳定正期望的交易系统。 + +新的关键监控指标: + +期望收益:核心中的核心,必须为正且大于0.5%。 + +风险调整后收益:如夏普比率。 + +最大连续亏损次数/金额:衡量策略的逆境承受力。 + +不同市场状态下的胜率与盈亏比:检验策略的适应性。 + +信号强度与胜率的相关性:验证信号系统的有效性。 + +总结:行动路线图 +本周: + +立即:将 MIN_SIGNAL_STRENGTH 提升至 7。 + +立即:实施 多时间周期共振入场规则(4H定方向)。 + +立即:将止损改为 基于支撑/阻力的动态止损。 + +设计:分步止盈和移动止损逻辑。 + +下周: + +引入 基于ATR的波动率仓位管理。 + +实施 基于固定百分比风险的仓位计算。 + +本月内: + +建立交易日志数据库。 + +建立 策略熔断机制(连续亏损5次暂停)。 + +完成首次数据分析报告。 + +交易系统的优化是一场“精益求精”的工程。您的文档已有一个极好的起点。请务必记住:每一次调整最好只改变1-2个变量,并观察足够样本(至少20-30笔交易)后再做下一次调整,这样才能清晰归因。 \ No newline at end of file diff --git a/trading_system/config.py b/trading_system/config.py index f717bb5..63fdacc 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -176,7 +176,7 @@ def _get_trading_config(): 'ENTRY_INTERVAL': '15m', 'MIN_VOLUME_24H': 10000000, 'MIN_VOLATILITY': 0.02, - 'MIN_SIGNAL_STRENGTH': 5, + 'MIN_SIGNAL_STRENGTH': 7, # 提升至7以提高入场质量,减少假信号 'LEVERAGE': 10, 'USE_TRAILING_STOP': True, 'TRAILING_STOP_ACTIVATION': 0.01, diff --git a/trading_system/market_scanner.py b/trading_system/market_scanner.py index cf707e1..16b6441 100644 --- a/trading_system/market_scanner.py +++ b/trading_system/market_scanner.py @@ -194,11 +194,22 @@ class MarketScanner: if len(klines) < 2: return None - # 提取价格数据 + # 获取4H周期数据用于多周期共振检查 + confirm_interval = config.TRADING_CONFIG.get('CONFIRM_INTERVAL', '4h') + klines_4h = await self.client.get_klines( + symbol=symbol, + interval=confirm_interval, + limit=50 # 获取4H周期数据 + ) + + # 提取价格数据(主周期) close_prices = [float(k[4]) for k in klines] # 收盘价 high_prices = [float(k[2]) for k in klines] # 最高价 low_prices = [float(k[3]) for k in klines] # 最低价 + # 提取4H周期价格数据 + close_prices_4h = [float(k[4]) for k in klines_4h] if len(klines_4h) >= 2 else close_prices + # 计算涨跌幅(基于主周期) current_price = close_prices[-1] prev_price = close_prices[-2] if len(close_prices) >= 2 else close_prices[0] @@ -216,6 +227,9 @@ class MarketScanner: ema20 = TechnicalIndicators.calculate_ema(close_prices, period=20) ema50 = TechnicalIndicators.calculate_ema(close_prices, period=50) + # 计算4H周期的EMA20用于多周期共振检查 + ema20_4h = TechnicalIndicators.calculate_ema(close_prices_4h, period=20) if len(close_prices_4h) >= 20 else None + # 判断市场状态 market_regime = TechnicalIndicators.detect_market_regime(close_prices) @@ -267,9 +281,12 @@ class MarketScanner: 'atr': atr, 'ema20': ema20, 'ema50': ema50, + 'ema20_4h': ema20_4h, # 4H周期EMA20,用于多周期共振 + 'price_4h': close_prices_4h[-1] if len(close_prices_4h) > 0 else current_price, # 4H周期当前价格 'marketRegime': market_regime, 'signalScore': signal_score, - 'klines': klines[-10:] # 保留最近10根K线 + 'klines': klines[-10:], # 保留最近10根K线 + 'klines_4h': klines_4h[-10:] if len(klines_4h) >= 10 else klines_4h # 保留最近10根4H K线 } except Exception as e: logger.debug(f"获取 {symbol} 数据失败: {e}") diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 5f55a94..0bd5172 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -67,7 +67,9 @@ class PositionManager: leverage: int = 10, trade_direction: Optional[str] = None, entry_reason: str = '', - atr: Optional[float] = None + atr: Optional[float] = None, + klines: Optional[List] = None, + bollinger: Optional[Dict] = None ) -> Optional[Dict]: """ 开仓 @@ -118,22 +120,52 @@ class PositionManager: entry_price = ticker['price'] - # 计算动态止损止盈(使用ATR或固定比例) - if atr and atr > 0: - # 使用ATR计算动态止损(2倍ATR) - atr_stop_loss_pct = (atr * 2) / entry_price - # 限制在合理范围内(1%-5%) - atr_stop_loss_pct = max(0.01, min(0.05, atr_stop_loss_pct)) + # 获取K线数据用于动态止损计算(从symbol_info中获取,如果可用) + klines = None + bollinger = None + if 'klines' in locals() or 'symbol_info' in locals(): + # 尝试从外部传入的symbol_info获取 + pass + + # 计算基于支撑/阻力的动态止损 + # 优先使用技术结构(支撑/阻力位、布林带) + # 如果无法获取K线数据,回退到ATR或固定止损 + if not klines: + # 如果没有传入K线数据,尝试获取 + try: + primary_interval = config.TRADING_CONFIG.get('PRIMARY_INTERVAL', '1h') + klines_data = await self.client.get_klines( + symbol=symbol, + interval=primary_interval, + limit=20 # 获取最近20根K线用于计算支撑/阻力 + ) + klines = klines_data if len(klines_data) >= 10 else None + except Exception as e: + logger.debug(f"获取K线数据失败,使用固定止损: {e}") + klines = None + + # 使用基于支撑/阻力的动态止损 + if klines or bollinger or atr: stop_loss_price = self.risk_manager.get_stop_loss_price( - entry_price, side, stop_loss_pct=atr_stop_loss_pct + entry_price, side, + klines=klines, + bollinger=bollinger, + atr=atr ) - # 止盈为止损的1.5-2倍 - take_profit_pct = atr_stop_loss_pct * 1.8 + + # 计算动态止损百分比(用于计算止盈) + if side == 'BUY': + stop_loss_pct = (entry_price - stop_loss_price) / entry_price + else: + stop_loss_pct = (stop_loss_price - entry_price) / entry_price + + # 止盈为止损的2-2.5倍(提高盈亏比) + take_profit_pct = stop_loss_pct * 2.5 take_profit_price = self.risk_manager.get_take_profit_price( entry_price, side, take_profit_pct=take_profit_pct ) else: - # 使用固定止损止盈 + # 回退到固定止损止盈 stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side) take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side) @@ -170,7 +202,17 @@ class PositionManager: elif not Trade: logger.warning(f"Trade模型未导入,无法保存 {symbol} 交易记录") - # 记录持仓信息(包含动态止损止盈) + # 计算分步止盈价格 + # 第一目标:盈亏比1:1(保守,了结50%仓位) + if side == 'BUY': + take_profit_1 = entry_price + (entry_price - stop_loss_price) # 盈亏比1:1 + else: + take_profit_1 = entry_price - (stop_loss_price - entry_price) # 盈亏比1:1 + + # 第二目标:原始止盈价(激进,剩余50%仓位) + take_profit_2 = take_profit_price + + # 记录持仓信息(包含动态止损止盈和分步止盈) position_info = { 'symbol': symbol, 'side': side, @@ -180,7 +222,11 @@ class PositionManager: 'orderId': order.get('orderId'), 'tradeId': trade_id, # 数据库交易ID 'stopLoss': stop_loss_price, - 'takeProfit': take_profit_price, + 'takeProfit': take_profit_price, # 保留原始止盈价(第二目标) + 'takeProfit1': take_profit_1, # 第一目标(盈亏比1:1,了结50%) + 'takeProfit2': take_profit_2, # 第二目标(原始止盈价,剩余50%) + 'partialProfitTaken': False, # 是否已部分止盈 + 'remainingQuantity': quantity, # 剩余仓位数量 'initialStopLoss': stop_loss_price, # 初始止损(用于移动止损) 'leverage': leverage, 'entryReason': entry_reason, @@ -537,57 +583,168 @@ class PositionManager: closed_positions.append(symbol) continue - # 检查止盈 - take_profit = position_info['takeProfit'] - if position_info['side'] == 'BUY' and current_price >= take_profit: - logger.info( - f"{symbol} 触发止盈: {current_price:.4f} >= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - # 确定平仓原因 - exit_reason = 'take_profit' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止盈记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue + # 检查分步止盈 + take_profit_1 = position_info.get('takeProfit1') # 第一目标(盈亏比1:1) + take_profit_2 = position_info.get('takeProfit2', position_info.get('takeProfit')) # 第二目标 + partial_profit_taken = position_info.get('partialProfitTaken', False) + remaining_quantity = position_info.get('remainingQuantity', quantity) - if position_info['side'] == 'SELL' and current_price <= take_profit: - logger.info( - f"{symbol} 触发止盈: {current_price:.4f} <= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - # 确定平仓原因 - exit_reason = 'take_profit' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent + # 第一目标:盈亏比1:1,了结50%仓位 + if not partial_profit_taken: + if position_info['side'] == 'BUY' and current_price >= take_profit_1: + logger.info( + f"{symbol} 触发第一目标止盈(盈亏比1:1): {current_price:.4f} >= {take_profit_1:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + # 部分平仓50% + partial_quantity = quantity * 0.5 + try: + # 部分平仓 + close_side = 'SELL' if position_info['side'] == 'BUY' else 'BUY' + partial_order = await self.client.place_order( + symbol=symbol, + side=close_side, + quantity=partial_quantity, + order_type='MARKET' + ) + if partial_order: + position_info['partialProfitTaken'] = True + position_info['remainingQuantity'] = remaining_quantity - partial_quantity + logger.info( + f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f},剩余{position_info['remainingQuantity']:.4f}" ) - except Exception as e: - logger.warning(f"更新止盈记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue + # 更新止损为成本价(保护剩余仓位) + position_info['stopLoss'] = entry_price + position_info['trailingStopActivated'] = True + logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") + except Exception as e: + logger.error(f"{symbol} 部分止盈失败: {e}") + elif position_info['side'] == 'SELL' and current_price <= take_profit_1: + logger.info( + f"{symbol} 触发第一目标止盈(盈亏比1:1): {current_price:.4f} <= {take_profit_1:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + # 部分平仓50% + partial_quantity = quantity * 0.5 + try: + # 部分平仓 + partial_order = await self.client.place_order( + symbol=symbol, + side='BUY', # 做空平仓用买入 + quantity=partial_quantity, + order_type='MARKET' + ) + if partial_order: + position_info['partialProfitTaken'] = True + position_info['remainingQuantity'] = remaining_quantity - partial_quantity + logger.info( + f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f},剩余{position_info['remainingQuantity']:.4f}" + ) + # 更新止损为成本价(保护剩余仓位) + position_info['stopLoss'] = entry_price + position_info['trailingStopActivated'] = True + logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") + except Exception as e: + logger.error(f"{symbol} 部分止盈失败: {e}") + + # 第二目标:原始止盈价,平掉剩余仓位 + if partial_profit_taken: + if position_info['side'] == 'BUY' and current_price >= take_profit_2: + logger.info( + f"{symbol} 触发第二目标止盈: {current_price:.4f} >= {take_profit_2:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + exit_reason = 'take_profit' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_percent * entry_price * quantity / 100, + pnl_percent=pnl_percent + ) + except Exception as e: + logger.warning(f"更新止盈记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue + elif position_info['side'] == 'SELL' and current_price <= take_profit_2: + logger.info( + f"{symbol} 触发第二目标止盈: {current_price:.4f} <= {take_profit_2:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + exit_reason = 'take_profit' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_percent * entry_price * quantity / 100, + pnl_percent=pnl_percent + ) + except Exception as e: + logger.warning(f"更新止盈记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue + else: + # 如果未部分止盈,但达到第二目标,直接全部平仓 + take_profit = position_info.get('takeProfit') + if position_info['side'] == 'BUY' and current_price >= take_profit: + logger.info( + f"{symbol} 触发止盈: {current_price:.4f} >= {take_profit:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + exit_reason = 'take_profit' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_percent * entry_price * quantity / 100, + pnl_percent=pnl_percent + ) + except Exception as e: + logger.warning(f"更新止盈记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue + + if position_info['side'] == 'SELL' and current_price <= take_profit: + logger.info( + f"{symbol} 触发止盈: {current_price:.4f} <= {take_profit:.4f} " + f"(盈亏: {pnl_percent:.2f}%)" + ) + exit_reason = 'take_profit' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_percent * entry_price * quantity / 100, + pnl_percent=pnl_percent + ) + except Exception as e: + logger.warning(f"更新止盈记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue except Exception as e: logger.error(f"检查止损止盈失败: {e}") diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 724e78e..020535c 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -347,19 +347,83 @@ class RiskManager: self, entry_price: float, side: str, - stop_loss_pct: Optional[float] = None + stop_loss_pct: Optional[float] = None, + klines: Optional[List] = None, + bollinger: Optional[Dict] = None, + atr: Optional[float] = None ) -> float: """ - 计算止损价格 + 计算止损价格(基于支撑/阻力的动态止损) Args: entry_price: 入场价格 side: 方向 'BUY' 或 'SELL' stop_loss_pct: 止损百分比,如果为None则使用配置值 + klines: K线数据,用于计算支撑/阻力位 + bollinger: 布林带数据,用于计算动态止损 + atr: 平均真实波幅,用于计算动态止损 Returns: 止损价格 """ + # 优先使用基于技术结构的动态止损 + if klines and len(klines) >= 10: + # 计算支撑/阻力位 + low_prices = [float(k[3]) for k in klines[-20:]] # 最近20根K线的最低价 + high_prices = [float(k[2]) for k in klines[-20:]] # 最近20根K线的最高价 + + if side == 'BUY': # 做多,止损放在支撑位下方 + # 找到近期波段低点 + recent_low = min(low_prices) + # 止损放在低点下方0.5-1% + buffer = entry_price * 0.005 # 0.5%缓冲 + dynamic_stop = recent_low - buffer + + # 如果布林带可用,也可以考虑布林带下轨 + if bollinger and bollinger.get('lower'): + bollinger_stop = bollinger['lower'] * 0.995 # 布林带下轨下方0.5% + dynamic_stop = max(dynamic_stop, bollinger_stop) + + # 确保止损不超过固定止损范围(1%-5%) + fixed_stop_pct = stop_loss_pct or self.config['STOP_LOSS_PERCENT'] + max_stop = entry_price * (1 - 0.01) # 最多1%止损 + min_stop = entry_price * (1 - 0.05) # 最少5%止损(极端情况) + + dynamic_stop = max(min_stop, min(max_stop, dynamic_stop)) + + logger.info( + f"动态止损计算 (BUY): 入场价={entry_price:.4f}, " + f"近期低点={recent_low:.4f}, 动态止损={dynamic_stop:.4f}, " + f"止损比例={((entry_price - dynamic_stop) / entry_price * 100):.2f}%" + ) + return dynamic_stop + else: # 做空,止损放在阻力位上方 + # 找到近期波段高点 + recent_high = max(high_prices) + # 止损放在高点上方0.5-1% + buffer = entry_price * 0.005 # 0.5%缓冲 + dynamic_stop = recent_high + buffer + + # 如果布林带可用,也可以考虑布林带上轨 + if bollinger and bollinger.get('upper'): + bollinger_stop = bollinger['upper'] * 1.005 # 布林带上轨上方0.5% + dynamic_stop = min(dynamic_stop, bollinger_stop) + + # 确保止损不超过固定止损范围(1%-5%) + fixed_stop_pct = stop_loss_pct or self.config['STOP_LOSS_PERCENT'] + min_stop = entry_price * (1 + 0.01) # 最少1%止损 + max_stop = entry_price * (1 + 0.05) # 最多5%止损(极端情况) + + dynamic_stop = min(max_stop, max(min_stop, dynamic_stop)) + + logger.info( + f"动态止损计算 (SELL): 入场价={entry_price:.4f}, " + f"近期高点={recent_high:.4f}, 动态止损={dynamic_stop:.4f}, " + f"止损比例={((dynamic_stop - entry_price) / entry_price * 100):.2f}%" + ) + return dynamic_stop + + # 回退到固定百分比止损 stop_loss_percent = stop_loss_pct or self.config['STOP_LOSS_PERCENT'] if side == 'BUY': # 做多,止损价低于入场价 diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 788e01d..bbfa63c 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -149,7 +149,9 @@ class TradingStrategy: leverage=config.TRADING_CONFIG.get('LEVERAGE', 10), trade_direction=trade_direction, entry_reason=entry_reason, - atr=symbol_info.get('atr') + atr=symbol_info.get('atr'), + klines=symbol_info.get('klines'), # 传递K线数据用于动态止损 + bollinger=symbol_info.get('bollinger') # 传递布林带数据用于动态止损 ) if position: @@ -261,6 +263,7 @@ class TradingStrategy: async def _analyze_trade_signal(self, symbol_info: Dict) -> Dict: """ 使用技术指标分析交易信号(高胜率策略) + 实施多周期共振:4H定方向,1H和15min找点位 Args: symbol_info: 交易对信息(包含技术指标) @@ -277,68 +280,117 @@ class TradingStrategy: ema20 = symbol_info.get('ema20') ema50 = symbol_info.get('ema50') + # 多周期共振检查:4H定方向 + price_4h = symbol_info.get('price_4h', current_price) + ema20_4h = symbol_info.get('ema20_4h') + + # 判断4H周期趋势方向 + trend_4h = None # 'up', 'down', 'neutral' + if ema20_4h is not None: + if price_4h > ema20_4h: + trend_4h = 'up' + elif price_4h < ema20_4h: + trend_4h = 'down' + else: + trend_4h = 'neutral' + signal_strength = 0 reasons = [] direction = None + # 多周期共振检查:4H定方向,禁止逆势交易 + if trend_4h == 'up': + # 4H趋势向上,只允许做多信号 + logger.debug(f"{symbol} 4H趋势向上,只允许做多信号") + elif trend_4h == 'down': + # 4H趋势向下,只允许做空信号 + logger.debug(f"{symbol} 4H趋势向下,只允许做空信号") + elif trend_4h is None: + # 无法判断4H趋势,记录警告 + logger.warning(f"{symbol} 无法判断4H趋势,可能影响信号质量") + # 策略1:均值回归(震荡市场,高胜率) if market_regime == 'ranging': - # RSI超卖,做多信号 + # RSI超卖,做多信号(需4H趋势向上或中性) if rsi and rsi < 30: - signal_strength += 4 - reasons.append(f"RSI超卖({rsi:.1f})") - if direction is None: - direction = 'BUY' + if trend_4h in ('up', 'neutral', None): + signal_strength += 4 + reasons.append(f"RSI超卖({rsi:.1f})") + if direction is None: + direction = 'BUY' + else: + reasons.append(f"RSI超卖但4H趋势向下,禁止逆势做多") - # RSI超买,做空信号 + # RSI超买,做空信号(需4H趋势向下或中性) elif rsi and rsi > 70: - signal_strength += 4 - reasons.append(f"RSI超买({rsi:.1f})") - if direction is None: - direction = 'SELL' + if trend_4h in ('down', 'neutral', None): + signal_strength += 4 + reasons.append(f"RSI超买({rsi:.1f})") + if direction is None: + direction = 'SELL' + else: + reasons.append(f"RSI超买但4H趋势向上,禁止逆势做空") - # 布林带下轨,做多信号 + # 布林带下轨,做多信号(需4H趋势向上或中性) if bollinger and current_price <= bollinger['lower']: - signal_strength += 3 - reasons.append("触及布林带下轨") - if direction is None: - direction = 'BUY' + if trend_4h in ('up', 'neutral', None): + signal_strength += 3 + reasons.append("触及布林带下轨") + if direction is None: + direction = 'BUY' + else: + reasons.append("触及布林带下轨但4H趋势向下,禁止逆势做多") - # 布林带上轨,做空信号 + # 布林带上轨,做空信号(需4H趋势向下或中性) elif bollinger and current_price >= bollinger['upper']: - signal_strength += 3 - reasons.append("触及布林带上轨") - if direction is None: - direction = 'SELL' + if trend_4h in ('down', 'neutral', None): + signal_strength += 3 + reasons.append("触及布林带上轨") + if direction is None: + direction = 'SELL' + else: + reasons.append("触及布林带上轨但4H趋势向上,禁止逆势做空") # 策略2:趋势跟踪(趋势市场) elif market_regime == 'trending': - # MACD金叉,做多信号 + # MACD金叉,做多信号(需4H趋势向上或中性) if macd and macd['macd'] > macd['signal'] and macd['histogram'] > 0: - signal_strength += 3 - reasons.append("MACD金叉") - if direction is None: - direction = 'BUY' + if trend_4h in ('up', 'neutral', None): + signal_strength += 3 + reasons.append("MACD金叉") + if direction is None: + direction = 'BUY' + else: + reasons.append("MACD金叉但4H趋势向下,禁止逆势做多") - # MACD死叉,做空信号 + # MACD死叉,做空信号(需4H趋势向下或中性) elif macd and macd['macd'] < macd['signal'] and macd['histogram'] < 0: - signal_strength += 3 - reasons.append("MACD死叉") - if direction is None: - direction = 'SELL' + if trend_4h in ('down', 'neutral', None): + signal_strength += 3 + reasons.append("MACD死叉") + if direction is None: + direction = 'SELL' + else: + reasons.append("MACD死叉但4H趋势向上,禁止逆势做空") # 均线系统 if ema20 and ema50: if current_price > ema20 > ema50: # 上升趋势 - signal_strength += 2 - reasons.append("价格在均线之上") - if direction is None: - direction = 'BUY' + if trend_4h in ('up', 'neutral', None): + signal_strength += 2 + reasons.append("价格在均线之上") + if direction is None: + direction = 'BUY' + else: + reasons.append("1H均线向上但4H趋势向下,禁止逆势做多") elif current_price < ema20 < ema50: # 下降趋势 - signal_strength += 2 - reasons.append("价格在均线之下") - if direction is None: - direction = 'SELL' + if trend_4h in ('down', 'neutral', None): + signal_strength += 2 + reasons.append("价格在均线之下") + if direction is None: + direction = 'SELL' + else: + reasons.append("1H均线向下但4H趋势向上,禁止逆势做空") # 策略3:综合信号(提高胜率) # 多个指标同时确认时,信号更强 @@ -370,8 +422,25 @@ class TradingStrategy: signal_strength += 2 reasons.append(f"{confirmations}个指标确认") - # 判断是否应该交易(信号强度 >= 5 才交易,提高胜率) - should_trade = signal_strength >= config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 5) + # 多周期共振加分:如果4H趋势与信号方向一致,增加信号强度 + if direction and trend_4h: + if (direction == 'BUY' and trend_4h == 'up') or (direction == 'SELL' and trend_4h == 'down'): + signal_strength += 2 + reasons.append("4H周期共振确认") + elif (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'): + # 逆势信号,降低信号强度或直接拒绝 + signal_strength -= 3 + reasons.append("⚠️ 逆4H趋势,信号强度降低") + + # 判断是否应该交易(信号强度 >= 7 才交易,提高胜率) + min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7) + should_trade = signal_strength >= min_signal_strength + + # 如果信号方向与4H趋势相反,直接拒绝交易 + if direction and trend_4h: + if (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'): + should_trade = False + reasons.append("❌ 禁止逆4H趋势交易") if not should_trade and direction: reasons.append(f"信号强度不足({signal_strength}/10)")