a
This commit is contained in:
parent
270a3cb8d8
commit
1adb3137d6
|
|
@ -5,14 +5,17 @@ from fastapi import APIRouter, HTTPException
|
|||
from api.models.config import ConfigItem, ConfigUpdate
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
# 添加项目根目录到路径
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
sys.path.insert(0, str(project_root / 'backend'))
|
||||
sys.path.insert(0, str(project_root / 'trading_system'))
|
||||
|
||||
from database.models import TradingConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
|
|
@ -171,3 +174,112 @@ async def update_configs_batch(configs: list[ConfigItem]):
|
|||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/feasibility-check")
|
||||
async def check_config_feasibility():
|
||||
"""
|
||||
检查配置可行性,基于当前账户余额和杠杆倍数计算可行的配置建议
|
||||
"""
|
||||
try:
|
||||
# 获取账户余额
|
||||
try:
|
||||
from api.routes.account import get_realtime_account_data
|
||||
account_data = await get_realtime_account_data()
|
||||
available_balance = account_data.get('available_balance', 0)
|
||||
total_balance = account_data.get('total_balance', 0)
|
||||
except Exception as e:
|
||||
logger.warning(f"获取账户余额失败: {e},使用默认值")
|
||||
available_balance = 0
|
||||
total_balance = 0
|
||||
|
||||
if available_balance <= 0:
|
||||
return {
|
||||
"feasible": False,
|
||||
"error": "无法获取账户余额,请检查API配置",
|
||||
"suggestions": []
|
||||
}
|
||||
|
||||
# 获取当前配置
|
||||
min_margin_usdt = TradingConfig.get_value('MIN_MARGIN_USDT', 5.0)
|
||||
min_position_percent = TradingConfig.get_value('MIN_POSITION_PERCENT', 0.02)
|
||||
max_position_percent = TradingConfig.get_value('MAX_POSITION_PERCENT', 0.08)
|
||||
leverage = TradingConfig.get_value('LEVERAGE', 10)
|
||||
|
||||
# 计算最小保证金要求对应的最小仓位价值
|
||||
required_position_value = min_margin_usdt * leverage
|
||||
required_position_percent = required_position_value / available_balance if available_balance > 0 else 0
|
||||
|
||||
# 检查是否可行
|
||||
is_feasible = required_position_percent <= max_position_percent
|
||||
|
||||
suggestions = []
|
||||
|
||||
if not is_feasible:
|
||||
# 不可行,给出建议
|
||||
# 方案1:降低最小保证金
|
||||
suggested_min_margin = (available_balance * max_position_percent) / leverage
|
||||
suggestions.append({
|
||||
"type": "reduce_min_margin",
|
||||
"title": "降低最小保证金",
|
||||
"description": f"将 MIN_MARGIN_USDT 调整为 {suggested_min_margin:.2f} USDT(当前: {min_margin_usdt:.2f} USDT)",
|
||||
"config_key": "MIN_MARGIN_USDT",
|
||||
"suggested_value": round(suggested_min_margin, 2),
|
||||
"reason": f"当前配置需要 {required_position_percent*100:.1f}% 的仓位价值,但最大允许 {max_position_percent*100:.1f}%"
|
||||
})
|
||||
|
||||
# 方案2:增加账户余额
|
||||
required_balance = required_position_value / max_position_percent
|
||||
suggestions.append({
|
||||
"type": "increase_balance",
|
||||
"title": "增加账户余额",
|
||||
"description": f"将账户余额增加到至少 {required_balance:.2f} USDT(当前: {available_balance:.2f} USDT)",
|
||||
"config_key": None,
|
||||
"suggested_value": round(required_balance, 2),
|
||||
"reason": f"当前余额不足以满足最小保证金 {min_margin_usdt:.2f} USDT 的要求"
|
||||
})
|
||||
|
||||
# 方案3:降低杠杆倍数
|
||||
suggested_leverage = int((available_balance * max_position_percent) / min_margin_usdt)
|
||||
if suggested_leverage >= 1:
|
||||
suggestions.append({
|
||||
"type": "reduce_leverage",
|
||||
"title": "降低杠杆倍数",
|
||||
"description": f"将 LEVERAGE 调整为 {suggested_leverage}x(当前: {leverage}x)",
|
||||
"config_key": "LEVERAGE",
|
||||
"suggested_value": suggested_leverage,
|
||||
"reason": f"降低杠杆可以减少所需仓位价值"
|
||||
})
|
||||
else:
|
||||
# 可行,显示当前配置信息
|
||||
actual_min_position_value = available_balance * min_position_percent
|
||||
actual_min_margin = actual_min_position_value / leverage if leverage > 0 else actual_min_position_value
|
||||
|
||||
suggestions.append({
|
||||
"type": "info",
|
||||
"title": "配置可行",
|
||||
"description": f"当前配置可以正常下单。最小仓位价值: {actual_min_position_value:.2f} USDT,对应保证金: {actual_min_margin:.2f} USDT",
|
||||
"config_key": None,
|
||||
"suggested_value": None,
|
||||
"reason": None
|
||||
})
|
||||
|
||||
return {
|
||||
"feasible": is_feasible,
|
||||
"account_balance": available_balance,
|
||||
"leverage": leverage,
|
||||
"current_config": {
|
||||
"min_margin_usdt": min_margin_usdt,
|
||||
"min_position_percent": min_position_percent,
|
||||
"max_position_percent": max_position_percent
|
||||
},
|
||||
"calculated_values": {
|
||||
"required_position_value": required_position_value,
|
||||
"required_position_percent": required_position_percent * 100,
|
||||
"max_allowed_position_percent": max_position_percent * 100
|
||||
},
|
||||
"suggestions": suggestions
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"检查配置可行性失败: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"检查配置可行性失败: {str(e)}")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,78 @@
|
|||
-- 添加交易统计字段
|
||||
-- 用于记录策略类型和持仓持续时间
|
||||
-- 兼容MySQL 5.7+,使用动态SQL检查列和索引是否存在
|
||||
|
||||
ALTER TABLE `trades`
|
||||
ADD COLUMN IF NOT EXISTS `strategy_type` VARCHAR(50) COMMENT '策略类型: trend_following, mean_reversion' AFTER `exit_reason`,
|
||||
ADD COLUMN IF NOT EXISTS `duration_minutes` INT COMMENT '持仓持续时间(分钟)' AFTER `strategy_type`;
|
||||
USE `auto_trade_sys`;
|
||||
|
||||
-- 添加索引以便统计查询
|
||||
ALTER TABLE `trades`
|
||||
ADD INDEX IF NOT EXISTS `idx_strategy_type` (`strategy_type`),
|
||||
ADD INDEX IF NOT EXISTS `idx_exit_reason` (`exit_reason`);
|
||||
-- 1. 添加 strategy_type 列(如果不存在)
|
||||
SET @column_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auto_trade_sys'
|
||||
AND table_name = 'trades'
|
||||
AND column_name = 'strategy_type'
|
||||
);
|
||||
|
||||
SET @sql = IF(@column_exists = 0,
|
||||
'ALTER TABLE `trades` ADD COLUMN `strategy_type` VARCHAR(50) COMMENT ''策略类型: trend_following, mean_reversion'' AFTER `exit_reason`',
|
||||
'SELECT "strategy_type 列已存在,跳过添加" as message'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2. 添加 duration_minutes 列(如果不存在)
|
||||
SET @column_exists2 = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auto_trade_sys'
|
||||
AND table_name = 'trades'
|
||||
AND column_name = 'duration_minutes'
|
||||
);
|
||||
|
||||
SET @sql2 = IF(@column_exists2 = 0,
|
||||
'ALTER TABLE `trades` ADD COLUMN `duration_minutes` INT COMMENT ''持仓持续时间(分钟)'' AFTER `strategy_type`',
|
||||
'SELECT "duration_minutes 列已存在,跳过添加" as message'
|
||||
);
|
||||
PREPARE stmt2 FROM @sql2;
|
||||
EXECUTE stmt2;
|
||||
DEALLOCATE PREPARE stmt2;
|
||||
|
||||
-- 3. 添加 idx_strategy_type 索引(如果不存在)
|
||||
SET @index_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'auto_trade_sys'
|
||||
AND table_name = 'trades'
|
||||
AND index_name = 'idx_strategy_type'
|
||||
);
|
||||
|
||||
SET @sql3 = IF(@index_exists = 0,
|
||||
'ALTER TABLE `trades` ADD INDEX `idx_strategy_type` (`strategy_type`)',
|
||||
'SELECT "idx_strategy_type 索引已存在,跳过添加" as message'
|
||||
);
|
||||
PREPARE stmt3 FROM @sql3;
|
||||
EXECUTE stmt3;
|
||||
DEALLOCATE PREPARE stmt3;
|
||||
|
||||
-- 4. 添加 idx_exit_reason 索引(如果不存在)
|
||||
SET @index_exists2 = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'auto_trade_sys'
|
||||
AND table_name = 'trades'
|
||||
AND index_name = 'idx_exit_reason'
|
||||
);
|
||||
|
||||
SET @sql4 = IF(@index_exists2 = 0,
|
||||
'ALTER TABLE `trades` ADD INDEX `idx_exit_reason` (`exit_reason`)',
|
||||
'SELECT "idx_exit_reason 索引已存在,跳过添加" as message'
|
||||
);
|
||||
PREPARE stmt4 FROM @sql4;
|
||||
EXECUTE stmt4;
|
||||
DEALLOCATE PREPARE stmt4;
|
||||
|
||||
-- 验证:检查表结构
|
||||
-- SHOW CREATE TABLE `trades`;
|
||||
-- 或者
|
||||
-- DESCRIBE `trades`;
|
||||
|
|
|
|||
|
|
@ -170,7 +170,11 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate
|
|||
('TRAILING_STOP_PROTECT', '0.05', 'number', 'strategy', '移动止损保护利润(保护5%利润,更合理)'),
|
||||
|
||||
-- 持仓同步
|
||||
<<<<<<< Current (Your changes)
|
||||
('POSITION_SYNC_INTERVAL', '300', 'number', 'scan', '持仓状态同步间隔(秒),默认5分钟,用于同步币安实际持仓与数据库状态'),
|
||||
=======
|
||||
('POSITION_SYNC_INTERVAL', '60', 'number', 'scan', '持仓状态同步间隔(秒),默认1分钟,用于同步币安实际持仓与数据库状态'),
|
||||
>>>>>>> Incoming (Background Agent changes)
|
||||
|
||||
|
||||
-- API配置
|
||||
|
|
|
|||
|
|
@ -464,3 +464,139 @@
|
|||
min-width: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 配置可行性检查样式 */
|
||||
.feasibility-check {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 2px solid;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.feasibility-check.feasible {
|
||||
background: #e8f5e9;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.feasibility-check.infeasible {
|
||||
background: #fff3e0;
|
||||
border-color: #FF9800;
|
||||
}
|
||||
|
||||
.feasibility-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.feasibility-header h4 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.refresh-btn:hover:not(:disabled) {
|
||||
background: #f5f5f5;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.refresh-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.feasibility-info {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feasibility-info p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.feasibility-info strong {
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #d32f2f !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.feasibility-suggestions {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.feasibility-suggestions h5 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
background: white;
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0.75rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.suggestion-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.suggestion-header strong {
|
||||
color: #1976D2;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.suggestion-desc {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.apply-suggestion-btn {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.apply-suggestion-btn:hover {
|
||||
background: #1976D2;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.apply-suggestion-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ const ConfigPanel = () => {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [feasibilityCheck, setFeasibilityCheck] = useState(null)
|
||||
const [checkingFeasibility, setCheckingFeasibility] = useState(false)
|
||||
|
||||
// 预设方案配置
|
||||
// 注意:百分比配置使用整数形式(如8.0表示8%),在应用时会转换为小数(0.08)
|
||||
|
|
@ -64,8 +66,22 @@ const ConfigPanel = () => {
|
|||
|
||||
useEffect(() => {
|
||||
loadConfigs()
|
||||
checkFeasibility()
|
||||
}, [])
|
||||
|
||||
const checkFeasibility = async () => {
|
||||
setCheckingFeasibility(true)
|
||||
try {
|
||||
const result = await api.checkConfigFeasibility()
|
||||
setFeasibilityCheck(result)
|
||||
} catch (error) {
|
||||
console.error('检查配置可行性失败:', error)
|
||||
// 静默失败,不显示错误
|
||||
} finally {
|
||||
setCheckingFeasibility(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loadConfigs = async () => {
|
||||
try {
|
||||
const data = await api.getConfigs()
|
||||
|
|
@ -257,6 +273,73 @@ const ConfigPanel = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 配置可行性检查提示 */}
|
||||
{feasibilityCheck && (
|
||||
<div className={`feasibility-check ${feasibilityCheck.feasible ? 'feasible' : 'infeasible'}`}>
|
||||
<div className="feasibility-header">
|
||||
<h4>
|
||||
{feasibilityCheck.feasible ? '✓ 配置可行' : '⚠ 配置冲突'}
|
||||
</h4>
|
||||
<button
|
||||
onClick={checkFeasibility}
|
||||
disabled={checkingFeasibility}
|
||||
className="refresh-btn"
|
||||
title="重新检查"
|
||||
>
|
||||
{checkingFeasibility ? '检查中...' : '🔄 刷新'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="feasibility-info">
|
||||
<p>
|
||||
账户余额: <strong>{feasibilityCheck.account_balance?.toFixed(2) || 'N/A'}</strong> USDT |
|
||||
杠杆: <strong>{feasibilityCheck.leverage}x</strong> |
|
||||
最小保证金: <strong>{feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2) || 'N/A'}</strong> USDT
|
||||
</p>
|
||||
{!feasibilityCheck.feasible && (
|
||||
<p className="warning-text">
|
||||
需要仓位价值: <strong>{feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}%</strong> |
|
||||
最大允许: <strong>{feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}%</strong>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{feasibilityCheck.suggestions && feasibilityCheck.suggestions.length > 0 && (
|
||||
<div className="feasibility-suggestions">
|
||||
<h5>建议方案:</h5>
|
||||
{feasibilityCheck.suggestions.map((suggestion, index) => (
|
||||
<div key={index} className="suggestion-item">
|
||||
<div className="suggestion-header">
|
||||
<strong>{suggestion.title}</strong>
|
||||
</div>
|
||||
<p className="suggestion-desc">{suggestion.description}</p>
|
||||
{suggestion.config_key && suggestion.suggested_value !== null && (
|
||||
<button
|
||||
className="apply-suggestion-btn"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await api.updateConfig(suggestion.config_key, {
|
||||
value: suggestion.suggested_value,
|
||||
type: 'number',
|
||||
category: 'position'
|
||||
})
|
||||
setMessage(`已应用建议: ${suggestion.title}`)
|
||||
await loadConfigs()
|
||||
await checkFeasibility()
|
||||
} catch (error) {
|
||||
setMessage('应用建议失败: ' + error.message)
|
||||
}
|
||||
}}
|
||||
>
|
||||
应用此建议
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
|
||||
{message}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ const TradeList = () => {
|
|||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
// 每30秒自动刷新一次,确保订单状态及时更新
|
||||
const interval = setInterval(() => {
|
||||
loadData()
|
||||
}, 30000) // 30秒刷新一次
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const loadData = async () => {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ export const api = {
|
|||
return response.json();
|
||||
},
|
||||
|
||||
// 检查配置可行性
|
||||
checkConfigFeasibility: async () => {
|
||||
const response = await fetch(buildUrl('/api/config/feasibility-check'));
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: '检查配置可行性失败' }));
|
||||
throw new Error(error.detail || '检查配置可行性失败');
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// 交易记录
|
||||
getTrades: async (params = {}) => {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
|
|
|||
|
|
@ -189,7 +189,11 @@ def _get_trading_config():
|
|||
'USE_TRAILING_STOP': True,
|
||||
'TRAILING_STOP_ACTIVATION': 0.10, # 移动止损激活提高到10%(盈利10%后激活,给趋势更多空间)
|
||||
'TRAILING_STOP_PROTECT': 0.05, # 保护利润提高到5%(保护5%利润,更合理)
|
||||
<<<<<<< Current (Your changes)
|
||||
'POSITION_SYNC_INTERVAL': 300, # 持仓状态同步间隔(秒),默认5分钟
|
||||
=======
|
||||
'POSITION_SYNC_INTERVAL': 60, # 持仓状态同步间隔(秒),缩短到1分钟,确保状态及时同步
|
||||
>>>>>>> Incoming (Background Agent changes)
|
||||
}
|
||||
|
||||
# 币安API配置(优先从数据库,回退到环境变量和默认值)
|
||||
|
|
|
|||
|
|
@ -1894,57 +1894,25 @@ class PositionManager:
|
|||
f"数量: {quantity:.4f}"
|
||||
)
|
||||
|
||||
# 更新数据库
|
||||
if DB_AVAILABLE and Trade:
|
||||
trade_id = position_info.get('tradeId')
|
||||
if trade_id:
|
||||
try:
|
||||
if position_info['side'] == 'BUY':
|
||||
pnl = (current_price_float - entry_price) * quantity
|
||||
else: # SELL
|
||||
pnl = (entry_price - current_price_float) * quantity
|
||||
|
||||
logger.info(f"{symbol} [自动平仓] 更新数据库记录 (ID: {trade_id})...")
|
||||
# 计算持仓持续时间和策略类型
|
||||
entry_time = position_info.get('entryTime')
|
||||
duration_minutes = None
|
||||
if entry_time:
|
||||
try:
|
||||
from datetime import datetime
|
||||
if isinstance(entry_time, str):
|
||||
entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
entry_dt = entry_time
|
||||
exit_dt = get_beijing_time() # 使用北京时间计算持续时间
|
||||
duration = exit_dt - entry_dt
|
||||
duration_minutes = int(duration.total_seconds() / 60)
|
||||
except Exception as e:
|
||||
logger.debug(f"计算持仓持续时间失败: {e}")
|
||||
|
||||
strategy_type = position_info.get('strategyType', 'trend_following')
|
||||
|
||||
Trade.update_exit(
|
||||
trade_id=trade_id,
|
||||
exit_price=current_price_float,
|
||||
exit_reason=exit_reason,
|
||||
pnl=pnl,
|
||||
pnl_percent=pnl_percent_margin, # 修复:使用 pnl_percent_margin 而不是 pnl_percent
|
||||
strategy_type=strategy_type,
|
||||
duration_minutes=duration_minutes
|
||||
)
|
||||
logger.info(f"{symbol} [自动平仓] ✓ 数据库记录已更新 (盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin)")
|
||||
except Exception as e:
|
||||
logger.error(f"{symbol} [自动平仓] ❌ 更新数据库记录失败: {e}")
|
||||
import traceback
|
||||
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
||||
|
||||
# 执行平仓(注意:这里会停止监控,所以先更新数据库)
|
||||
# 执行平仓(让 close_position 统一处理数据库更新,避免重复更新和状态不一致)
|
||||
logger.info(f"{symbol} [自动平仓] 正在执行平仓订单...")
|
||||
success = await self.close_position(symbol, reason=exit_reason)
|
||||
if success:
|
||||
logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成")
|
||||
# 平仓成功后,立即触发一次状态同步,确保数据库状态与币安一致
|
||||
try:
|
||||
await asyncio.sleep(2) # 等待2秒让币安订单完全成交
|
||||
await self.sync_positions_with_binance()
|
||||
logger.debug(f"{symbol} [自动平仓] 已触发状态同步")
|
||||
except Exception as sync_error:
|
||||
logger.warning(f"{symbol} [自动平仓] 状态同步失败: {sync_error}")
|
||||
else:
|
||||
logger.error(f"{symbol} [自动平仓] ❌ 平仓失败")
|
||||
# 即使平仓失败,也尝试同步状态(可能币安已经平仓了)
|
||||
try:
|
||||
await self.sync_positions_with_binance()
|
||||
except Exception as sync_error:
|
||||
logger.warning(f"{symbol} [自动平仓] 状态同步失败: {sync_error}")
|
||||
|
||||
async def diagnose_position(self, symbol: str):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user