a
This commit is contained in:
parent
e4d72057eb
commit
6e922d7921
|
|
@ -174,6 +174,37 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
|||
|
||||
sl_order = await client.client.futures_create_order(**sl_params)
|
||||
tp_order = await client.client.futures_create_order(**tp_params)
|
||||
|
||||
# 再查一次未成交委托,确认是否真的挂上(并用于前端展示/排查)
|
||||
open_orders = []
|
||||
try:
|
||||
oo = await client.client.futures_get_open_orders(symbol=symbol)
|
||||
if isinstance(oo, list):
|
||||
for o in oo:
|
||||
try:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
otype2 = str(o.get("type") or "").upper()
|
||||
if otype2 in {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}:
|
||||
open_orders.append(
|
||||
{
|
||||
"orderId": o.get("orderId"),
|
||||
"type": otype2,
|
||||
"side": o.get("side"),
|
||||
"stopPrice": o.get("stopPrice"),
|
||||
"price": o.get("price"),
|
||||
"workingType": o.get("workingType"),
|
||||
"positionSide": o.get("positionSide"),
|
||||
"closePosition": o.get("closePosition"),
|
||||
"status": o.get("status"),
|
||||
"updateTime": o.get("updateTime"),
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
open_orders = []
|
||||
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"position_side": side,
|
||||
|
|
@ -184,6 +215,8 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
|||
"stop_market": sl_order,
|
||||
"take_profit_market": tp_order,
|
||||
},
|
||||
"open_protection_orders": open_orders,
|
||||
"ui_hint": "在币安【U本位合约】里,这类 STOP/TP 通常显示在【条件单/止盈止损/计划委托】而不一定在普通【当前委托(限价)】列表。",
|
||||
}
|
||||
finally:
|
||||
await client.disconnect()
|
||||
|
|
|
|||
|
|
@ -446,6 +446,44 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.positions-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.positions-header h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sltp-all-btn {
|
||||
padding: 0.5rem 0.9rem;
|
||||
background: #1f7aec;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sltp-all-btn:hover:not(:disabled) {
|
||||
background: #175fb8;
|
||||
}
|
||||
|
||||
.sltp-all-btn:active:not(:disabled) {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.sltp-all-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
background: #95a5a6;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #e74c3c;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const StatsDashboard = () => {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [closingSymbol, setClosingSymbol] = useState(null)
|
||||
const [sltpSymbol, setSltpSymbol] = useState(null)
|
||||
const [sltpAllBusy, setSltpAllBusy] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [tradingConfig, setTradingConfig] = useState(null)
|
||||
|
||||
|
|
@ -116,7 +117,8 @@ const StatsDashboard = () => {
|
|||
const res = await api.ensurePositionSLTP(symbol)
|
||||
const slId = res?.orders?.stop_market?.orderId
|
||||
const tpId = res?.orders?.take_profit_market?.orderId
|
||||
setMessage(`${symbol} 已补挂保护单:SL=${slId || '-'} / TP=${tpId || '-'}`)
|
||||
const cnt = Array.isArray(res?.open_protection_orders) ? res.open_protection_orders.length : 0
|
||||
setMessage(`${symbol} 已补挂保护单:SL=${slId || '-'} / TP=${tpId || '-'}(币安条件单可见,当前检测到保护单 ${cnt} 条)`)
|
||||
await loadDashboard()
|
||||
} catch (error) {
|
||||
setMessage(`补挂失败 ${symbol}: ${error.message || '未知错误'}`)
|
||||
|
|
@ -126,6 +128,26 @@ const StatsDashboard = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleEnsureAllSLTP = async () => {
|
||||
if (!window.confirm('确定要为【所有当前持仓】一键补挂“币安止损/止盈保护单”吗?\n\n说明:会对每个持仓 symbol 自动取消旧的 STOP/TP 保护单并重新挂单(避免重复)。')) {
|
||||
return
|
||||
}
|
||||
setSltpAllBusy(true)
|
||||
setMessage('')
|
||||
try {
|
||||
const res = await api.ensureAllPositionsSLTP(50)
|
||||
const ok = res?.ok ?? 0
|
||||
const failed = res?.failed ?? 0
|
||||
setMessage(`一键补挂完成:成功 ${ok} / 失败 ${failed}(请在币安【条件单/止盈止损】里查看)`)
|
||||
await loadDashboard()
|
||||
} catch (error) {
|
||||
setMessage(`一键补挂失败: ${error.message || '未知错误'}`)
|
||||
} finally {
|
||||
setSltpAllBusy(false)
|
||||
setTimeout(() => setMessage(''), 5000)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSyncPositions = async () => {
|
||||
if (!window.confirm('确定要同步持仓状态吗?这将检查币安实际持仓并更新数据库状态。')) {
|
||||
return
|
||||
|
|
@ -337,7 +359,17 @@ const StatsDashboard = () => {
|
|||
)}
|
||||
|
||||
<div className="dashboard-card">
|
||||
<h3>当前持仓</h3>
|
||||
<div className="positions-header">
|
||||
<h3>当前持仓</h3>
|
||||
<button
|
||||
className="sltp-all-btn"
|
||||
onClick={handleEnsureAllSLTP}
|
||||
disabled={sltpAllBusy || openTrades.length === 0}
|
||||
title="为所有持仓在币安侧补挂 STOP_MARKET + TAKE_PROFIT_MARKET 保护单(closePosition)"
|
||||
>
|
||||
{sltpAllBusy ? '一键补挂中...' : '一键补挂止盈止损'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="entry-type-summary">
|
||||
<span className="entry-type-badge limit">限价入场: {entryTypeCounts.limit}</span>
|
||||
<span className="entry-type-badge market">市价入场: {entryTypeCounts.market}</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user