1
This commit is contained in:
parent
cb251a7866
commit
a1b54d658f
65
docs/止损止盈双通道说明.md
Normal file
65
docs/止损止盈双通道说明.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# 止损/止盈的两种实现方式(双通道)
|
||||
|
||||
当前确实存在**两种**止损(以及止盈)执行方式,互为备份;逻辑如下。
|
||||
|
||||
---
|
||||
|
||||
## 一、两种方式分别是什么
|
||||
|
||||
### 1. 币安条件单(交易所侧,自动执行)
|
||||
|
||||
- **做法**:开仓或补挂时,在币安挂 **STOP_MARKET**(止损)和 **TAKE_PROFIT_MARKET**(止盈)条件单,触发价为策略算出的 `stop_loss` / `take_profit` 价格。
|
||||
- **触发**:价格到达触发价时,**由币安自动撮合**,无需本机程序在线。
|
||||
- **代码位置**:`position_manager._ensure_exchange_sltp_orders` → `client.place_trigger_close_position_order(trigger_type="STOP_MARKET" / "TAKE_PROFIT_MARKET", stop_price=...)`。
|
||||
- **特点**:服务重启、断网、本机崩溃后,只要仓位和条件单还在,仍能按价止损/止盈。
|
||||
|
||||
### 2. 系统 WebSocket 监控(本机侧,到价后市价平仓)
|
||||
|
||||
- **做法**:本机对每个持仓订阅该交易对价格(WebSocket),在 `_check_single_position` 里用**当前价**和**目标止损/止盈**比较(按保证金的亏损/盈利比例),到价则调用 `close_position(symbol, reason='stop_loss'/'take_profit'/...)`,即**市价平仓**。
|
||||
- **触发**:程序判定「当前盈亏已到止损或止盈目标」时,**本机主动发市价平仓单**。
|
||||
- **代码位置**:`position_manager._monitor_position_price` → `_check_single_position` → 比较 `pnl_percent_margin` 与 `stop_loss_pct_margin` / 止盈目标 → `close_position(...)`。
|
||||
- **特点**:可以做**移动止损、分步止盈(TP1 部分平仓)**等复杂逻辑,这些无法用交易所单一张单实现。
|
||||
|
||||
---
|
||||
|
||||
## 二、当前整体流程(开仓后)
|
||||
|
||||
1. 开仓成交后(或补建/修复后)调用 **`_ensure_exchange_sltp_orders`**:
|
||||
- 先取消该 symbol 上已有的 STOP_MARKET / TAKE_PROFIT_MARKET / TRAILING_STOP_MARKET;
|
||||
- 再按当前 `position_info` 的止损价、止盈价挂**一张止损 + 一张止盈**(交易所条件单)。
|
||||
2. 同时对该 symbol 启动 **WebSocket 监控**(`_monitor_position_price`):
|
||||
- 每次收到价格,用**保证金盈亏比例**判断是否到止损/到第一止盈/到第二止盈/到移动止损;
|
||||
- 到则 **`close_position(..., reason=...)`**(市价平仓)。
|
||||
|
||||
因此:**同一笔仓位既有交易所条件单,又有本机监控**,两条路都能触发平仓,形成双通道。
|
||||
|
||||
---
|
||||
|
||||
## 三、设计意图与分工
|
||||
|
||||
- **交易所条件单**:保证在**本机不在线**时(重启、断网、崩溃)仍有止损/止盈保护。
|
||||
- **本机 WebSocket**:
|
||||
- 实现**移动止损、TP1 部分平仓**等(交易所只挂固定止损/止盈,不会自动“移动”或“分步”);
|
||||
- 在**挂单失败**时作为兜底(见下)。
|
||||
|
||||
---
|
||||
|
||||
## 四、可能的问题与现状
|
||||
|
||||
| 问题 | 说明 | 当前处理 |
|
||||
|------|------|----------|
|
||||
| **重复平仓** | 交易所先触发平仓后,本机再判“到价”可能再发平仓。 | `close_position` 会先查币安是否还有仓位;无仓位则不再下单,仅同步 DB,避免重复市价单。 |
|
||||
| **挂单失败只剩 WS** | 若 STOP_MARKET 挂单失败(如 -2021、网络错误),则**只有** WebSocket 能止损。 | 代码已写:挂单失败时打日志「将依赖 WebSocket 监控」,并再次检查当前价是否已穿止损,若已穿则立即市价平仓;未穿则仅靠 WS,进程若挂则无交易所保护。 |
|
||||
| **价格源不一致** | 交易所条件单多用 **MARK_PRICE**,本机 WS 多用 last/mark。 | 可能有毫秒级差异,一般先触发的一方完成平仓,另一方发现无仓位即不再操作。 |
|
||||
| **移动止损仅本机** | 移动止损(盈利到 X% 上移止损)只能由本机逻辑实现。 | **已改**:在移动止损**激活**或**更新**时,会调用 `_ensure_exchange_sltp_orders`,取消原止损/止盈条件单并按新止损价重挂,使移动止损也有交易所保护。 |
|
||||
|
||||
---
|
||||
|
||||
## 五、小结
|
||||
|
||||
- **是,止损(和止盈)有两种**:
|
||||
1)**币安条件单**:到价由交易所自动止损/止盈;
|
||||
2)**系统 WebSocket 监控**:到价由本机市价平仓。
|
||||
- **两者并存**:同一仓位既有交易所单,又有本机监控,互为备份;本机还负责移动止损、TP1 部分平仓等。
|
||||
- **主要风险点**:交易所止损/止盈**挂单失败**时,只剩 WebSocket,进程挂了就无交易所保护。
|
||||
- **移动止损**:已在「移动止损激活」与「移动止损更新」时同步至交易所(取消原条件单并按新止损价重挂),本机宕机后交易所仍能按最新移动止损价执行。
|
||||
|
|
@ -1719,6 +1719,11 @@ class PositionManager:
|
|||
f"{symbol} 移动止损激活: 止损移至成本价 {entry_price:.4f} "
|
||||
f"(盈利: {pnl_percent_margin:.2f}% of margin)"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
|
||||
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
|
||||
else:
|
||||
# 盈利超过阈值后,止损移至保护利润位(基于保证金)
|
||||
# 如果已经部分止盈,使用剩余仓位计算
|
||||
|
|
@ -1744,6 +1749,11 @@ class PositionManager:
|
|||
f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, "
|
||||
f"剩余数量: {remaining_quantity:.4f})"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
|
||||
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
|
||||
else:
|
||||
# 做空:止损价 = 开仓价 + (剩余盈亏 - 保护金额) / 剩余数量
|
||||
# 注意:对于做空,止损价应该高于开仓价,所以用加法
|
||||
|
|
@ -1759,6 +1769,11 @@ class PositionManager:
|
|||
f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, "
|
||||
f"剩余数量: {remaining_quantity:.4f})"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
|
||||
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
|
||||
else:
|
||||
# 未部分止盈,使用原始仓位计算
|
||||
protect_amount = margin * trailing_protect
|
||||
|
|
@ -1774,6 +1789,11 @@ class PositionManager:
|
|||
f"{symbol} 移动止损更新: {new_stop_loss:.4f} "
|
||||
f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
|
||||
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
|
||||
else:
|
||||
# 做空:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量
|
||||
# 注意:对于做空,止损价应该高于开仓价,所以用加法
|
||||
|
|
@ -1789,6 +1809,11 @@ class PositionManager:
|
|||
f"{symbol} 移动止损更新: {new_stop_loss:.4f} "
|
||||
f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
|
||||
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
|
||||
|
||||
# 检查止损(使用更新后的止损价,基于保证金收益比)
|
||||
# ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行
|
||||
|
|
@ -3507,6 +3532,12 @@ class PositionManager:
|
|||
f"{symbol} [实时监控] 移动止损激活: 止损移至保护利润位 {new_stop_loss:.4f} "
|
||||
f"(盈利: {pnl_percent_margin:.2f}% of margin, 保护: {trailing_protect*100:.1f}% of margin)"
|
||||
)
|
||||
# 同步至交易所:取消原止损单并按新止损价重挂,使移动止损也有交易所保护
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
|
||||
logger.info(f"{symbol} [实时监控] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
|
||||
else:
|
||||
# ⚠️ 优化:如果分步止盈第一目标已触发,移动止损不再更新剩余仓位的止损价
|
||||
# 原因:分步止盈第一目标触发后,剩余50%仓位止损已移至成本价(保本),等待第二目标
|
||||
|
|
@ -3529,6 +3560,11 @@ class PositionManager:
|
|||
f"{symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} "
|
||||
f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
|
||||
logger.info(f"{symbol} [实时监控] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
|
||||
else: # SELL
|
||||
# 做空:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量
|
||||
# 注意:对于做空,止损价应该高于开仓价,所以用加法
|
||||
|
|
@ -3543,6 +3579,11 @@ class PositionManager:
|
|||
f"{symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} "
|
||||
f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
|
||||
)
|
||||
try:
|
||||
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
|
||||
logger.info(f"{symbol} [实时监控] 已同步移动止损至交易所")
|
||||
except Exception as sync_e:
|
||||
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
|
||||
|
||||
# 检查止损(基于保证金收益比)
|
||||
# ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user