a
This commit is contained in:
parent
081deb8d62
commit
1b4b881eeb
|
|
@ -379,7 +379,8 @@ async def update_config(key: str, item: ConfigUpdate):
|
|||
item.value = bool(item.value)
|
||||
|
||||
# 特殊验证:百分比配置应该在0-1之间
|
||||
if 'PERCENT' in key and config_type == 'number':
|
||||
# 兼容:PERCENT / PCT
|
||||
if ('PERCENT' in key or 'PCT' in key) and config_type == 'number':
|
||||
if not (0 <= float(item.value) <= 1):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
|
|
|||
|
|
@ -765,9 +765,17 @@ const ConfigPanel = () => {
|
|||
}
|
||||
|
||||
const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||
// 初始化显示值:百分比配置转换为整数形式显示
|
||||
const isPercentKey = label.includes('PERCENT') || label.includes('PCT')
|
||||
|
||||
const formatPercent = (n) => {
|
||||
// 最多 4 位小数,去掉尾随 0(例如 2.3000 -> 2.3)
|
||||
if (typeof n !== 'number' || isNaN(n)) return ''
|
||||
return n.toFixed(4).replace(/\.?0+$/, '')
|
||||
}
|
||||
|
||||
// 初始化显示值:百分比配置转为“百分比数值”(支持小数)
|
||||
const getInitialDisplayValue = (val) => {
|
||||
if (config.type === 'number' && label.includes('PERCENT')) {
|
||||
if (config.type === 'number' && isPercentKey) {
|
||||
if (val === null || val === undefined || val === '') {
|
||||
return ''
|
||||
}
|
||||
|
|
@ -775,12 +783,9 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
if (isNaN(numVal)) {
|
||||
return ''
|
||||
}
|
||||
// 如果是小数形式(0.05),转换为整数显示(5)
|
||||
if (numVal < 1) {
|
||||
return Math.round(numVal * 100)
|
||||
}
|
||||
// 如果已经是整数形式(5),直接显示
|
||||
return numVal
|
||||
// 存储为 0~1,小于等于 1 则换算成百分比;异常旧值则原样展示
|
||||
const percent = numVal <= 1 ? numVal * 100 : numVal
|
||||
return formatPercent(percent)
|
||||
}
|
||||
return val === null || val === undefined ? '' : val
|
||||
}
|
||||
|
|
@ -823,8 +828,8 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
numValue = parseFloat(localValue)
|
||||
} else if (localValue === '.' || localValue === '-.') {
|
||||
// "." 或 "-." 视为无效,恢复原值
|
||||
const restoreValue = label.includes('PERCENT')
|
||||
? (typeof value === 'number' && value < 1 ? Math.round(value * 100) : value)
|
||||
const restoreValue = isPercentKey
|
||||
? (typeof value === 'number' ? formatPercent((value <= 1 ? value * 100 : value)) : value)
|
||||
: value
|
||||
setLocalValue(restoreValue)
|
||||
return
|
||||
|
|
@ -837,23 +842,22 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
|
||||
if (isNaN(numValue)) {
|
||||
// 如果输入无效,恢复原值
|
||||
const restoreValue = label.includes('PERCENT')
|
||||
? (typeof value === 'number' && value < 1 ? Math.round(value * 100) : value)
|
||||
const restoreValue = isPercentKey
|
||||
? (typeof value === 'number' ? formatPercent((value <= 1 ? value * 100 : value)) : value)
|
||||
: value
|
||||
setLocalValue(restoreValue)
|
||||
return
|
||||
}
|
||||
|
||||
processedValue = numValue
|
||||
// 百分比配置需要转换:用户输入的是整数(如5),需要转换为小数(0.05)
|
||||
if (label.includes('PERCENT')) {
|
||||
// 用户输入的是整数形式(如5表示5%),需要转换为小数(0.05)
|
||||
// 如果输入值大于等于1,说明是百分比形式,需要除以100
|
||||
// 如果输入值小于1,说明已经是小数形式,直接使用
|
||||
if (processedValue >= 1) {
|
||||
processedValue = processedValue / 100
|
||||
// 百分比配置:前端统一按“百分比”输入(支持小数),存储为 0~1
|
||||
if (isPercentKey) {
|
||||
// 限制范围 0-100
|
||||
if (processedValue < 0 || processedValue > 100) {
|
||||
setLocalValue(getInitialDisplayValue(value))
|
||||
return
|
||||
}
|
||||
// 如果小于1,说明已经是小数形式,直接使用
|
||||
processedValue = processedValue / 100
|
||||
}
|
||||
} else if (config.type === 'boolean') {
|
||||
processedValue = localValue === 'true' || localValue === true
|
||||
|
|
@ -881,24 +885,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
}
|
||||
}
|
||||
|
||||
// 显示值:百分比配置显示为整数(如5),其他保持原样
|
||||
// 处理localValue可能是字符串的情况
|
||||
const getDisplayValue = () => {
|
||||
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()
|
||||
const displayValue = localValue === null || localValue === undefined ? '' : localValue
|
||||
|
||||
if (config.type === 'boolean') {
|
||||
return (
|
||||
|
|
@ -1007,9 +994,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
<input
|
||||
type="text"
|
||||
inputMode={config.type === 'number' ? 'decimal' : 'text'}
|
||||
value={label.includes('PERCENT')
|
||||
? (displayValue === '' || displayValue === null || displayValue === undefined ? '' : String(displayValue))
|
||||
: (localValue === '' || localValue === null || localValue === undefined ? '' : String(localValue))}
|
||||
value={displayValue === '' ? '' : String(displayValue)}
|
||||
onChange={(e) => {
|
||||
// 使用文本输入,避免number类型的自动补0问题
|
||||
let newValue = e.target.value
|
||||
|
|
@ -1024,7 +1009,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
// }
|
||||
|
||||
// 如果是百分比配置,限制输入范围(0-100)
|
||||
if (label.includes('PERCENT')) {
|
||||
if (isPercentKey) {
|
||||
const numValue = parseFloat(newValue)
|
||||
if (newValue !== '' && !isNaN(numValue) && (numValue < 0 || numValue > 100)) {
|
||||
// 超出范围,不更新
|
||||
|
|
@ -1034,16 +1019,15 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
}
|
||||
|
||||
// 更新本地值
|
||||
if (config.type === 'number' && label.includes('PERCENT')) {
|
||||
// 百分比配置:保持数字形式(整数),允许空值
|
||||
if (config.type === 'number' && isPercentKey) {
|
||||
// 百分比配置:允许字符串中间态(如 "2."),并支持小数
|
||||
if (newValue === '') {
|
||||
handleChange('')
|
||||
} else {
|
||||
const numValue = parseFloat(newValue)
|
||||
if (!isNaN(numValue)) {
|
||||
handleChange(numValue)
|
||||
const validPattern = /^-?\d*\.?\d*$/
|
||||
if (validPattern.test(newValue)) {
|
||||
handleChange(newValue)
|
||||
}
|
||||
// 如果无效,不更新(保持原值)
|
||||
}
|
||||
} else if (config.type === 'number') {
|
||||
// 其他数字配置:允许字符串形式的中间状态(如 "0." 或 "."),支持小数输入
|
||||
|
|
@ -1091,9 +1075,9 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
}}
|
||||
disabled={disabled}
|
||||
className={isEditing ? 'editing' : ''}
|
||||
placeholder={label.includes('PERCENT') ? '输入百分比' : '输入数值'}
|
||||
placeholder={isPercentKey ? '输入百分比(可小数)' : '输入数值'}
|
||||
/>
|
||||
{label.includes('PERCENT') && <span className="percent-suffix">%</span>}
|
||||
{isPercentKey && <span className="percent-suffix">%</span>}
|
||||
{isEditing && (
|
||||
<button
|
||||
className="save-btn"
|
||||
|
|
@ -1139,8 +1123,8 @@ const getConfigDetail = (key) => {
|
|||
// 风险控制参数
|
||||
'STOP_LOSS_PERCENT': '止损百分比(如0.08表示8%,相对于保证金)。当亏损达到此百分比时自动平仓止损,限制单笔交易的最大亏损。值越小止损更严格,单笔损失更小但可能被正常波动触发。值越大允许更大的回撤,但单笔损失可能较大。建议:保守策略10-15%,平衡策略8-10%,激进策略5-8%。注意:止损应该小于止盈,建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
|
||||
'TAKE_PROFIT_PERCENT': '止盈百分比(如0.15表示15%,相对于保证金)。当盈利达到此百分比时自动平仓止盈,锁定利润。值越大目标利润更高,但可能错过及时止盈的机会,持仓时间更长。值越小能更快锁定利润,但可能错过更大的趋势。建议:保守策略20-30%,平衡策略15-20%,激进策略10-15%。注意:应该大于止损,建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
|
||||
'MIN_STOP_LOSS_PRICE_PCT': '最小止损价格变动百分比(如0.02表示2%)。防止止损过紧,即使基于保证金的止损更紧,也会使用至少此百分比的价格变动。建议:保守策略2-3%,平衡策略2%,激进策略1.5-2%。',
|
||||
'MIN_TAKE_PROFIT_PRICE_PCT': '最小止盈价格变动百分比(如0.03表示3%)。防止止盈过紧,即使基于保证金的止盈更紧,也会使用至少此百分比的价格变动。建议:保守策略3-4%,平衡策略3%,激进策略2-3%。',
|
||||
'MIN_STOP_LOSS_PRICE_PCT': '最小止损价格变动百分比(存储为0~1,前端输入按%显示,支持小数,如1.5%)。用于防止止损过紧:即使基于保证金的止损更紧,也会使用至少该百分比的价格变动。建议:1.5%-3%。',
|
||||
'MIN_TAKE_PROFIT_PRICE_PCT': '最小止盈价格变动百分比(存储为0~1,前端输入按%显示,支持小数,如2.5%)。用于防止止盈过紧:即使基于保证金的止盈更紧,也会使用至少该百分比的价格变动。建议:2%-4%。',
|
||||
|
||||
// 策略参数
|
||||
'LEVERAGE': '交易杠杆倍数。放大资金利用率,同时放大收益和风险。杠杆越高,相同仓位下需要的保证金越少,但风险越大。建议:保守策略5-10倍,平衡策略10倍,激进策略10-15倍。注意:高杠杆会增加爆仓风险,请谨慎使用。',
|
||||
|
|
|
|||
|
|
@ -979,10 +979,16 @@ class PositionManager:
|
|||
logger.info(
|
||||
f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f},剩余{position_info['remainingQuantity']:.4f}"
|
||||
)
|
||||
# 更新止损为成本价(保护剩余仓位)
|
||||
# 分步止盈后的“保本”处理:
|
||||
# - 若启用 USE_TRAILING_STOP:允许把剩余仓位止损移至成本价,并进入移动止损阶段
|
||||
# - 若关闭 USE_TRAILING_STOP:严格不自动移动止损(避免你说的“仍然保本/仍然移动止损”)
|
||||
use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', True)
|
||||
if use_trailing:
|
||||
position_info['stopLoss'] = entry_price
|
||||
position_info['trailingStopActivated'] = True
|
||||
logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润")
|
||||
logger.info(f"{symbol} 剩余仓位止损移至成本价(保本),配合移动止损博取更大利润")
|
||||
else:
|
||||
logger.info(f"{symbol} 已部分止盈,但已关闭移动止损:不自动将止损移至成本价")
|
||||
except Exception as e:
|
||||
logger.error(f"{symbol} 部分止盈失败: {e}")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user