a
This commit is contained in:
parent
321ff599f6
commit
e725cb19fa
|
|
@ -21,6 +21,21 @@ const StatsDashboard = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fmtPrice = (v) => {
|
||||||
|
const n = Number(v)
|
||||||
|
if (!isFinite(n)) return '-'
|
||||||
|
const a = Math.abs(n)
|
||||||
|
// 低价币需要更高精度,否则“止损价=入场价”只是显示被四舍五入了
|
||||||
|
const dp =
|
||||||
|
a >= 1000 ? 2 :
|
||||||
|
a >= 100 ? 3 :
|
||||||
|
a >= 1 ? 4 :
|
||||||
|
a >= 0.01 ? 6 :
|
||||||
|
a >= 0.0001 ? 8 :
|
||||||
|
10
|
||||||
|
return n.toFixed(dp).replace(/\.?0+$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDashboard()
|
loadDashboard()
|
||||||
loadTradingConfig()
|
loadTradingConfig()
|
||||||
|
|
@ -483,9 +498,9 @@ const StatsDashboard = () => {
|
||||||
<div>名义: {entryValue >= 0.01 ? entryValue.toFixed(2) : entryValue.toFixed(4)} USDT</div>
|
<div>名义: {entryValue >= 0.01 ? entryValue.toFixed(2) : entryValue.toFixed(4)} USDT</div>
|
||||||
<div>保证金: {margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</div>
|
<div>保证金: {margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</div>
|
||||||
|
|
||||||
<div>入场价: {entryPrice.toFixed(4)}</div>
|
<div>入场价: {fmtPrice(entryPrice)}</div>
|
||||||
{trade.mark_price && (
|
{trade.mark_price && (
|
||||||
<div>标记价: {markPrice.toFixed(4)}</div>
|
<div>标记价: {fmtPrice(markPrice)}</div>
|
||||||
)}
|
)}
|
||||||
{trade.leverage && (
|
{trade.leverage && (
|
||||||
<div>杠杆: {trade.leverage}x</div>
|
<div>杠杆: {trade.leverage}x</div>
|
||||||
|
|
@ -510,22 +525,22 @@ const StatsDashboard = () => {
|
||||||
<div className="stop-loss-info">
|
<div className="stop-loss-info">
|
||||||
止损: <span className="negative">-{stopLossPercent.toFixed(2)}%</span>
|
止损: <span className="negative">-{stopLossPercent.toFixed(2)}%</span>
|
||||||
<span className="stop-note">(of margin)</span>
|
<span className="stop-note">(of margin)</span>
|
||||||
<span className="stop-price">(价: {stopLossPrice.toFixed(4)})</span>
|
<span className="stop-price">(价: {fmtPrice(stopLossPrice)})</span>
|
||||||
<span className="stop-amount">(金额: -{stopLossAmount >= 0.01 ? stopLossAmount.toFixed(2) : stopLossAmount.toFixed(4)} USDT)</span>
|
<span className="stop-amount">(金额: -{stopLossAmount >= 0.01 ? stopLossAmount.toFixed(2) : stopLossAmount.toFixed(4)} USDT)</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="take-profit-info">
|
<div className="take-profit-info">
|
||||||
止盈: <span className="positive">+{takeProfitPercent.toFixed(2)}%</span>
|
止盈: <span className="positive">+{takeProfitPercent.toFixed(2)}%</span>
|
||||||
<span className="take-note">(of margin)</span>
|
<span className="take-note">(of margin)</span>
|
||||||
<span className="take-price">(价: {takeProfitPrice.toFixed(4)})</span>
|
<span className="take-price">(价: {fmtPrice(takeProfitPrice)})</span>
|
||||||
<span className="take-amount">(金额: +{takeProfitAmount >= 0.01 ? takeProfitAmount.toFixed(2) : takeProfitAmount.toFixed(4)} USDT)</span>
|
<span className="take-amount">(金额: +{takeProfitAmount >= 0.01 ? takeProfitAmount.toFixed(2) : takeProfitAmount.toFixed(4)} USDT)</span>
|
||||||
</div>
|
</div>
|
||||||
{(trade.take_profit_1 || trade.take_profit_2) && (
|
{(trade.take_profit_1 || trade.take_profit_2) && (
|
||||||
<div style={{ marginTop: '6px', fontSize: '12px', color: '#666' }}>
|
<div style={{ marginTop: '6px', fontSize: '12px', color: '#666' }}>
|
||||||
{trade.take_profit_1 && (
|
{trade.take_profit_1 && (
|
||||||
<span style={{ marginRight: '10px' }}>TP1: {parseFloat(trade.take_profit_1).toFixed(4)}</span>
|
<span style={{ marginRight: '10px' }}>TP1: {fmtPrice(parseFloat(trade.take_profit_1))}</span>
|
||||||
)}
|
)}
|
||||||
{trade.take_profit_2 && (
|
{trade.take_profit_2 && (
|
||||||
<span>TP2: {parseFloat(trade.take_profit_2).toFixed(4)}</span>
|
<span>TP2: {fmtPrice(parseFloat(trade.take_profit_2))}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -858,6 +858,51 @@ class BinanceClient:
|
||||||
except Exception:
|
except Exception:
|
||||||
return BinanceClient._format_decimal_str(p)
|
return BinanceClient._format_decimal_str(p)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_price_str_with_rounding(price: float, symbol_info: Optional[Dict], rounding_mode: str) -> str:
|
||||||
|
"""
|
||||||
|
通用价格格式化(tickSize/pricePrecision 对齐),并允许显式指定 ROUND_UP / ROUND_DOWN。
|
||||||
|
rounding_mode: "UP" 或 "DOWN"
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from decimal import Decimal, ROUND_DOWN, ROUND_UP
|
||||||
|
except Exception:
|
||||||
|
return str(round(float(price), 8))
|
||||||
|
|
||||||
|
tick = 0.0
|
||||||
|
pp = 8
|
||||||
|
try:
|
||||||
|
tick = float(symbol_info.get("tickSize", 0) or 0) if symbol_info else 0.0
|
||||||
|
except Exception:
|
||||||
|
tick = 0.0
|
||||||
|
try:
|
||||||
|
pp = int(symbol_info.get("pricePrecision", 8) or 8) if symbol_info else 8
|
||||||
|
except Exception:
|
||||||
|
pp = 8
|
||||||
|
|
||||||
|
p = Decimal(str(price))
|
||||||
|
rounding = ROUND_UP if str(rounding_mode).upper() == "UP" else ROUND_DOWN
|
||||||
|
|
||||||
|
# tickSize 优先
|
||||||
|
try:
|
||||||
|
t = Decimal(str(tick))
|
||||||
|
if t > 0:
|
||||||
|
q = p / t
|
||||||
|
q2 = q.to_integral_value(rounding=rounding)
|
||||||
|
p2 = q2 * t
|
||||||
|
return BinanceClient._format_decimal_str(p2)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# pricePrecision 兜底
|
||||||
|
try:
|
||||||
|
if pp <= 0:
|
||||||
|
return BinanceClient._format_decimal_str(p.to_integral_value(rounding=rounding))
|
||||||
|
p2 = p.quantize(Decimal(f"1e-{pp}"), rounding=rounding)
|
||||||
|
return BinanceClient._format_decimal_str(p2)
|
||||||
|
except Exception:
|
||||||
|
return BinanceClient._format_decimal_str(p)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _adjust_price_to_tick(price: float, tick_size: float, side: str) -> float:
|
def _adjust_price_to_tick(price: float, tick_size: float, side: str) -> float:
|
||||||
"""
|
"""
|
||||||
|
|
@ -1252,6 +1297,165 @@ class BinanceClient:
|
||||||
except BinanceAPIException as e:
|
except BinanceAPIException as e:
|
||||||
logger.error(f"取消订单失败: {e}")
|
logger.error(f"取消订单失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def get_open_orders(self, symbol: str) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
获取某交易对的未成交委托(用于防止重复挂保护单)。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
orders = await self.client.futures_get_open_orders(symbol=symbol)
|
||||||
|
return orders if isinstance(orders, list) else []
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"{symbol} 获取未成交委托失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def cancel_open_orders_by_types(self, symbol: str, types: set[str]) -> int:
|
||||||
|
"""
|
||||||
|
取消指定类型的未成交委托(只取消保护单相关类型,避免重复下单)。
|
||||||
|
返回取消数量。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
want = {str(t).upper() for t in (types or set())}
|
||||||
|
if not want:
|
||||||
|
return 0
|
||||||
|
orders = await self.get_open_orders(symbol)
|
||||||
|
cancelled = 0
|
||||||
|
for o in orders:
|
||||||
|
try:
|
||||||
|
if not isinstance(o, dict):
|
||||||
|
continue
|
||||||
|
otype = str(o.get("type") or "").upper()
|
||||||
|
oid = o.get("orderId")
|
||||||
|
if otype in want and oid:
|
||||||
|
ok = await self.cancel_order(symbol, int(oid))
|
||||||
|
if ok:
|
||||||
|
cancelled += 1
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return cancelled
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def place_trigger_close_position_order(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
position_direction: str,
|
||||||
|
trigger_type: str,
|
||||||
|
stop_price: float,
|
||||||
|
current_price: Optional[float] = None,
|
||||||
|
working_type: str = "MARK_PRICE",
|
||||||
|
) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
在币安侧挂“保护单”,用于止损/止盈:
|
||||||
|
- STOP_MARKET / TAKE_PROFIT_MARKET
|
||||||
|
- closePosition=True(自动平掉该 symbol 的当前仓位)
|
||||||
|
|
||||||
|
注意:这类单子不会在本地生成 exit_reason;触发后我们靠“持仓同步/订单同步”去回写数据库。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
symbol_info = await self.get_symbol_info(symbol)
|
||||||
|
dual = None
|
||||||
|
try:
|
||||||
|
dual = await self._get_dual_side_position()
|
||||||
|
except Exception:
|
||||||
|
dual = None
|
||||||
|
|
||||||
|
pd = (position_direction or "").upper()
|
||||||
|
if pd not in {"BUY", "SELL"}:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ttype = str(trigger_type or "").upper()
|
||||||
|
if ttype not in {"STOP_MARKET", "TAKE_PROFIT_MARKET"}:
|
||||||
|
return None
|
||||||
|
|
||||||
|
close_side = "SELL" if pd == "BUY" else "BUY"
|
||||||
|
|
||||||
|
# stopPrice 的“避免立即触发”修正(按 MARK_PRICE)
|
||||||
|
cp = None
|
||||||
|
try:
|
||||||
|
cp = float(current_price) if current_price is not None else None
|
||||||
|
except Exception:
|
||||||
|
cp = None
|
||||||
|
|
||||||
|
tick = 0.0
|
||||||
|
pp = 8
|
||||||
|
try:
|
||||||
|
tick = float(symbol_info.get("tickSize", 0) or 0) if symbol_info else 0.0
|
||||||
|
except Exception:
|
||||||
|
tick = 0.0
|
||||||
|
try:
|
||||||
|
pp = int(symbol_info.get("pricePrecision", 8) or 8) if symbol_info else 8
|
||||||
|
except Exception:
|
||||||
|
pp = 8
|
||||||
|
|
||||||
|
min_step = tick if tick and tick > 0 else (10 ** (-pp) if pp and pp > 0 else 1e-8)
|
||||||
|
|
||||||
|
sp = float(stop_price or 0)
|
||||||
|
if sp <= 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 触发方向约束:
|
||||||
|
# - long 止损:价格 <= stopPrice(stopPrice 应 < current)
|
||||||
|
# - short 止损:价格 >= stopPrice(stopPrice 应 > current)
|
||||||
|
# - long 止盈:价格 >= stopPrice(stopPrice 应 > current)
|
||||||
|
# - short 止盈:价格 <= stopPrice(stopPrice 应 < current)
|
||||||
|
if cp and cp > 0:
|
||||||
|
if ttype == "STOP_MARKET":
|
||||||
|
if pd == "BUY" and sp >= cp:
|
||||||
|
sp = max(0.0, cp - min_step)
|
||||||
|
if pd == "SELL" and sp <= cp:
|
||||||
|
sp = cp + min_step
|
||||||
|
if ttype == "TAKE_PROFIT_MARKET":
|
||||||
|
if pd == "BUY" and sp <= cp:
|
||||||
|
sp = cp + min_step
|
||||||
|
if pd == "SELL" and sp >= cp:
|
||||||
|
sp = max(0.0, cp - min_step)
|
||||||
|
|
||||||
|
# rounding 规则(提高命中概率,避免“显示等于入场价”的误差带来立即触发/永不触发):
|
||||||
|
# 止损:long 用 UP(更靠近当前价),short 用 DOWN
|
||||||
|
# 止盈:long 用 DOWN(更靠近当前价),short 用 UP
|
||||||
|
rounding_mode = "DOWN"
|
||||||
|
if ttype == "STOP_MARKET":
|
||||||
|
rounding_mode = "UP" if pd == "BUY" else "DOWN"
|
||||||
|
else: # TAKE_PROFIT_MARKET
|
||||||
|
rounding_mode = "DOWN" if pd == "BUY" else "UP"
|
||||||
|
|
||||||
|
stop_price_str = self._format_price_str_with_rounding(sp, symbol_info, rounding_mode)
|
||||||
|
|
||||||
|
params: Dict[str, Any] = {
|
||||||
|
"symbol": symbol,
|
||||||
|
"side": close_side,
|
||||||
|
"type": ttype,
|
||||||
|
"stopPrice": stop_price_str,
|
||||||
|
"closePosition": True,
|
||||||
|
"workingType": working_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 对冲模式:必须指定 positionSide
|
||||||
|
if dual is True:
|
||||||
|
params["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self.client.futures_create_order(**params)
|
||||||
|
except BinanceAPIException as e:
|
||||||
|
code = getattr(e, "code", None)
|
||||||
|
# -4061: positionSide 不匹配 -> 兜底切换一次
|
||||||
|
if code == -4061:
|
||||||
|
retry = dict(params)
|
||||||
|
if "positionSide" in retry:
|
||||||
|
retry.pop("positionSide", None)
|
||||||
|
else:
|
||||||
|
retry["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
||||||
|
return await self.client.futures_create_order(**retry)
|
||||||
|
raise
|
||||||
|
|
||||||
|
except BinanceAPIException as e:
|
||||||
|
# 常见:Order would immediately trigger(止损/止盈价不在正确一侧)
|
||||||
|
logger.warning(f"{symbol} 挂保护单失败({trigger_type}): {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{symbol} 挂保护单失败({trigger_type}): {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
async def set_leverage(self, symbol: str, leverage: int = 10) -> bool:
|
async def set_leverage(self, symbol: str, leverage: int = 10) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,16 @@ class PositionManager:
|
||||||
f"数量={float(binance_position.get('positionAmt', 0)):.4f}, "
|
f"数量={float(binance_position.get('positionAmt', 0)):.4f}, "
|
||||||
f"入场价={float(binance_position.get('entryPrice', 0)):.4f}"
|
f"入场价={float(binance_position.get('entryPrice', 0)):.4f}"
|
||||||
)
|
)
|
||||||
|
# 在币安侧挂“止损/止盈保护单”,避免仅依赖本地监控(服务重启/网络波动时更安全)
|
||||||
|
try:
|
||||||
|
current_mark = None
|
||||||
|
try:
|
||||||
|
current_mark = float(binance_position.get("markPrice", 0) or 0) or None
|
||||||
|
except Exception:
|
||||||
|
current_mark = None
|
||||||
|
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_mark)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{symbol} 挂币安止盈止损失败(不影响持仓监控): {e}")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{symbol} [开仓验证] ⚠️ 币安账户中没有持仓,可能订单未成交或被立即平仓"
|
f"{symbol} [开仓验证] ⚠️ 币安账户中没有持仓,可能订单未成交或被立即平仓"
|
||||||
|
|
@ -637,6 +647,22 @@ class PositionManager:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logger.info(f"{symbol} [平仓] 开始平仓操作 (原因: {reason})")
|
logger.info(f"{symbol} [平仓] 开始平仓操作 (原因: {reason})")
|
||||||
|
|
||||||
|
# 先取消币安侧的保护单(避免平仓后残留委托导致反向开仓/误触发)
|
||||||
|
try:
|
||||||
|
info0 = self.active_positions.get(symbol) if hasattr(self, "active_positions") else None
|
||||||
|
if info0 and isinstance(info0, dict):
|
||||||
|
for k in ("exchangeSlOrderId", "exchangeTpOrderId"):
|
||||||
|
oid = info0.get(k)
|
||||||
|
if oid:
|
||||||
|
try:
|
||||||
|
await self.client.cancel_order(symbol, int(oid))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
info0.pop("exchangeSlOrderId", None)
|
||||||
|
info0.pop("exchangeTpOrderId", None)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# 获取当前持仓
|
# 获取当前持仓
|
||||||
positions = await self.client.get_open_positions()
|
positions = await self.client.get_open_positions()
|
||||||
|
|
@ -1026,6 +1052,82 @@ class PositionManager:
|
||||||
q = round(q, qty_precision)
|
q = round(q, qty_precision)
|
||||||
q = round(q, qty_precision)
|
q = round(q, qty_precision)
|
||||||
return max(0.0, q)
|
return max(0.0, q)
|
||||||
|
|
||||||
|
async def _ensure_exchange_sltp_orders(self, symbol: str, position_info: Dict, current_price: Optional[float] = None) -> None:
|
||||||
|
"""
|
||||||
|
在币安侧挂止损/止盈保护单(STOP_MARKET + TAKE_PROFIT_MARKET)。
|
||||||
|
目的:
|
||||||
|
- 服务重启/网络波动时仍有交易所级别保护
|
||||||
|
- 用户在币安界面能看到止损/止盈委托
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
enabled = bool(config.TRADING_CONFIG.get("EXCHANGE_SLTP_ENABLED", True))
|
||||||
|
except Exception:
|
||||||
|
enabled = True
|
||||||
|
if not enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not position_info or not isinstance(position_info, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
side = (position_info.get("side") or "").upper()
|
||||||
|
if side not in {"BUY", "SELL"}:
|
||||||
|
return
|
||||||
|
|
||||||
|
stop_loss = position_info.get("stopLoss")
|
||||||
|
take_profit = position_info.get("takeProfit2") or position_info.get("takeProfit")
|
||||||
|
try:
|
||||||
|
stop_loss = float(stop_loss) if stop_loss is not None else None
|
||||||
|
except Exception:
|
||||||
|
stop_loss = None
|
||||||
|
try:
|
||||||
|
take_profit = float(take_profit) if take_profit is not None else None
|
||||||
|
except Exception:
|
||||||
|
take_profit = None
|
||||||
|
|
||||||
|
if not stop_loss or not take_profit:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 防重复:先取消旧的保护单(仅取消特定类型,避免误伤普通挂单)
|
||||||
|
try:
|
||||||
|
await self.client.cancel_open_orders_by_types(
|
||||||
|
symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sl_order = await self.client.place_trigger_close_position_order(
|
||||||
|
symbol=symbol,
|
||||||
|
position_direction=side,
|
||||||
|
trigger_type="STOP_MARKET",
|
||||||
|
stop_price=stop_loss,
|
||||||
|
current_price=current_price,
|
||||||
|
working_type="MARK_PRICE",
|
||||||
|
)
|
||||||
|
tp_order = await self.client.place_trigger_close_position_order(
|
||||||
|
symbol=symbol,
|
||||||
|
position_direction=side,
|
||||||
|
trigger_type="TAKE_PROFIT_MARKET",
|
||||||
|
stop_price=take_profit,
|
||||||
|
current_price=current_price,
|
||||||
|
working_type="MARK_PRICE",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
position_info["exchangeSlOrderId"] = sl_order.get("orderId") if isinstance(sl_order, dict) else None
|
||||||
|
except Exception:
|
||||||
|
position_info["exchangeSlOrderId"] = None
|
||||||
|
try:
|
||||||
|
position_info["exchangeTpOrderId"] = tp_order.get("orderId") if isinstance(tp_order, dict) else None
|
||||||
|
except Exception:
|
||||||
|
position_info["exchangeTpOrderId"] = None
|
||||||
|
|
||||||
|
if position_info.get("exchangeSlOrderId") or position_info.get("exchangeTpOrderId"):
|
||||||
|
logger.info(
|
||||||
|
f"{symbol} 已挂币安保护单: "
|
||||||
|
f"SL={position_info.get('exchangeSlOrderId') or '-'} "
|
||||||
|
f"TP={position_info.get('exchangeTpOrderId') or '-'}"
|
||||||
|
)
|
||||||
|
|
||||||
async def check_stop_loss_take_profit(self) -> List[str]:
|
async def check_stop_loss_take_profit(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -2019,6 +2121,16 @@ class PositionManager:
|
||||||
}
|
}
|
||||||
self.active_positions[symbol] = position_info
|
self.active_positions[symbol] = position_info
|
||||||
logger.info(f"{symbol} 已创建临时持仓记录用于监控")
|
logger.info(f"{symbol} 已创建临时持仓记录用于监控")
|
||||||
|
# 也为“现有持仓”补挂交易所保护单(重启/掉线更安全)
|
||||||
|
try:
|
||||||
|
mp = None
|
||||||
|
try:
|
||||||
|
mp = float(position.get("markPrice", 0) or 0) or None
|
||||||
|
except Exception:
|
||||||
|
mp = None
|
||||||
|
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=mp or current_price)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{symbol} 补挂币安止盈止损失败(不影响监控): {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{symbol} 创建临时持仓记录失败: {e}")
|
logger.error(f"{symbol} 创建临时持仓记录失败: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user