This commit is contained in:
薇薇安 2026-01-18 22:44:50 +08:00
parent 081deb8d62
commit 1b4b881eeb
3 changed files with 48 additions and 57 deletions

View File

@ -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,

View File

@ -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.055
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
// 50.05
if (label.includes('PERCENT')) {
// 55%0.05
// 1100
// 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) => {
// 使number0
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倍。注意高杠杆会增加爆仓风险请谨慎使用。',

View File

@ -979,10 +979,16 @@ class PositionManager:
logger.info(
f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f},剩余{position_info['remainingQuantity']:.4f}"
)
# 更新止损为成本价(保护剩余仓位)
position_info['stopLoss'] = entry_price
position_info['trailingStopActivated'] = True
logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润")
# 分步止盈后的“保本”处理:
# - 若启用 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} 剩余仓位止损移至成本价(保本),配合移动止损博取更大利润")
else:
logger.info(f"{symbol} 已部分止盈,但已关闭移动止损:不自动将止损移至成本价")
except Exception as e:
logger.error(f"{symbol} 部分止盈失败: {e}")