a
This commit is contained in:
parent
0ecdff4530
commit
5717614f61
|
|
@ -26,9 +26,11 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str, account_id: int = 1):
|
||||||
该接口用于“手动补挂”,不依赖 trading_system 的监控任务。
|
该接口用于“手动补挂”,不依赖 trading_system 的监控任务。
|
||||||
"""
|
"""
|
||||||
# 从 accounts 表读取账号私有API密钥
|
# 从 accounts 表读取账号私有API密钥
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(int(account_id or 1))
|
account_id_int = int(account_id or 1)
|
||||||
|
api_key, api_secret, use_testnet = Account.get_credentials(account_id_int)
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
raise HTTPException(status_code=400, detail="API密钥未配置")
|
logger.error(f"[account_id={account_id_int}] API密钥未配置")
|
||||||
|
raise HTTPException(status_code=400, detail=f"API密钥未配置(account_id={account_id_int})")
|
||||||
|
|
||||||
# 导入交易系统的BinanceClient(复用其精度/持仓模式处理)
|
# 导入交易系统的BinanceClient(复用其精度/持仓模式处理)
|
||||||
try:
|
try:
|
||||||
|
|
@ -286,7 +288,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 = Account.get_credentials(account_id)
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
raise HTTPException(status_code=400, detail="API密钥未配置")
|
logger.error(f"[account_id={account_id}] API密钥未配置")
|
||||||
|
raise HTTPException(status_code=400, detail=f"API密钥未配置(account_id={account_id})")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from binance_client import BinanceClient
|
from binance_client import BinanceClient
|
||||||
|
|
@ -365,7 +368,8 @@ async def get_realtime_account_data(account_id: int = 1):
|
||||||
logger.info(f" - 使用测试网: {use_testnet}")
|
logger.info(f" - 使用测试网: {use_testnet}")
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
error_msg = "API密钥未配置,请在配置界面设置该账号的BINANCE_API_KEY和BINANCE_API_SECRET"
|
error_msg = f"API密钥未配置(account_id={account_id}),请在配置界面设置该账号的BINANCE_API_KEY和BINANCE_API_SECRET"
|
||||||
|
logger.error(f"[account_id={account_id}] API密钥未配置")
|
||||||
logger.error(f" ✗ {error_msg}")
|
logger.error(f" ✗ {error_msg}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
|
@ -563,11 +567,11 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)):
|
||||||
try:
|
try:
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
||||||
|
|
||||||
logger.info(f"尝试获取实时持仓数据 (testnet={use_testnet})")
|
logger.info(f"尝试获取实时持仓数据 (testnet={use_testnet}, account_id={account_id})")
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
error_msg = "API密钥未配置"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(error_msg)
|
logger.warning(f"[account_id={account_id}] {error_msg}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=error_msg
|
detail=error_msg
|
||||||
|
|
@ -737,8 +741,8 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
error_msg = "API密钥未配置"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(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)
|
||||||
|
|
||||||
# 导入必要的模块
|
# 导入必要的模块
|
||||||
|
|
@ -1048,8 +1052,8 @@ async def sync_positions(account_id: int = Depends(get_account_id)):
|
||||||
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
api_key, api_secret, use_testnet = Account.get_credentials(account_id)
|
||||||
|
|
||||||
if not api_key or not api_secret:
|
if not api_key or not api_secret:
|
||||||
error_msg = "API密钥未配置"
|
error_msg = f"API密钥未配置(account_id={account_id})"
|
||||||
logger.warning(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)
|
||||||
|
|
||||||
# 导入必要的模块
|
# 导入必要的模块
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { api, getCurrentAccountId } from '../services/api'
|
import { api } from '../services/api'
|
||||||
import './GlobalConfig.css'
|
import './GlobalConfig.css'
|
||||||
import './ConfigPanel.css' // 复用 ConfigPanel 的样式
|
import './ConfigPanel.css' // 复用 ConfigPanel 的样式
|
||||||
|
|
||||||
|
|
@ -23,7 +23,6 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
// 预设方案相关
|
// 预设方案相关
|
||||||
const [configs, setConfigs] = useState({})
|
const [configs, setConfigs] = useState({})
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [currentAccountId, setCurrentAccountId] = useState(getCurrentAccountId())
|
|
||||||
const [configMeta, setConfigMeta] = useState(null)
|
const [configMeta, setConfigMeta] = useState(null)
|
||||||
|
|
||||||
// 配置快照相关
|
// 配置快照相关
|
||||||
|
|
@ -217,8 +216,16 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
|
|
||||||
const loadConfigs = async () => {
|
const loadConfigs = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 管理员全局配置:使用全局策略账号的配置,不依赖当前 account
|
||||||
|
if (isAdmin && configMeta?.global_strategy_account_id) {
|
||||||
|
const globalAccountId = parseInt(String(configMeta.global_strategy_account_id), 10) || 1
|
||||||
|
const data = await api.getGlobalConfigs(globalAccountId)
|
||||||
|
setConfigs(data)
|
||||||
|
} else {
|
||||||
|
// 非管理员或未加载 configMeta 时,使用默认方式
|
||||||
const data = await api.getConfigs()
|
const data = await api.getConfigs()
|
||||||
setConfigs(data)
|
setConfigs(data)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load configs:', error)
|
console.error('Failed to load configs:', error)
|
||||||
}
|
}
|
||||||
|
|
@ -288,8 +295,13 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
loadAccounts()
|
loadAccounts()
|
||||||
// 只有管理员才加载配置和系统状态
|
// 只有管理员才加载配置和系统状态
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
loadConfigMeta().catch(() => {}) // 静默失败
|
// 先加载 configMeta,再加载 configs(因为 loadConfigs 需要 global_strategy_account_id)
|
||||||
loadConfigs().catch(() => {}) // 静默失败
|
loadConfigMeta()
|
||||||
|
.then(() => {
|
||||||
|
// configMeta 加载完成后,再加载 configs
|
||||||
|
loadConfigs().catch(() => {})
|
||||||
|
})
|
||||||
|
.catch(() => {}) // 静默失败
|
||||||
loadSystemStatus().catch(() => {}) // 静默失败
|
loadSystemStatus().catch(() => {}) // 静默失败
|
||||||
loadBackendStatus().catch(() => {}) // 静默失败
|
loadBackendStatus().catch(() => {}) // 静默失败
|
||||||
|
|
||||||
|
|
@ -451,7 +463,14 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
}
|
}
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
|
|
||||||
const response = await api.updateConfigsBatch(configItems)
|
// 管理员全局配置:应用到全局策略账号
|
||||||
|
let response
|
||||||
|
if (isAdmin && configMeta?.global_strategy_account_id) {
|
||||||
|
const globalAccountId = parseInt(String(configMeta.global_strategy_account_id), 10) || 1
|
||||||
|
response = await api.updateGlobalConfigsBatch(configItems, globalAccountId)
|
||||||
|
} else {
|
||||||
|
response = await api.updateConfigsBatch(configItems)
|
||||||
|
}
|
||||||
setMessage(response.message || `已应用${preset.name}`)
|
setMessage(response.message || `已应用${preset.name}`)
|
||||||
if (response.note) {
|
if (response.note) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -490,7 +509,14 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildConfigSnapshot = async (includeSecrets) => {
|
const buildConfigSnapshot = async (includeSecrets) => {
|
||||||
const data = await api.getConfigs()
|
// 管理员全局配置:使用全局策略账号的配置
|
||||||
|
let data
|
||||||
|
if (isAdmin && configMeta?.global_strategy_account_id) {
|
||||||
|
const globalAccountId = parseInt(String(configMeta.global_strategy_account_id), 10) || 1
|
||||||
|
data = await api.getGlobalConfigs(globalAccountId)
|
||||||
|
} else {
|
||||||
|
data = await api.getConfigs()
|
||||||
|
}
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
||||||
const categoryMap = {
|
const categoryMap = {
|
||||||
|
|
@ -695,7 +721,8 @@ const GlobalConfig = ({ currentUser }) => {
|
||||||
const globalStrategyAccountId = configMeta?.global_strategy_account_id
|
const globalStrategyAccountId = configMeta?.global_strategy_account_id
|
||||||
? parseInt(String(configMeta.global_strategy_account_id), 10)
|
? parseInt(String(configMeta.global_strategy_account_id), 10)
|
||||||
: 1
|
: 1
|
||||||
const isGlobalStrategyAccount = isAdmin && currentAccountId === globalStrategyAccountId
|
// 管理员全局配置页面:不依赖当前 account,直接管理全局策略账号
|
||||||
|
const isGlobalStrategyAccount = isAdmin
|
||||||
|
|
||||||
// 简单计算:当前预设(直接在 render 时计算,不使用 useMemo)
|
// 简单计算:当前预设(直接在 render 时计算,不使用 useMemo)
|
||||||
let currentPreset = null
|
let currentPreset = null
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,35 @@ export const api = {
|
||||||
}
|
}
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 全局配置:获取全局策略账号的配置(管理员专用,不依赖当前 account)
|
||||||
|
getGlobalConfigs: async (globalAccountId) => {
|
||||||
|
const response = await fetch(buildUrl('/api/config'), {
|
||||||
|
headers: withAuthHeaders({ 'X-Account-Id': String(globalAccountId || 1) })
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ detail: '获取全局配置失败' }))
|
||||||
|
throw new Error(error.detail || '获取全局配置失败')
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 全局配置:批量更新全局策略账号的配置(管理员专用)
|
||||||
|
updateGlobalConfigsBatch: async (configs, globalAccountId) => {
|
||||||
|
const response = await fetch(buildUrl('/api/config/batch'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: withAuthHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Account-Id': String(globalAccountId || 1)
|
||||||
|
}),
|
||||||
|
body: JSON.stringify(configs)
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json()
|
||||||
|
throw new Error(error.detail || '更新全局配置失败')
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
getConfigs: async () => {
|
getConfigs: async () => {
|
||||||
const response = await fetch(buildUrl('/api/config'), { headers: withAccountHeaders() });
|
const response = await fetch(buildUrl('/api/config'), { headers: withAccountHeaders() });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1189,6 +1189,7 @@ class PositionManager:
|
||||||
min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800)
|
min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800)
|
||||||
entry_time = position_info.get('entryTime')
|
entry_time = position_info.get('entryTime')
|
||||||
hold_time_sec = 0
|
hold_time_sec = 0
|
||||||
|
hold_time_minutes = 0
|
||||||
if entry_time:
|
if entry_time:
|
||||||
try:
|
try:
|
||||||
if isinstance(entry_time, datetime):
|
if isinstance(entry_time, datetime):
|
||||||
|
|
@ -1196,14 +1197,22 @@ class PositionManager:
|
||||||
else:
|
else:
|
||||||
# 兼容:如果是时间戳或字符串
|
# 兼容:如果是时间戳或字符串
|
||||||
hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
|
hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
|
||||||
|
hold_time_minutes = hold_time_sec / 60.0
|
||||||
except Exception:
|
except Exception:
|
||||||
hold_time_sec = 0
|
hold_time_sec = 0
|
||||||
|
hold_time_minutes = 0
|
||||||
|
|
||||||
# 如果持仓时间不足,禁止平仓(除非是手动平仓)
|
# 如果持仓时间不足,禁止平仓(除非是手动平仓)
|
||||||
if hold_time_sec < min_hold_sec:
|
if hold_time_sec < min_hold_sec:
|
||||||
logger.debug(
|
remaining_sec = min_hold_sec - hold_time_sec
|
||||||
f"{symbol} [持仓时间锁] 持仓时间 {hold_time_sec}s < 最小要求 {min_hold_sec}s,"
|
remaining_minutes = remaining_sec / 60.0
|
||||||
f"禁止自动平仓(强制波段持仓纪律)"
|
logger.warning(
|
||||||
|
f"{symbol} [持仓时间锁] ⚠ 持仓时间不足,禁止自动平仓: "
|
||||||
|
f"已持仓 {hold_time_minutes:.1f} 分钟 ({hold_time_sec}s) < "
|
||||||
|
f"最小要求 {min_hold_sec/60:.1f} 分钟 ({min_hold_sec}s) | "
|
||||||
|
f"还需等待 {remaining_minutes:.1f} 分钟 ({remaining_sec}s) | "
|
||||||
|
f"入场时间: {entry_time} | "
|
||||||
|
f"强制波段持仓纪律,避免分钟级平仓"
|
||||||
)
|
)
|
||||||
continue # 跳过这个持仓,不触发任何平仓逻辑
|
continue # 跳过这个持仓,不触发任何平仓逻辑
|
||||||
|
|
||||||
|
|
@ -1240,7 +1249,12 @@ class PositionManager:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"从Redis重新加载配置失败: {e}")
|
logger.debug(f"从Redis重新加载配置失败: {e}")
|
||||||
|
|
||||||
|
# 检查是否启用移动止损(默认False,需要显式启用)
|
||||||
use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False)
|
use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False)
|
||||||
|
if use_trailing:
|
||||||
|
logger.debug(f"{symbol} [移动止损] 已启用,将检查移动止损逻辑")
|
||||||
|
else:
|
||||||
|
logger.debug(f"{symbol} [移动止损] 已禁用(USE_TRAILING_STOP=False),跳过移动止损检查")
|
||||||
if use_trailing:
|
if use_trailing:
|
||||||
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金
|
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金
|
||||||
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金
|
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金
|
||||||
|
|
@ -1327,13 +1341,38 @@ class PositionManager:
|
||||||
|
|
||||||
# 直接比较当前盈亏百分比与止损目标(基于保证金)
|
# 直接比较当前盈亏百分比与止损目标(基于保证金)
|
||||||
if pnl_percent_margin <= -stop_loss_pct_margin:
|
if pnl_percent_margin <= -stop_loss_pct_margin:
|
||||||
logger.warning(
|
|
||||||
f"{symbol} 触发止损(基于保证金): "
|
|
||||||
f"当前盈亏={pnl_percent_margin:.2f}% of margin <= 止损目标=-{stop_loss_pct_margin:.2f}% of margin | "
|
|
||||||
f"当前价={current_price:.4f}, 止损价={stop_loss:.4f}"
|
|
||||||
)
|
|
||||||
# 确定平仓原因
|
# 确定平仓原因
|
||||||
exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
|
exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
|
||||||
|
|
||||||
|
# 计算持仓时间
|
||||||
|
entry_time = position_info.get('entryTime')
|
||||||
|
hold_time_minutes = 0
|
||||||
|
if entry_time:
|
||||||
|
try:
|
||||||
|
if isinstance(entry_time, datetime):
|
||||||
|
hold_time_sec = int((get_beijing_time() - entry_time).total_seconds())
|
||||||
|
else:
|
||||||
|
hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
|
||||||
|
hold_time_minutes = hold_time_sec / 60.0
|
||||||
|
except Exception:
|
||||||
|
hold_time_minutes = 0
|
||||||
|
|
||||||
|
# 详细诊断日志:记录平仓时的所有关键信息
|
||||||
|
logger.warning("=" * 80)
|
||||||
|
logger.warning(f"{symbol} [平仓诊断日志] ===== 触发止损平仓 =====")
|
||||||
|
logger.warning(f" 平仓原因: {exit_reason}")
|
||||||
|
logger.warning(f" 入场价格: {entry_price:.6f} USDT")
|
||||||
|
logger.warning(f" 当前价格: {current_price:.4f} USDT")
|
||||||
|
logger.warning(f" 止损价格: {stop_loss:.4f} USDT")
|
||||||
|
logger.warning(f" 持仓数量: {quantity:.4f}")
|
||||||
|
logger.warning(f" 持仓时间: {hold_time_minutes:.1f} 分钟")
|
||||||
|
logger.warning(f" 入场时间: {entry_time}")
|
||||||
|
logger.warning(f" 当前盈亏: {pnl_percent_margin:.2f}% of margin")
|
||||||
|
logger.warning(f" 止损目标: -{stop_loss_pct_margin*100:.2f}% of margin")
|
||||||
|
logger.warning(f" 亏损金额: {abs(pnl_amount):.4f} USDT")
|
||||||
|
if position_info.get('trailingStopActivated'):
|
||||||
|
logger.warning(f" 移动止损: 已激活(从初始止损 {position_info.get('initialStopLoss', 'N/A')} 调整)")
|
||||||
|
logger.warning("=" * 80)
|
||||||
# 更新数据库
|
# 更新数据库
|
||||||
if DB_AVAILABLE:
|
if DB_AVAILABLE:
|
||||||
trade_id = position_info.get('tradeId')
|
trade_id = position_info.get('tradeId')
|
||||||
|
|
@ -2370,6 +2409,7 @@ class PositionManager:
|
||||||
min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800)
|
min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800)
|
||||||
entry_time = position_info.get('entryTime')
|
entry_time = position_info.get('entryTime')
|
||||||
hold_time_sec = 0
|
hold_time_sec = 0
|
||||||
|
hold_time_minutes = 0
|
||||||
if entry_time:
|
if entry_time:
|
||||||
try:
|
try:
|
||||||
if isinstance(entry_time, datetime):
|
if isinstance(entry_time, datetime):
|
||||||
|
|
@ -2377,18 +2417,31 @@ class PositionManager:
|
||||||
else:
|
else:
|
||||||
# 兼容:如果是时间戳或字符串
|
# 兼容:如果是时间戳或字符串
|
||||||
hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
|
hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
|
||||||
|
hold_time_minutes = hold_time_sec / 60.0
|
||||||
except Exception:
|
except Exception:
|
||||||
hold_time_sec = 0
|
hold_time_sec = 0
|
||||||
|
hold_time_minutes = 0
|
||||||
|
|
||||||
# 如果持仓时间不足,禁止平仓(除非是手动平仓)
|
# 如果持仓时间不足,禁止平仓(除非是手动平仓)
|
||||||
if hold_time_sec < min_hold_sec:
|
if hold_time_sec < min_hold_sec:
|
||||||
logger.debug(
|
remaining_sec = min_hold_sec - hold_time_sec
|
||||||
f"{symbol} [持仓时间锁] 持仓时间 {hold_time_sec}s < 最小要求 {min_hold_sec}s,"
|
remaining_minutes = remaining_sec / 60.0
|
||||||
f"禁止自动平仓(强制波段持仓纪律)"
|
logger.warning(
|
||||||
|
f"{symbol} [实时监控-持仓时间锁] ⚠ 持仓时间不足,禁止自动平仓: "
|
||||||
|
f"已持仓 {hold_time_minutes:.1f} 分钟 ({hold_time_sec}s) < "
|
||||||
|
f"最小要求 {min_hold_sec/60:.1f} 分钟 ({min_hold_sec}s) | "
|
||||||
|
f"还需等待 {remaining_minutes:.1f} 分钟 ({remaining_sec}s) | "
|
||||||
|
f"入场时间: {entry_time} | "
|
||||||
|
f"强制波段持仓纪律,避免分钟级平仓"
|
||||||
)
|
)
|
||||||
return # 不触发任何平仓逻辑
|
return # 不触发任何平仓逻辑
|
||||||
|
|
||||||
|
# 检查是否启用移动止损(默认False,需要显式启用)
|
||||||
use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False)
|
use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False)
|
||||||
|
if use_trailing:
|
||||||
|
logger.debug(f"{symbol} [实时监控-移动止损] 已启用,将检查移动止损逻辑")
|
||||||
|
else:
|
||||||
|
logger.debug(f"{symbol} [实时监控-移动止损] 已禁用(USE_TRAILING_STOP=False),跳过移动止损检查")
|
||||||
if use_trailing:
|
if use_trailing:
|
||||||
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金
|
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金
|
||||||
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金
|
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金
|
||||||
|
|
@ -2484,12 +2537,23 @@ class PositionManager:
|
||||||
if pnl_percent_margin <= -stop_loss_pct_margin:
|
if pnl_percent_margin <= -stop_loss_pct_margin:
|
||||||
should_close = True
|
should_close = True
|
||||||
exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
|
exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
|
||||||
logger.warning(
|
|
||||||
f"{symbol} [实时监控] ⚠⚠⚠ 触发止损(基于保证金): "
|
# 详细诊断日志:记录平仓时的所有关键信息
|
||||||
f"当前盈亏={pnl_percent_margin:.2f}% of margin <= 止损目标=-{stop_loss_pct_margin:.2f}% of margin | "
|
logger.warning("=" * 80)
|
||||||
f"当前价={current_price_float:.4f}, 止损价={stop_loss:.4f} | "
|
logger.warning(f"{symbol} [实时监控-平仓诊断日志] ===== 触发止损平仓 =====")
|
||||||
f"保证金={margin:.4f} USDT, 亏损金额={pnl_amount:.4f} USDT"
|
logger.warning(f" 平仓原因: {exit_reason}")
|
||||||
)
|
logger.warning(f" 入场价格: {entry_price:.6f} USDT")
|
||||||
|
logger.warning(f" 当前价格: {current_price_float:.6f} USDT")
|
||||||
|
logger.warning(f" 止损价格: {stop_loss:.4f} USDT")
|
||||||
|
logger.warning(f" 持仓数量: {quantity:.4f}")
|
||||||
|
logger.warning(f" 持仓时间: {hold_time_minutes:.1f} 分钟")
|
||||||
|
logger.warning(f" 入场时间: {entry_time}")
|
||||||
|
logger.warning(f" 当前盈亏: {pnl_percent_margin:.2f}% of margin")
|
||||||
|
logger.warning(f" 止损目标: -{stop_loss_pct_margin:.2f}% of margin")
|
||||||
|
logger.warning(f" 亏损金额: {abs(pnl_amount):.4f} USDT")
|
||||||
|
if position_info.get('trailingStopActivated'):
|
||||||
|
logger.warning(f" 移动止损: 已激活(从初始止损 {position_info.get('initialStopLoss', 'N/A')} 调整)")
|
||||||
|
logger.warning("=" * 80)
|
||||||
|
|
||||||
# 检查止盈(基于保证金收益比)
|
# 检查止盈(基于保证金收益比)
|
||||||
if not should_close:
|
if not should_close:
|
||||||
|
|
@ -2523,11 +2587,21 @@ class PositionManager:
|
||||||
if pnl_percent_margin >= take_profit_pct_margin:
|
if pnl_percent_margin >= take_profit_pct_margin:
|
||||||
should_close = True
|
should_close = True
|
||||||
exit_reason = 'take_profit'
|
exit_reason = 'take_profit'
|
||||||
logger.info(
|
|
||||||
f"{symbol} [实时监控] 触发止盈(基于保证金): "
|
# 详细诊断日志:记录平仓时的所有关键信息
|
||||||
f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 止盈目标={take_profit_pct_margin:.2f}% of margin | "
|
logger.info("=" * 80)
|
||||||
f"当前价={current_price_float:.4f}, 止盈价={take_profit:.4f}"
|
logger.info(f"{symbol} [实时监控-平仓诊断日志] ===== 触发止盈平仓 =====")
|
||||||
)
|
logger.info(f" 平仓原因: {exit_reason}")
|
||||||
|
logger.info(f" 入场价格: {entry_price:.6f} USDT")
|
||||||
|
logger.info(f" 当前价格: {current_price_float:.6f} USDT")
|
||||||
|
logger.info(f" 止盈价格: {take_profit:.4f} USDT")
|
||||||
|
logger.info(f" 持仓数量: {quantity:.4f}")
|
||||||
|
logger.info(f" 持仓时间: {hold_time_minutes:.1f} 分钟")
|
||||||
|
logger.info(f" 入场时间: {entry_time}")
|
||||||
|
logger.info(f" 当前盈亏: {pnl_percent_margin:.2f}% of margin")
|
||||||
|
logger.info(f" 止盈目标: {take_profit_pct_margin:.2f}% of margin")
|
||||||
|
logger.info(f" 盈利金额: {pnl_amount:.4f} USDT")
|
||||||
|
logger.info("=" * 80)
|
||||||
|
|
||||||
# 如果触发止损止盈,执行平仓
|
# 如果触发止损止盈,执行平仓
|
||||||
if should_close:
|
if should_close:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user