a
This commit is contained in:
parent
cb7b091280
commit
1fcd692368
|
|
@ -286,8 +286,8 @@ async def ensure_all_positions_sltp(
|
||||||
批量补挂当前所有持仓的止盈止损保护单。
|
批量补挂当前所有持仓的止盈止损保护单。
|
||||||
"""
|
"""
|
||||||
# 先拿当前持仓symbol列表
|
# 先拿当前持仓symbol列表
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
if not api_key or not api_secret:
|
if (not api_key or not api_secret) and status == "active":
|
||||||
logger.error(f"[account_id={account_id}] API密钥未配置")
|
logger.error(f"[account_id={account_id}] API密钥未配置")
|
||||||
raise HTTPException(status_code=400, detail=f"API密钥未配置(account_id={account_id})")
|
raise HTTPException(status_code=400, detail=f"API密钥未配置(account_id={account_id})")
|
||||||
|
|
||||||
|
|
@ -351,7 +351,7 @@ async def get_realtime_account_data(account_id: int = 1):
|
||||||
try:
|
try:
|
||||||
# 从 accounts 表读取账号私有API密钥
|
# 从 accounts 表读取账号私有API密钥
|
||||||
logger.info(f"步骤1: 从accounts读取API配置... (account_id={account_id})")
|
logger.info(f"步骤1: 从accounts读取API配置... (account_id={account_id})")
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
logger.info(f" - API密钥存在: {bool(api_key)}")
|
logger.info(f" - API密钥存在: {bool(api_key)}")
|
||||||
if api_key:
|
if api_key:
|
||||||
|
|
@ -565,7 +565,7 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)):
|
||||||
"""获取实时持仓数据"""
|
"""获取实时持仓数据"""
|
||||||
client = None
|
client = None
|
||||||
try:
|
try:
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
logger.info(f"尝试获取实时持仓数据 (testnet={use_testnet}, account_id={account_id})")
|
logger.info(f"尝试获取实时持仓数据 (testnet={use_testnet}, account_id={account_id})")
|
||||||
|
|
||||||
|
|
@ -738,9 +738,9 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
|
||||||
logger.info(f"收到平仓请求: {symbol}")
|
logger.info(f"收到平仓请求: {symbol}")
|
||||||
logger.info(f"=" * 60)
|
logger.info(f"=" * 60)
|
||||||
|
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if (not api_key or not api_secret) and status == "active":
|
||||||
error_msg = f"API密钥未配置(account_id={account_id})"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(f"[account_id={account_id}] {error_msg}")
|
logger.warning(f"[account_id={account_id}] {error_msg}")
|
||||||
raise HTTPException(status_code=400, detail=error_msg)
|
raise HTTPException(status_code=400, detail=error_msg)
|
||||||
|
|
@ -1041,6 +1041,169 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
|
||||||
raise HTTPException(status_code=500, detail=error_msg)
|
raise HTTPException(status_code=500, detail=error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/positions/close-all")
|
||||||
|
async def close_all_positions(account_id: int = Depends(get_account_id)):
|
||||||
|
"""一键全平:平仓所有持仓"""
|
||||||
|
try:
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("收到一键全平请求")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
|
if (not api_key or not api_secret) and status == "active":
|
||||||
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
|
logger.warning(f"[account_id={account_id}] {error_msg}")
|
||||||
|
raise HTTPException(status_code=400, detail=error_msg)
|
||||||
|
|
||||||
|
# 导入必要的模块
|
||||||
|
try:
|
||||||
|
from binance_client import BinanceClient
|
||||||
|
logger.info("✓ 成功导入交易系统模块")
|
||||||
|
except ImportError as import_error:
|
||||||
|
logger.warning(f"首次导入失败: {import_error},尝试从trading_system路径导入")
|
||||||
|
trading_system_path = project_root / 'trading_system'
|
||||||
|
sys.path.insert(0, str(trading_system_path))
|
||||||
|
from binance_client import BinanceClient
|
||||||
|
logger.info("✓ 从trading_system路径导入成功")
|
||||||
|
|
||||||
|
# 导入数据库模型
|
||||||
|
from database.models import Trade
|
||||||
|
|
||||||
|
# 创建客户端
|
||||||
|
logger.info(f"创建BinanceClient (testnet={use_testnet})...")
|
||||||
|
client = BinanceClient(
|
||||||
|
api_key=api_key,
|
||||||
|
api_secret=api_secret,
|
||||||
|
testnet=use_testnet
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("连接币安API...")
|
||||||
|
await client.connect()
|
||||||
|
logger.info("✓ 币安API连接成功")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取所有持仓
|
||||||
|
positions = await client.get_open_positions()
|
||||||
|
if not positions:
|
||||||
|
logger.info("当前没有持仓")
|
||||||
|
return {
|
||||||
|
"message": "当前没有持仓",
|
||||||
|
"closed": 0,
|
||||||
|
"failed": 0,
|
||||||
|
"results": []
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"发现 {len(positions)} 个持仓,开始逐一平仓...")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
closed_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
for position in positions:
|
||||||
|
symbol = position.get('symbol')
|
||||||
|
position_amt = float(position.get('positionAmt', 0))
|
||||||
|
|
||||||
|
if abs(position_amt) <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"开始平仓 {symbol} (数量: {position_amt})...")
|
||||||
|
|
||||||
|
# 确定平仓方向
|
||||||
|
side = 'SELL' if position_amt > 0 else 'BUY'
|
||||||
|
|
||||||
|
# 使用市价单平仓
|
||||||
|
order = await client.place_order(
|
||||||
|
symbol=symbol,
|
||||||
|
side=side,
|
||||||
|
order_type='MARKET',
|
||||||
|
quantity=abs(position_amt),
|
||||||
|
reduce_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if order and order.get('orderId'):
|
||||||
|
logger.info(f"✓ {symbol} 平仓订单已提交: {order.get('orderId')}")
|
||||||
|
|
||||||
|
# 获取成交价格
|
||||||
|
exit_price = float(order.get('avgPrice', 0)) or float(order.get('price', 0))
|
||||||
|
if not exit_price:
|
||||||
|
# 如果订单中没有价格,获取当前价格
|
||||||
|
ticker = await client.get_ticker_24h(symbol)
|
||||||
|
exit_price = float(ticker['price']) if ticker else 0
|
||||||
|
|
||||||
|
# 更新数据库记录
|
||||||
|
open_trades = Trade.get_by_symbol(symbol, status='open')
|
||||||
|
for trade in open_trades:
|
||||||
|
entry_price = float(trade['entry_price'])
|
||||||
|
quantity = float(trade['quantity'])
|
||||||
|
|
||||||
|
if trade['side'] == 'BUY':
|
||||||
|
pnl = (exit_price - entry_price) * quantity
|
||||||
|
pnl_percent = ((exit_price - entry_price) / entry_price) * 100
|
||||||
|
else:
|
||||||
|
pnl = (entry_price - exit_price) * quantity
|
||||||
|
pnl_percent = ((entry_price - exit_price) / entry_price) * 100
|
||||||
|
|
||||||
|
Trade.update_exit(
|
||||||
|
trade_id=trade['id'],
|
||||||
|
exit_price=exit_price,
|
||||||
|
exit_reason='manual',
|
||||||
|
pnl=pnl,
|
||||||
|
pnl_percent=pnl_percent,
|
||||||
|
exit_order_id=order.get('orderId')
|
||||||
|
)
|
||||||
|
logger.info(f"✓ 已更新数据库记录 trade_id={trade['id']} (盈亏: {pnl:.2f} USDT)")
|
||||||
|
|
||||||
|
closed_count += 1
|
||||||
|
results.append({
|
||||||
|
"symbol": symbol,
|
||||||
|
"status": "success",
|
||||||
|
"order_id": order.get('orderId'),
|
||||||
|
"message": f"{symbol} 平仓成功"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠ {symbol} 平仓订单提交失败")
|
||||||
|
failed_count += 1
|
||||||
|
results.append({
|
||||||
|
"symbol": symbol,
|
||||||
|
"status": "failed",
|
||||||
|
"message": f"{symbol} 平仓失败: 订单未提交"
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ {symbol} 平仓失败: {e}")
|
||||||
|
failed_count += 1
|
||||||
|
results.append({
|
||||||
|
"symbol": symbol,
|
||||||
|
"status": "failed",
|
||||||
|
"message": f"{symbol} 平仓失败: {str(e)}"
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info(f"一键全平完成: 成功 {closed_count} / 失败 {failed_count}")
|
||||||
|
return {
|
||||||
|
"message": f"一键全平完成: 成功 {closed_count} / 失败 {failed_count}",
|
||||||
|
"closed": closed_count,
|
||||||
|
"failed": failed_count,
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
finally:
|
||||||
|
logger.info("断开币安API连接...")
|
||||||
|
await client.disconnect()
|
||||||
|
logger.info("✓ 已断开连接")
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"一键全平失败: {str(e)}"
|
||||||
|
logger.error("=" * 60)
|
||||||
|
logger.error(f"一键全平操作异常: {error_msg}")
|
||||||
|
logger.error(f"错误类型: {type(e).__name__}")
|
||||||
|
logger.error("=" * 60, exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=error_msg)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/positions/{symbol}/open")
|
@router.post("/positions/{symbol}/open")
|
||||||
async def open_position_from_recommendation(
|
async def open_position_from_recommendation(
|
||||||
symbol: str,
|
symbol: str,
|
||||||
|
|
@ -1068,9 +1231,9 @@ async def open_position_from_recommendation(
|
||||||
if entry_price <= 0 or stop_loss_price <= 0:
|
if entry_price <= 0 or stop_loss_price <= 0:
|
||||||
raise HTTPException(status_code=400, detail="入场价和止损价必须大于0")
|
raise HTTPException(status_code=400, detail="入场价和止损价必须大于0")
|
||||||
|
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if (not api_key or not api_secret) and status == "active":
|
||||||
error_msg = f"API密钥未配置(account_id={account_id})"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(f"[account_id={account_id}] {error_msg}")
|
logger.warning(f"[account_id={account_id}] {error_msg}")
|
||||||
raise HTTPException(status_code=400, detail=error_msg)
|
raise HTTPException(status_code=400, detail=error_msg)
|
||||||
|
|
@ -1247,9 +1410,9 @@ async def sync_positions(account_id: int = Depends(get_account_id)):
|
||||||
logger.info("收到持仓状态同步请求")
|
logger.info("收到持仓状态同步请求")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if (not api_key or not api_secret) and status == "active":
|
||||||
error_msg = f"API密钥未配置(account_id={account_id})"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(f"[account_id={account_id}] {error_msg}")
|
logger.warning(f"[account_id={account_id}] {error_msg}")
|
||||||
raise HTTPException(status_code=400, detail=error_msg)
|
raise HTTPException(status_code=400, detail=error_msg)
|
||||||
|
|
|
||||||
|
|
@ -99,12 +99,12 @@ async def list_accounts(user: Dict[str, Any] = Depends(get_current_user)) -> Lis
|
||||||
if not r:
|
if not r:
|
||||||
continue
|
continue
|
||||||
# 普通用户:不返回密钥明文,但返回“是否已配置”的状态,方便前端提示
|
# 普通用户:不返回密钥明文,但返回“是否已配置”的状态,方便前端提示
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(int(aid))
|
api_key, api_secret, use_testnet, status = Account.get_credentials(int(aid))
|
||||||
out.append(
|
out.append(
|
||||||
{
|
{
|
||||||
"id": int(aid),
|
"id": int(aid),
|
||||||
"name": r.get("name") or "",
|
"name": r.get("name") or "",
|
||||||
"status": r.get("status") or "active",
|
"status": status or r.get("status") or "active",
|
||||||
"use_testnet": bool(use_testnet),
|
"use_testnet": bool(use_testnet),
|
||||||
"role": membership_map.get(int(aid), "viewer"),
|
"role": membership_map.get(int(aid), "viewer"),
|
||||||
"has_api_key": bool(api_key),
|
"has_api_key": bool(api_key),
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,9 @@ async def get_all_configs(
|
||||||
|
|
||||||
# 合并账号级 API Key/Secret(从 accounts 表读,避免把密钥当普通配置存)
|
# 合并账号级 API Key/Secret(从 accounts 表读,避免把密钥当普通配置存)
|
||||||
try:
|
try:
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
api_key, api_secret, use_testnet = "", "", False
|
api_key, api_secret, use_testnet, status = "", "", False, "active"
|
||||||
# 仅用于配置页展示/更新:不返回 secret 明文;api_key 仅脱敏展示
|
# 仅用于配置页展示/更新:不返回 secret 明文;api_key 仅脱敏展示
|
||||||
result["BINANCE_API_KEY"] = {
|
result["BINANCE_API_KEY"] = {
|
||||||
"value": _mask(api_key or ""),
|
"value": _mask(api_key or ""),
|
||||||
|
|
@ -733,7 +733,7 @@ async def get_config(
|
||||||
try:
|
try:
|
||||||
# 虚拟字段:从 accounts 表读取
|
# 虚拟字段:从 accounts 表读取
|
||||||
if key in {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}:
|
if key in {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}:
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet, status = Account.get_credentials(account_id)
|
||||||
if key == "BINANCE_API_KEY":
|
if key == "BINANCE_API_KEY":
|
||||||
return {"key": key, "value": _mask(api_key or ""), "type": "string", "category": "api", "description": "币安API密钥(仅脱敏展示)"}
|
return {"key": key, "value": _mask(api_key or ""), "type": "string", "category": "api", "description": "币安API密钥(仅脱敏展示)"}
|
||||||
if key == "BINANCE_API_SECRET":
|
if key == "BINANCE_API_SECRET":
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,47 @@ const StatsDashboard = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCloseAllPositions = async () => {
|
||||||
|
if (!window.confirm(`确定要一键全平所有持仓吗?\n\n这将使用市价单平仓所有持仓,请谨慎操作!`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setClosingSymbol('ALL') // 使用特殊标记表示全平操作
|
||||||
|
setMessage('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('开始一键全平...')
|
||||||
|
const result = await api.closeAllPositions()
|
||||||
|
console.log('一键全平结果:', result)
|
||||||
|
|
||||||
|
let message = result.message || '一键全平完成'
|
||||||
|
if (result.closed > 0 || result.failed > 0) {
|
||||||
|
message = `一键全平完成: 成功 ${result.closed} / 失败 ${result.failed}`
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage(message)
|
||||||
|
|
||||||
|
// 立即刷新数据
|
||||||
|
await loadDashboard()
|
||||||
|
|
||||||
|
// 5秒后清除消息
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage('')
|
||||||
|
}, 5000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Close all positions error:', error)
|
||||||
|
const errorMessage = error.message || error.toString() || '一键全平失败,请检查网络连接或后端服务'
|
||||||
|
setMessage(`一键全平失败: ${errorMessage}`)
|
||||||
|
|
||||||
|
// 错误消息5秒后清除
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage('')
|
||||||
|
}, 5000)
|
||||||
|
} finally {
|
||||||
|
setClosingSymbol(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleEnsureSLTP = async (symbol) => {
|
const handleEnsureSLTP = async (symbol) => {
|
||||||
if (!window.confirm(`确定要为 ${symbol} 补挂“币安止损/止盈保护单”吗?\n\n说明:将自动取消该交易对已有的 STOP/TP 保护单并重新挂单(避免重复)。`)) {
|
if (!window.confirm(`确定要为 ${symbol} 补挂“币安止损/止盈保护单”吗?\n\n说明:将自动取消该交易对已有的 STOP/TP 保护单并重新挂单(避免重复)。`)) {
|
||||||
return
|
return
|
||||||
|
|
@ -387,14 +428,35 @@ const StatsDashboard = () => {
|
||||||
<div className="dashboard-card">
|
<div className="dashboard-card">
|
||||||
<div className="positions-header">
|
<div className="positions-header">
|
||||||
<h3>当前持仓</h3>
|
<h3>当前持仓</h3>
|
||||||
<button
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
className="sltp-all-btn"
|
<button
|
||||||
onClick={handleEnsureAllSLTP}
|
className="sltp-all-btn"
|
||||||
disabled={sltpAllBusy || openTrades.length === 0}
|
onClick={handleEnsureAllSLTP}
|
||||||
title="为所有持仓在币安侧补挂 STOP_MARKET + TAKE_PROFIT_MARKET 保护单(closePosition)"
|
disabled={sltpAllBusy || openTrades.length === 0}
|
||||||
>
|
title="为所有持仓在币安侧补挂 STOP_MARKET + TAKE_PROFIT_MARKET 保护单"
|
||||||
{sltpAllBusy ? '一键补挂中...' : '一键补挂止盈止损'}
|
>
|
||||||
</button>
|
{sltpAllBusy ? '一键补挂中...' : '一键补挂止盈止损'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="close-all-btn"
|
||||||
|
onClick={handleCloseAllPositions}
|
||||||
|
disabled={closingSymbol !== null || openTrades.length === 0}
|
||||||
|
style={{
|
||||||
|
padding: '6px 12px',
|
||||||
|
backgroundColor: '#ff6b6b',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: (closingSymbol !== null || openTrades.length === 0) ? 'not-allowed' : 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
opacity: (closingSymbol !== null || openTrades.length === 0) ? 0.6 : 1
|
||||||
|
}}
|
||||||
|
title="一键平仓所有持仓(市价单)"
|
||||||
|
>
|
||||||
|
{closingSymbol !== null ? '全平中...' : '⚡ 一键全平'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,7 @@ export const api = {
|
||||||
|
|
||||||
// 平仓操作
|
// 平仓操作
|
||||||
closePosition: async (symbol) => {
|
closePosition: async (symbol) => {
|
||||||
const response = await fetch(buildUrl(`/api/accounts/positions/${symbol}/close`), {
|
const response = await fetch(buildUrl(`/api/account/positions/${symbol}/close`), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...withAccountHeaders({ 'Content-Type': 'application/json' }),
|
...withAccountHeaders({ 'Content-Type': 'application/json' }),
|
||||||
|
|
@ -366,6 +366,21 @@ export const api = {
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 一键全平(平仓所有持仓)
|
||||||
|
closeAllPositions: async () => {
|
||||||
|
const response = await fetch(buildUrl(`/api/account/positions/close-all`), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
...withAccountHeaders({ 'Content-Type': 'application/json' }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ detail: '一键全平失败' }));
|
||||||
|
throw new Error(error.detail || '一键全平失败');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
// 补挂止盈止损(交易所保护单)
|
// 补挂止盈止损(交易所保护单)
|
||||||
ensurePositionSLTP: async (symbol) => {
|
ensurePositionSLTP: async (symbol) => {
|
||||||
const response = await fetch(buildUrl(`/api/accounts/positions/${symbol}/sltp/ensure`), {
|
const response = await fetch(buildUrl(`/api/accounts/positions/${symbol}/sltp/ensure`), {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user