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)
|
sl_order = await client.client.futures_create_order(**sl_params)
|
||||||
tp_order = await client.client.futures_create_order(**tp_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 {
|
return {
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"position_side": side,
|
"position_side": side,
|
||||||
|
|
@ -184,6 +215,8 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
||||||
"stop_market": sl_order,
|
"stop_market": sl_order,
|
||||||
"take_profit_market": tp_order,
|
"take_profit_market": tp_order,
|
||||||
},
|
},
|
||||||
|
"open_protection_orders": open_orders,
|
||||||
|
"ui_hint": "在币安【U本位合约】里,这类 STOP/TP 通常显示在【条件单/止盈止损/计划委托】而不一定在普通【当前委托(限价)】列表。",
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,44 @@
|
||||||
font-style: normal;
|
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 {
|
.close-btn {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
background: #e74c3c;
|
background: #e74c3c;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const StatsDashboard = () => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [closingSymbol, setClosingSymbol] = useState(null)
|
const [closingSymbol, setClosingSymbol] = useState(null)
|
||||||
const [sltpSymbol, setSltpSymbol] = useState(null)
|
const [sltpSymbol, setSltpSymbol] = useState(null)
|
||||||
|
const [sltpAllBusy, setSltpAllBusy] = useState(false)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
const [tradingConfig, setTradingConfig] = useState(null)
|
const [tradingConfig, setTradingConfig] = useState(null)
|
||||||
|
|
||||||
|
|
@ -116,7 +117,8 @@ const StatsDashboard = () => {
|
||||||
const res = await api.ensurePositionSLTP(symbol)
|
const res = await api.ensurePositionSLTP(symbol)
|
||||||
const slId = res?.orders?.stop_market?.orderId
|
const slId = res?.orders?.stop_market?.orderId
|
||||||
const tpId = res?.orders?.take_profit_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()
|
await loadDashboard()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage(`补挂失败 ${symbol}: ${error.message || '未知错误'}`)
|
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 () => {
|
const handleSyncPositions = async () => {
|
||||||
if (!window.confirm('确定要同步持仓状态吗?这将检查币安实际持仓并更新数据库状态。')) {
|
if (!window.confirm('确定要同步持仓状态吗?这将检查币安实际持仓并更新数据库状态。')) {
|
||||||
return
|
return
|
||||||
|
|
@ -337,7 +359,17 @@ const StatsDashboard = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="dashboard-card">
|
<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">
|
<div className="entry-type-summary">
|
||||||
<span className="entry-type-badge limit">限价入场: {entryTypeCounts.limit}</span>
|
<span className="entry-type-badge limit">限价入场: {entryTypeCounts.limit}</span>
|
||||||
<span className="entry-type-badge market">市价入场: {entryTypeCounts.market}</span>
|
<span className="entry-type-badge market">市价入场: {entryTypeCounts.market}</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user