a
This commit is contained in:
parent
bb1490b909
commit
48e657e8cc
|
|
@ -375,11 +375,12 @@ async def close_position(symbol: str):
|
||||||
"status": "closed"
|
"status": "closed"
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠ {symbol} 平仓失败或持仓不存在")
|
# 即使返回False,也可能是币安没有持仓但数据库已更新,这种情况也算成功
|
||||||
|
logger.warning(f"⚠ {symbol} 平仓操作返回False,可能币安没有持仓或已平仓")
|
||||||
return {
|
return {
|
||||||
"message": f"{symbol} 平仓失败或持仓不存在",
|
"message": f"{symbol} 平仓操作完成(币安可能没有持仓或已平仓)",
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"status": "failed"
|
"status": "closed"
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
logger.info("断开币安API连接...")
|
logger.info("断开币安API连接...")
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,16 @@ async def update_config(key: str, item: ConfigUpdate):
|
||||||
# 更新配置
|
# 更新配置
|
||||||
TradingConfig.set(key, item.value, config_type, category, description)
|
TradingConfig.set(key, item.value, config_type, category, description)
|
||||||
|
|
||||||
|
# 清除配置缓存,确保立即生效
|
||||||
|
try:
|
||||||
|
from config_manager import ConfigManager
|
||||||
|
# 如果存在全局配置管理器实例,清除其缓存
|
||||||
|
import config_manager
|
||||||
|
if hasattr(config_manager, '_config_manager') and config_manager._config_manager:
|
||||||
|
config_manager._config_manager.reload()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"清除配置缓存失败: {e}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"message": "配置已更新",
|
"message": "配置已更新",
|
||||||
"key": key,
|
"key": key,
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,28 @@ async def get_dashboard_data():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"计算仓位占比信息失败: {e}")
|
logger.warning(f"计算仓位占比信息失败: {e}")
|
||||||
|
|
||||||
|
# 获取交易配置(用于前端显示止损止盈等参数)
|
||||||
|
trading_config = {}
|
||||||
|
try:
|
||||||
|
from database.models import TradingConfig
|
||||||
|
config_keys = ['STOP_LOSS_PERCENT', 'TAKE_PROFIT_PERCENT', 'LEVERAGE', 'MAX_POSITION_PERCENT']
|
||||||
|
for key in config_keys:
|
||||||
|
config = TradingConfig.get(key)
|
||||||
|
if config:
|
||||||
|
trading_config[key] = {
|
||||||
|
'value': TradingConfig._convert_value(config['config_value'], config['config_type']),
|
||||||
|
'type': config['config_type']
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"获取交易配置失败: {e}")
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"account": account_data,
|
"account": account_data,
|
||||||
"open_trades": open_trades,
|
"open_trades": open_trades,
|
||||||
"recent_scans": recent_scans,
|
"recent_scans": recent_scans,
|
||||||
"recent_signals": recent_signals,
|
"recent_signals": recent_signals,
|
||||||
"position_stats": position_stats
|
"position_stats": position_stats,
|
||||||
|
"trading_config": trading_config # 添加交易配置
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果有错误,在响应中包含错误信息(但不影响返回)
|
# 如果有错误,在响应中包含错误信息(但不影响返回)
|
||||||
|
|
|
||||||
|
|
@ -247,14 +247,36 @@ const ConfigPanel = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||||
|
// 初始化显示值:百分比配置转换为整数形式显示
|
||||||
|
const getInitialDisplayValue = (val) => {
|
||||||
|
if (config.type === 'number' && label.includes('PERCENT')) {
|
||||||
|
if (val === null || val === undefined || val === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const numVal = typeof val === 'string' ? parseFloat(val) : val
|
||||||
|
if (isNaN(numVal)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
// 如果是小数形式(0.05),转换为整数显示(5)
|
||||||
|
if (numVal < 1) {
|
||||||
|
return Math.round(numVal * 100)
|
||||||
|
}
|
||||||
|
// 如果已经是整数形式(5),直接显示
|
||||||
|
return numVal
|
||||||
|
}
|
||||||
|
return val === null || val === undefined ? '' : val
|
||||||
|
}
|
||||||
|
|
||||||
const [value, setValue] = useState(config.value)
|
const [value, setValue] = useState(config.value)
|
||||||
const [localValue, setLocalValue] = useState(config.value)
|
const [localValue, setLocalValue] = useState(getInitialDisplayValue(config.value))
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [showDetail, setShowDetail] = useState(false)
|
const [showDetail, setShowDetail] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(config.value)
|
setValue(config.value)
|
||||||
setLocalValue(config.value)
|
// 当配置值更新时,重置编辑状态和本地值
|
||||||
|
setIsEditing(false)
|
||||||
|
setLocalValue(getInitialDisplayValue(config.value))
|
||||||
}, [config.value])
|
}, [config.value])
|
||||||
|
|
||||||
const handleChange = (newValue) => {
|
const handleChange = (newValue) => {
|
||||||
|
|
@ -264,24 +286,47 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
if (localValue !== value) {
|
|
||||||
// 值发生变化,保存
|
// 处理localValue可能是字符串的情况
|
||||||
let finalValue = localValue
|
let processedValue = localValue
|
||||||
if (config.type === 'number') {
|
if (config.type === 'number') {
|
||||||
const numValue = parseFloat(localValue)
|
// 如果是空字符串,恢复原值
|
||||||
if (isNaN(numValue)) {
|
if (localValue === '' || localValue === null || localValue === undefined) {
|
||||||
// 如果输入为空或无效,不保存
|
setLocalValue(value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
finalValue = numValue
|
|
||||||
|
const numValue = typeof localValue === 'string' ? parseFloat(localValue) : localValue
|
||||||
|
if (isNaN(numValue)) {
|
||||||
|
// 如果输入无效,恢复原值
|
||||||
|
const restoreValue = label.includes('PERCENT')
|
||||||
|
? (typeof value === 'number' && value < 1 ? Math.round(value * 100) : value)
|
||||||
|
: value
|
||||||
|
setLocalValue(restoreValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processedValue = numValue
|
||||||
// 百分比配置需要转换:用户输入的是整数(如5),需要转换为小数(0.05)
|
// 百分比配置需要转换:用户输入的是整数(如5),需要转换为小数(0.05)
|
||||||
if (label.includes('PERCENT')) {
|
if (label.includes('PERCENT')) {
|
||||||
finalValue = finalValue / 100
|
// 用户输入的是整数形式(如5表示5%),需要转换为小数(0.05)
|
||||||
|
// 如果输入值大于等于1,说明是百分比形式,需要除以100
|
||||||
|
// 如果输入值小于1,说明已经是小数形式,直接使用
|
||||||
|
if (processedValue >= 1) {
|
||||||
|
processedValue = processedValue / 100
|
||||||
|
}
|
||||||
|
// 如果小于1,说明已经是小数形式,直接使用
|
||||||
}
|
}
|
||||||
} else if (config.type === 'boolean') {
|
} else if (config.type === 'boolean') {
|
||||||
finalValue = localValue === 'true' || localValue === true
|
processedValue = localValue === 'true' || localValue === true
|
||||||
}
|
}
|
||||||
onUpdate(finalValue)
|
|
||||||
|
// 只有当值真正发生变化时才保存
|
||||||
|
if (processedValue !== value) {
|
||||||
|
onUpdate(processedValue)
|
||||||
|
} else {
|
||||||
|
// 值没变化,但需要更新显示值
|
||||||
|
setValue(processedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,9 +344,23 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示值:百分比配置显示为整数(如5),其他保持原样
|
// 显示值:百分比配置显示为整数(如5),其他保持原样
|
||||||
const displayValue = config.type === 'number' && label.includes('PERCENT')
|
// 处理localValue可能是字符串的情况
|
||||||
? (localValue === '' || isNaN(localValue) ? '' : Math.round(localValue * 100))
|
const getDisplayValue = () => {
|
||||||
: localValue
|
if (config.type === 'number' && label.includes('PERCENT')) {
|
||||||
|
if (localValue === '' || localValue === null || localValue === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const numValue = typeof localValue === 'string' ? parseFloat(localValue) : localValue
|
||||||
|
if (isNaN(numValue)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
// localValue已经是显示值(整数形式),直接返回
|
||||||
|
return numValue
|
||||||
|
}
|
||||||
|
return localValue === null || localValue === undefined ? '' : localValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayValue = getDisplayValue()
|
||||||
|
|
||||||
if (config.type === 'boolean') {
|
if (config.type === 'boolean') {
|
||||||
return (
|
return (
|
||||||
|
|
@ -408,30 +467,79 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||||
)}
|
)}
|
||||||
<div className="config-input-wrapper">
|
<div className="config-input-wrapper">
|
||||||
<input
|
<input
|
||||||
type={config.type === 'number' ? 'number' : 'text'}
|
type="text"
|
||||||
value={label.includes('PERCENT') ? (displayValue === '' ? '' : displayValue) : (localValue === '' ? '' : localValue)}
|
inputMode={config.type === 'number' ? 'decimal' : 'text'}
|
||||||
|
value={label.includes('PERCENT')
|
||||||
|
? (displayValue === '' || displayValue === null || displayValue === undefined ? '' : String(displayValue))
|
||||||
|
: (localValue === '' || localValue === null || localValue === undefined ? '' : String(localValue))}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
// 百分比配置:用户直接输入整数(如5),不转换,保存时再转换
|
// 使用文本输入,避免number类型的自动补0问题
|
||||||
// 其他数字配置:直接使用输入值
|
let newValue = e.target.value
|
||||||
let newValue
|
|
||||||
if (config.type === 'number' && label.includes('PERCENT')) {
|
// 对于数字类型,只允许数字、小数点和负号
|
||||||
// 百分比配置:保持整数形式,允许空值
|
if (config.type === 'number') {
|
||||||
const numValue = parseFloat(e.target.value)
|
// 允许空字符串、数字、小数点和负号
|
||||||
newValue = isNaN(numValue) ? '' : numValue
|
const validPattern = /^-?\d*\.?\d*$/
|
||||||
} else if (config.type === 'number') {
|
if (newValue !== '' && !validPattern.test(newValue)) {
|
||||||
// 其他数字配置:允许空值
|
// 无效输入,不更新
|
||||||
const numValue = parseFloat(e.target.value)
|
return
|
||||||
newValue = isNaN(numValue) ? '' : numValue
|
|
||||||
} else {
|
|
||||||
newValue = e.target.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是百分比配置,限制输入范围(0-100)
|
||||||
|
if (label.includes('PERCENT')) {
|
||||||
|
const numValue = parseFloat(newValue)
|
||||||
|
if (newValue !== '' && !isNaN(numValue) && (numValue < 0 || numValue > 100)) {
|
||||||
|
// 超出范围,不更新
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地值
|
||||||
|
if (config.type === 'number' && label.includes('PERCENT')) {
|
||||||
|
// 百分比配置:保持数字形式(整数),允许空值
|
||||||
|
if (newValue === '') {
|
||||||
|
handleChange('')
|
||||||
|
} else {
|
||||||
|
const numValue = parseFloat(newValue)
|
||||||
|
if (!isNaN(numValue)) {
|
||||||
|
handleChange(numValue)
|
||||||
|
}
|
||||||
|
// 如果无效,不更新(保持原值)
|
||||||
|
}
|
||||||
|
} else if (config.type === 'number') {
|
||||||
|
// 其他数字配置:保持数字形式,允许空值
|
||||||
|
if (newValue === '') {
|
||||||
|
handleChange('')
|
||||||
|
} else {
|
||||||
|
const numValue = parseFloat(newValue)
|
||||||
|
if (!isNaN(numValue)) {
|
||||||
|
handleChange(numValue)
|
||||||
|
}
|
||||||
|
// 如果无效,不更新(保持原值)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
handleChange(newValue)
|
handleChange(newValue)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
// 允许删除、退格、方向键等
|
||||||
|
if (['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 对于数字输入,只允许数字、小数点和负号
|
||||||
|
if (config.type === 'number') {
|
||||||
|
const validKeys = /^[0-9.-]$/
|
||||||
|
if (!validKeys.test(e.key) && !['Enter', 'Escape'].includes(e.key)) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
step={config.type === 'number' && label.includes('PERCENT') ? '1' : (config.type === 'number' ? '0.01' : undefined)}
|
|
||||||
className={isEditing ? 'editing' : ''}
|
className={isEditing ? 'editing' : ''}
|
||||||
|
placeholder={label.includes('PERCENT') ? '输入百分比' : '输入数值'}
|
||||||
/>
|
/>
|
||||||
{label.includes('PERCENT') && <span className="percent-suffix">%</span>}
|
{label.includes('PERCENT') && <span className="percent-suffix">%</span>}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,35 @@ const StatsDashboard = () => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [closingSymbol, setClosingSymbol] = useState(null)
|
const [closingSymbol, setClosingSymbol] = useState(null)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
|
const [tradingConfig, setTradingConfig] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDashboard()
|
loadDashboard()
|
||||||
const interval = setInterval(loadDashboard, 30000) // 每30秒刷新
|
loadTradingConfig()
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
loadDashboard()
|
||||||
|
loadTradingConfig() // 同时刷新配置
|
||||||
|
}, 30000) // 每30秒刷新
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const loadTradingConfig = async () => {
|
||||||
|
try {
|
||||||
|
const configs = await api.getConfigs()
|
||||||
|
setTradingConfig(configs)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load trading config:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadDashboard = async () => {
|
const loadDashboard = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.getDashboard()
|
const data = await api.getDashboard()
|
||||||
setDashboardData(data)
|
setDashboardData(data)
|
||||||
|
// 如果dashboard数据中包含配置,也更新配置状态
|
||||||
|
if (data.trading_config) {
|
||||||
|
setTradingConfig(data.trading_config)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load dashboard:', error)
|
console.error('Failed to load dashboard:', error)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -183,10 +201,38 @@ const StatsDashboard = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认止损止盈比例(从配置获取,如果没有则使用默认值)
|
// 从配置获取止损止盈比例(优先使用dashboard返回的配置,其次使用单独加载的配置)
|
||||||
// 注意:这里使用默认值,实际止损止盈可能是动态计算的
|
const configSource = dashboardData?.trading_config || tradingConfig
|
||||||
const defaultStopLossPercent = 3.0 // 默认3%
|
const stopLossConfig = configSource?.STOP_LOSS_PERCENT
|
||||||
const defaultTakeProfitPercent = 5.0 // 默认5%
|
const takeProfitConfig = configSource?.TAKE_PROFIT_PERCENT
|
||||||
|
|
||||||
|
// 配置值是小数形式(0.03表示3%),需要转换为百分比
|
||||||
|
let defaultStopLossPercent = 3.0 // 默认3%
|
||||||
|
let defaultTakeProfitPercent = 5.0 // 默认5%
|
||||||
|
|
||||||
|
if (stopLossConfig) {
|
||||||
|
const configValue = stopLossConfig.value
|
||||||
|
if (typeof configValue === 'number') {
|
||||||
|
defaultStopLossPercent = configValue * 100
|
||||||
|
} else {
|
||||||
|
const parsed = parseFloat(configValue)
|
||||||
|
if (!isNaN(parsed)) {
|
||||||
|
defaultStopLossPercent = parsed * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeProfitConfig) {
|
||||||
|
const configValue = takeProfitConfig.value
|
||||||
|
if (typeof configValue === 'number') {
|
||||||
|
defaultTakeProfitPercent = configValue * 100
|
||||||
|
} else {
|
||||||
|
const parsed = parseFloat(configValue)
|
||||||
|
if (!isNaN(parsed)) {
|
||||||
|
defaultTakeProfitPercent = parsed * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算止损价和止盈价(基于默认比例)
|
// 计算止损价和止盈价(基于默认比例)
|
||||||
let stopLossPrice = 0
|
let stopLossPrice = 0
|
||||||
|
|
|
||||||
|
|
@ -592,13 +592,8 @@ class BinanceClient:
|
||||||
订单信息
|
订单信息
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 获取交易对精度信息并调整数量
|
# 获取交易对精度信息
|
||||||
symbol_info = await self.get_symbol_info(symbol)
|
symbol_info = await self.get_symbol_info(symbol)
|
||||||
adjusted_quantity = self._adjust_quantity_precision(quantity, symbol_info)
|
|
||||||
|
|
||||||
if adjusted_quantity <= 0:
|
|
||||||
logger.error(f"调整后的数量无效: {adjusted_quantity} (原始: {quantity})")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 获取当前价格以计算名义价值
|
# 获取当前价格以计算名义价值
|
||||||
if price is None:
|
if price is None:
|
||||||
|
|
@ -610,10 +605,20 @@ class BinanceClient:
|
||||||
else:
|
else:
|
||||||
current_price = price
|
current_price = price
|
||||||
|
|
||||||
# 计算订单名义价值
|
# 先按原始数量计算名义价值,用于保证金检查
|
||||||
notional_value = adjusted_quantity * current_price
|
initial_notional_value = quantity * current_price
|
||||||
min_notional = symbol_info.get('minNotional', 5.0) if symbol_info else 5.0
|
min_notional = symbol_info.get('minNotional', 5.0) if symbol_info else 5.0
|
||||||
|
|
||||||
|
# 调整数量精度(在保证金检查之前)
|
||||||
|
adjusted_quantity = self._adjust_quantity_precision(quantity, symbol_info)
|
||||||
|
|
||||||
|
if adjusted_quantity <= 0:
|
||||||
|
logger.error(f"调整后的数量无效: {adjusted_quantity} (原始: {quantity})")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 使用调整后的数量重新计算名义价值
|
||||||
|
notional_value = adjusted_quantity * current_price
|
||||||
|
|
||||||
logger.info(f"下单检查: {symbol} {side} {adjusted_quantity} (原始: {quantity}) @ {order_type}")
|
logger.info(f"下单检查: {symbol} {side} {adjusted_quantity} (原始: {quantity}) @ {order_type}")
|
||||||
logger.info(f" 当前价格: {current_price:.4f} USDT")
|
logger.info(f" 当前价格: {current_price:.4f} USDT")
|
||||||
logger.info(f" 订单名义价值: {notional_value:.2f} USDT")
|
logger.info(f" 订单名义价值: {notional_value:.2f} USDT")
|
||||||
|
|
@ -644,24 +649,37 @@ class BinanceClient:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"无法获取 {symbol} 的杠杆信息,使用默认值: {current_leverage}x ({e})")
|
logger.debug(f"无法获取 {symbol} 的杠杆信息,使用默认值: {current_leverage}x ({e})")
|
||||||
|
|
||||||
min_margin_usdt = config.TRADING_CONFIG.get('MIN_MARGIN_USDT', 1.0)
|
min_margin_usdt = config.TRADING_CONFIG.get('MIN_MARGIN_USDT', 0.5) # 默认0.5 USDT
|
||||||
required_margin = notional_value / current_leverage
|
required_margin = notional_value / current_leverage
|
||||||
|
|
||||||
if required_margin < min_margin_usdt:
|
if required_margin < min_margin_usdt:
|
||||||
|
# 如果保证金不足,自动调整到最小保证金要求
|
||||||
|
required_notional_value = min_margin_usdt * current_leverage
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"❌ {symbol} 订单保证金不足: {required_margin:.4f} USDT < "
|
f"⚠ {symbol} 订单保证金不足: {required_margin:.4f} USDT < "
|
||||||
f"最小保证金要求: {min_margin_usdt:.2f} USDT"
|
f"最小保证金要求: {min_margin_usdt:.2f} USDT"
|
||||||
)
|
)
|
||||||
logger.warning(
|
logger.info(
|
||||||
f" 订单名义价值: {notional_value:.2f} USDT, "
|
f" 自动调整订单名义价值: {notional_value:.2f} USDT -> {required_notional_value:.2f} USDT "
|
||||||
f"杠杆: {current_leverage}x, "
|
f"(杠杆: {current_leverage}x, 保证金: {min_margin_usdt:.2f} USDT)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 调整数量以满足最小保证金要求
|
||||||
|
if current_price > 0:
|
||||||
|
new_quantity = required_notional_value / current_price
|
||||||
|
# 调整到符合精度要求
|
||||||
|
adjusted_quantity = self._adjust_quantity_precision(new_quantity, symbol_info)
|
||||||
|
# 重新计算名义价值和保证金
|
||||||
|
notional_value = adjusted_quantity * current_price
|
||||||
|
required_margin = notional_value / current_leverage
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f" ✓ 调整数量: {quantity:.4f} -> {adjusted_quantity:.4f}, "
|
||||||
|
f"名义价值: {notional_value:.2f} USDT, "
|
||||||
f"保证金: {required_margin:.4f} USDT"
|
f"保证金: {required_margin:.4f} USDT"
|
||||||
)
|
)
|
||||||
logger.warning(
|
else:
|
||||||
f" 需要的最小名义价值: {min_margin_usdt * current_leverage:.2f} USDT "
|
logger.error(f" ❌ 无法获取 {symbol} 的当前价格,无法调整订单大小")
|
||||||
f"(最小保证金 {min_margin_usdt:.2f} USDT × 杠杆 {current_leverage}x)"
|
|
||||||
)
|
|
||||||
logger.warning(f" 跳过此订单,避免手续费侵蚀收益")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ def _get_trading_config():
|
||||||
'MAX_POSITION_PERCENT': 0.05,
|
'MAX_POSITION_PERCENT': 0.05,
|
||||||
'MAX_TOTAL_POSITION_PERCENT': 0.30,
|
'MAX_TOTAL_POSITION_PERCENT': 0.30,
|
||||||
'MIN_POSITION_PERCENT': 0.01,
|
'MIN_POSITION_PERCENT': 0.01,
|
||||||
'MIN_MARGIN_USDT': 1.0, # 最小保证金要求(USDT),避免手续费侵蚀收益(提高到1.0以确保有足够收益)
|
'MIN_MARGIN_USDT': 0.5, # 最小保证金要求(USDT),如果保证金小于此值,自动调整到0.5U保证金
|
||||||
'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松)
|
'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松)
|
||||||
'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐)
|
'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐)
|
||||||
'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有)
|
'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有)
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,7 @@ class PositionManager:
|
||||||
if not position:
|
if not position:
|
||||||
logger.warning(f"{symbol} [平仓] 币安账户中没有持仓,可能已被平仓")
|
logger.warning(f"{symbol} [平仓] 币安账户中没有持仓,可能已被平仓")
|
||||||
# 即使币安没有持仓,也要更新数据库状态
|
# 即使币安没有持仓,也要更新数据库状态
|
||||||
|
updated = False
|
||||||
if DB_AVAILABLE and Trade and symbol in self.active_positions:
|
if DB_AVAILABLE and Trade and symbol in self.active_positions:
|
||||||
position_info = self.active_positions[symbol]
|
position_info = self.active_positions[symbol]
|
||||||
trade_id = position_info.get('tradeId')
|
trade_id = position_info.get('tradeId')
|
||||||
|
|
@ -315,6 +316,7 @@ class PositionManager:
|
||||||
exit_order_id=None # 同步平仓时没有订单号
|
exit_order_id=None # 同步平仓时没有订单号
|
||||||
)
|
)
|
||||||
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
|
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
|
||||||
|
updated = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {e}")
|
logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {e}")
|
||||||
|
|
||||||
|
|
@ -323,7 +325,8 @@ class PositionManager:
|
||||||
if symbol in self.active_positions:
|
if symbol in self.active_positions:
|
||||||
del self.active_positions[symbol]
|
del self.active_positions[symbol]
|
||||||
|
|
||||||
return False
|
# 如果更新了数据库,返回成功;否则返回失败
|
||||||
|
return updated
|
||||||
|
|
||||||
# 确定平仓方向(与开仓相反)
|
# 确定平仓方向(与开仓相反)
|
||||||
position_amt = position['positionAmt']
|
position_amt = position['positionAmt']
|
||||||
|
|
|
||||||
|
|
@ -302,8 +302,8 @@ class RiskManager:
|
||||||
quantity = required_quantity
|
quantity = required_quantity
|
||||||
position_value = required_quantity * current_price
|
position_value = required_quantity * current_price
|
||||||
|
|
||||||
# 检查最小保证金要求(避免手续费侵蚀收益)
|
# 检查最小保证金要求(如果保证金小于此值,自动调整到0.5U保证金)
|
||||||
min_margin_usdt = self.config.get('MIN_MARGIN_USDT', 1.0) # 默认1.0 USDT(提高到1.0以确保有足够收益)
|
min_margin_usdt = self.config.get('MIN_MARGIN_USDT', 0.5) # 默认0.5 USDT
|
||||||
# 使用传入的实际杠杆,如果没有传入则使用配置的基础杠杆
|
# 使用传入的实际杠杆,如果没有传入则使用配置的基础杠杆
|
||||||
actual_leverage = leverage if leverage is not None else self.config.get('LEVERAGE', 10)
|
actual_leverage = leverage if leverage is not None else self.config.get('LEVERAGE', 10)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user