This commit is contained in:
薇薇安 2026-02-15 09:44:56 +08:00
parent c4a23be3bf
commit e024bf8ebe
2 changed files with 54 additions and 24 deletions

View File

@ -312,6 +312,9 @@ DEFAULT_TRADING_CONFIG = {
# ===== 系统单标识(用于同步时区分本系统开仓 vs 手动开仓)=====
# 下单时写入 newClientOrderId = SYSTEM_ORDER_ID_PREFIX_时间戳_随机同步/补建时根据订单 clientOrderId 前缀判断是否系统单
'SYSTEM_ORDER_ID_PREFIX': 'SYS',
# ===== 仅币安有仓时的监管策略 =====
# 当 SYNC_CREATE_MANUAL_ENTRY_RECORD=False 时,仍对「仅币安有仓且存在止损/止盈条件单」的持仓接入监控并补建记录(多为系统单)
'SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP': True,
}
def _get_trading_config():

View File

@ -1293,6 +1293,21 @@ class PositionManager:
q = round(q, qty_precision)
return max(0.0, q)
async def _symbol_has_sltp_orders(self, symbol: str) -> bool:
"""该交易对在交易所是否存在止损/止盈类条件单(用于识别「可能为系统单」的持仓)。"""
try:
for o in (await self.client.get_open_orders(symbol)) or []:
t = str(o.get("type") or "").upper()
if t in ("STOP_MARKET", "TAKE_PROFIT_MARKET", "STOP", "TAKE_PROFIT"):
return True
for o in (await self.client.futures_get_open_algo_orders(symbol, algo_type="CONDITIONAL")) or []:
t = str(o.get("orderType") or o.get("type") or "").upper()
if t in ("STOP_MARKET", "TAKE_PROFIT_MARKET", "STOP", "TAKE_PROFIT"):
return True
except Exception:
pass
return False
async def _ensure_exchange_sltp_orders(self, symbol: str, position_info: Dict, current_price: Optional[float] = None) -> None:
"""
在币安侧挂止损/止盈保护单STOP_MARKET + TAKE_PROFIT_MARKET
@ -2844,22 +2859,6 @@ class PositionManager:
sync_recover = config.TRADING_CONFIG.get("SYNC_RECOVER_MISSING_POSITIONS", True)
sync_recover_only_has_sltp = config.TRADING_CONFIG.get("SYNC_RECOVER_ONLY_WHEN_HAS_SLTP", True)
def _order_is_sltp(o, type_key="type"):
t = str(o.get(type_key) or o.get("orderType") or "").upper()
return t in ("STOP_MARKET", "TAKE_PROFIT_MARKET", "STOP", "TAKE_PROFIT")
async def _symbol_has_sltp(sym):
try:
for o in (await self.client.get_open_orders(sym)) or []:
if _order_is_sltp(o, "type"):
return True
for o in (await self.client.futures_get_open_algo_orders(sym, algo_type="CONDITIONAL")) or []:
if _order_is_sltp(o, "orderType"):
return True
except Exception:
pass
return False
if sync_recover:
system_order_prefix = (config.TRADING_CONFIG.get("SYSTEM_ORDER_ID_PREFIX") or "").strip()
if system_order_prefix:
@ -2907,7 +2906,7 @@ class PositionManager:
# 无法获取订单或 cid 为空(历史单/未带前缀)时不视为手动单,继续补建
else:
# 未配置前缀时,用「是否有止损/止盈单」区分
if sync_recover_only_has_sltp and not (await _symbol_has_sltp(symbol)):
if sync_recover_only_has_sltp and not (await self._symbol_has_sltp_orders(symbol)):
logger.debug(f" {symbol} 无止损/止盈单,跳过补建")
continue
if is_clearly_manual:
@ -3146,18 +3145,25 @@ class PositionManager:
logger.info(f"本地持仓记录: {len(active_symbols)} 个 ({', '.join(active_symbols) if active_symbols else ''})")
# 仅为本系统已有记录的持仓启动监控;若未开启「同步创建手动开仓记录」,则不为「仅币安有仓」创建临时记录或监控
# 例外SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP=True 时,对「仅币安有仓且存在止损/止盈单」的视为可监管(多为系统单),补建并监控
only_binance = binance_symbols - active_symbols
if only_binance and not sync_create_manual:
logger.info(f"跳过 {len(only_binance)} 个仅币安持仓的监控SYNC_CREATE_MANUAL_ENTRY_RECORD=False: {', '.join(only_binance)}")
monitor_binance_with_sltp = config.TRADING_CONFIG.get("SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP", True)
if only_binance and not sync_create_manual and not monitor_binance_with_sltp:
logger.info(f"跳过 {len(only_binance)} 个仅币安持仓的监控SYNC_CREATE_MANUAL_ENTRY_RECORD=False 且 SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP=False: {', '.join(only_binance)}")
for position in positions:
symbol = position['symbol']
if symbol not in self._monitor_tasks:
# 若不在 active_positions 且未开启「同步创建手动开仓记录」,不创建临时记录、不为其启动监控
# 若不在 active_positions:要么开启「同步创建手动开仓」则全部接入,要么仅对「有止损/止盈单」的接入(视为系统单)
if symbol not in self.active_positions:
if not sync_create_manual:
has_sltp = await self._symbol_has_sltp_orders(symbol) if monitor_binance_with_sltp else False
should_create = sync_create_manual or (monitor_binance_with_sltp and has_sltp)
if not should_create:
continue
logger.warning(f"{symbol} 在币安有持仓但不在本地记录中,可能是手动开仓,尝试创建记录...")
if sync_create_manual:
logger.warning(f"{symbol} 在币安有持仓但不在本地记录中,可能是手动开仓,尝试创建记录...")
else:
logger.info(f"{symbol} 仅币安有仓且存在止损/止盈单,按系统单接入监控并补建记录")
try:
entry_price = position.get('entryPrice', 0)
position_amt = position['positionAmt']
@ -3195,6 +3201,7 @@ class PositionManager:
take_profit_pct=take_profit_pct_margin
)
entry_reason = 'manual_entry_temp' if sync_create_manual else 'sync_recovered'
position_info = {
'symbol': symbol,
'side': side,
@ -3207,13 +3214,33 @@ class PositionManager:
'takeProfit': take_profit_price,
'initialStopLoss': stop_loss_price,
'leverage': leverage,
'entryReason': 'manual_entry_temp',
'entryReason': entry_reason,
'atr': None,
'maxProfit': 0.0,
'trailingStopActivated': False
}
if not sync_create_manual and DB_AVAILABLE and Trade:
try:
notional = float(entry_price) * quantity
trade_id = Trade.create(
symbol=symbol,
side=side,
quantity=quantity,
entry_price=entry_price,
leverage=leverage,
entry_reason="sync_recovered",
entry_order_id=None,
notional_usdt=notional,
margin_usdt=(notional / leverage) if leverage else None,
account_id=self.account_id,
stop_loss_price=stop_loss_price,
take_profit_price=take_profit_price,
)
position_info['tradeId'] = trade_id
except Exception as db_e:
logger.debug(f"{symbol} 补建 DB 记录失败(不影响监控): {db_e}")
self.active_positions[symbol] = position_info
logger.info(f"{symbol} 已创建临时持仓记录用于监控")
logger.info(f"{symbol} 已创建持仓记录用于监控" + (" (已写入 DB)" if position_info.get("tradeId") else ""))
# 也为“现有持仓”补挂交易所保护单(重启/掉线更安全)
try:
mp = None