feat(config): Introduce layered profit locking mechanism and manual trading adjustments

Added new configurations for a layered profit locking system, allowing for gradual profit protection at specified thresholds. Introduced manual trading options, including reduced and blocked symbol lists, to enhance trading strategy flexibility. Updated relevant backend and frontend components to reflect these changes, improving risk management and user control over trading activities.
This commit is contained in:
薇薇安 2026-03-09 14:51:08 +08:00
parent 218ab7e195
commit 1446bf852b
8 changed files with 446 additions and 24 deletions

View File

@ -83,6 +83,10 @@ USER_VISIBLE_DEFAULTS = {
"EXCLUDE_MAJOR_COINS": {"value": True, "type": "boolean", "category": "scan", "description": "是否排除主流币BTC/ETH 等)"}, "EXCLUDE_MAJOR_COINS": {"value": True, "type": "boolean", "category": "scan", "description": "是否排除主流币BTC/ETH 等)"},
"MAX_SCAN_SYMBOLS": {"value": 1500, "type": "number", "category": "scan", "description": "最大扫描交易对数量"}, "MAX_SCAN_SYMBOLS": {"value": 1500, "type": "number", "category": "scan", "description": "最大扫描交易对数量"},
"SCAN_INTERVAL": {"value": 1800, "type": "number", "category": "scan", "description": "市场扫描间隔(秒)"}, "SCAN_INTERVAL": {"value": 1800, "type": "number", "category": "scan", "description": "市场扫描间隔(秒)"},
"MANUAL_REDUCED_SYMBOLS": {"value": "", "type": "string", "category": "strategy", "description": "手动减仓交易对列表,逗号/空格/换行分隔。命中后缩小仓位并提高自动交易门槛。"},
"MANUAL_BLOCKED_SYMBOLS": {"value": "", "type": "string", "category": "strategy", "description": "手动停做交易对列表,逗号/空格/换行分隔。命中后禁止自动开仓。"},
"MANUAL_REDUCED_SYMBOL_POSITION_FACTOR": {"value": 0.5, "type": "number", "category": "strategy", "description": "手动减仓交易对的仓位系数。0.5 表示只开正常仓位的一半。"},
"MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST": {"value": 1, "type": "number", "category": "strategy", "description": "手动减仓交易对额外提高的最小信号门槛。1 表示在当前基础上 +1。"},
} }
RISK_KNOBS_DEFAULTS = { RISK_KNOBS_DEFAULTS = {
@ -214,12 +218,30 @@ AUTO_TRADE_FILTER_DEFAULTS = {
"category": "strategy", "category": "strategy",
"description": "自动交易仅在市场状态=trending时执行ranging/unknown只生成推荐不自动下单。用于显著降低震荡扫损与交易次数。", "description": "自动交易仅在市场状态=trending时执行ranging/unknown只生成推荐不自动下单。用于显著降低震荡扫损与交易次数。",
}, },
"AUTO_TRADE_ALLOW_RANGING": {
"value": False,
"type": "boolean",
"category": "strategy",
"description": "当不强制 only_trending 时,是否允许在震荡市自动交易。默认关闭,避免在横盘里来回扫损。",
},
"AUTO_TRADE_ALLOW_UNKNOWN": {
"value": False,
"type": "boolean",
"category": "strategy",
"description": "当市场状态=unknownK线不足或未判定是否允许自动交易。默认关闭更稳健。",
},
"AUTO_TRADE_ALLOW_4H_NEUTRAL": { "AUTO_TRADE_ALLOW_4H_NEUTRAL": {
"value": False, "value": False,
"type": "boolean", "type": "boolean",
"category": "strategy", "category": "strategy",
"description": "是否允许4H趋势=neutral时自动交易。默认关闭中性趋势最容易被扫损若你希望更积极可开启。", "description": "是否允许4H趋势=neutral时自动交易。默认关闭中性趋势最容易被扫损若你希望更积极可开启。",
}, },
"RANGING_MARKET_SIGNAL_BOOST": {
"value": 2,
"type": "number",
"category": "strategy",
"description": "震荡市额外提高的最小信号门槛。比如 2 表示在基础 MIN_SIGNAL_STRENGTH 上再 +2。",
},
} }
# 风险/策略预设(用于一键切换“稳健 / 快速验证”等模式) # 风险/策略预设(用于一键切换“稳健 / 快速验证”等模式)
@ -544,6 +566,30 @@ async def get_global_configs(
"category": "strategy", "category": "strategy",
"description": "盈利达保证金的该比例时移至保本(如 0.03=3%0=关闭保本步骤)", "description": "盈利达保证金的该比例时移至保本(如 0.03=3%0=关闭保本步骤)",
}, },
"LOCK_PROFIT_STAGE1_TRIGGER_PCT": {
"value": 0.08,
"type": "number",
"category": "strategy",
"description": "分层锁盈第一层触发阈值(相对保证金,如 0.08=8%)。达到后将止损从保本抬到小幅锁利。",
},
"LOCK_PROFIT_STAGE1_PCT": {
"value": 0.02,
"type": "number",
"category": "strategy",
"description": "分层锁盈第一层锁利比例(相对保证金,如 0.02=2%)。",
},
"LOCK_PROFIT_STAGE2_TRIGGER_PCT": {
"value": 0.15,
"type": "number",
"category": "strategy",
"description": "分层锁盈第二层触发阈值(相对保证金,如 0.15=15%)。达到后进一步抬高止损。",
},
"LOCK_PROFIT_STAGE2_PCT": {
"value": 0.05,
"type": "number",
"category": "strategy",
"description": "分层锁盈第二层锁利比例(相对保证金,如 0.05=5%)。",
},
"USE_TRAILING_STOP": { "USE_TRAILING_STOP": {
"value": False, "value": False,
"type": "boolean", "type": "boolean",

View File

@ -777,6 +777,10 @@ class ConfigManager:
percent_keys = [ percent_keys = [
'TRAILING_STOP_ACTIVATION', 'TRAILING_STOP_ACTIVATION',
'TRAILING_STOP_PROTECT', 'TRAILING_STOP_PROTECT',
'LOCK_PROFIT_STAGE1_TRIGGER_PCT',
'LOCK_PROFIT_STAGE1_PCT',
'LOCK_PROFIT_STAGE2_TRIGGER_PCT',
'LOCK_PROFIT_STAGE2_PCT',
'MIN_VOLATILITY', 'MIN_VOLATILITY',
'TAKE_PROFIT_PERCENT', 'TAKE_PROFIT_PERCENT',
'TAKE_PROFIT_1_PERCENT', # 分步止盈第一目标默认15% 'TAKE_PROFIT_1_PERCENT', # 分步止盈第一目标默认15%
@ -897,6 +901,10 @@ class ConfigManager:
# 盈利保护总开关与保本:关闭后不执行保本、不执行移动止损 # 盈利保护总开关与保本:关闭后不执行保本、不执行移动止损
'PROFIT_PROTECTION_ENABLED': eff_get('PROFIT_PROTECTION_ENABLED', True), # True=启用保本+移动止损False=全部关闭 'PROFIT_PROTECTION_ENABLED': eff_get('PROFIT_PROTECTION_ENABLED', True), # True=启用保本+移动止损False=全部关闭
'LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT': eff_get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT', 0.03), # 盈利达保证金比例时移至保本0.03=3%0=关闭) 'LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT': eff_get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT', 0.03), # 盈利达保证金比例时移至保本0.03=3%0=关闭)
'LOCK_PROFIT_STAGE1_TRIGGER_PCT': eff_get('LOCK_PROFIT_STAGE1_TRIGGER_PCT', 0.08), # 盈利达该比例后进入第一层锁盈
'LOCK_PROFIT_STAGE1_PCT': eff_get('LOCK_PROFIT_STAGE1_PCT', 0.02), # 第一层锁住的利润比例
'LOCK_PROFIT_STAGE2_TRIGGER_PCT': eff_get('LOCK_PROFIT_STAGE2_TRIGGER_PCT', 0.15), # 盈利达该比例后进入第二层锁盈
'LOCK_PROFIT_STAGE2_PCT': eff_get('LOCK_PROFIT_STAGE2_PCT', 0.05), # 第二层锁住的利润比例
# 移动止损 # 移动止损
'USE_TRAILING_STOP': eff_get('USE_TRAILING_STOP', True), # 默认启用2026-01-27优化启用移动止损保护利润 'USE_TRAILING_STOP': eff_get('USE_TRAILING_STOP', True), # 默认启用2026-01-27优化启用移动止损保护利润
'TRAILING_STOP_ACTIVATION': eff_get('TRAILING_STOP_ACTIVATION', 0.05), # 默认5%2026-01-27优化更早保护利润避免回吐 'TRAILING_STOP_ACTIVATION': eff_get('TRAILING_STOP_ACTIVATION', 0.05), # 默认5%2026-01-27优化更早保护利润避免回吐
@ -909,7 +917,10 @@ class ConfigManager:
# 说明:这两个 key 需要出现在 TRADING_CONFIG 中,否则 trading_system 在每次 reload_from_redis 后会丢失它们, # 说明:这两个 key 需要出现在 TRADING_CONFIG 中,否则 trading_system 在每次 reload_from_redis 后会丢失它们,
# 导致始终按默认值拦截自动交易(用户在配置页怎么开都没用)。 # 导致始终按默认值拦截自动交易(用户在配置页怎么开都没用)。
'AUTO_TRADE_ONLY_TRENDING': eff_get('AUTO_TRADE_ONLY_TRENDING', True), 'AUTO_TRADE_ONLY_TRENDING': eff_get('AUTO_TRADE_ONLY_TRENDING', True),
'AUTO_TRADE_ALLOW_RANGING': eff_get('AUTO_TRADE_ALLOW_RANGING', False),
'AUTO_TRADE_ALLOW_UNKNOWN': eff_get('AUTO_TRADE_ALLOW_UNKNOWN', False),
'AUTO_TRADE_ALLOW_4H_NEUTRAL': eff_get('AUTO_TRADE_ALLOW_4H_NEUTRAL', allow_neutral_default), 'AUTO_TRADE_ALLOW_4H_NEUTRAL': eff_get('AUTO_TRADE_ALLOW_4H_NEUTRAL', allow_neutral_default),
'RANGING_MARKET_SIGNAL_BOOST': eff_get('RANGING_MARKET_SIGNAL_BOOST', 2),
# 智能入场/限价偏移(部分逻辑会直接读取 TRADING_CONFIG # 智能入场/限价偏移(部分逻辑会直接读取 TRADING_CONFIG
'LIMIT_ORDER_OFFSET_PCT': eff_get('LIMIT_ORDER_OFFSET_PCT', 0.5), 'LIMIT_ORDER_OFFSET_PCT': eff_get('LIMIT_ORDER_OFFSET_PCT', 0.5),

View File

@ -35,7 +35,11 @@ const KEY_LABELS = {
RSI_EXTREME_REVERSE_ENABLED: 'RSI 极端反向(超买反空/超卖反多)', RSI_EXTREME_REVERSE_ENABLED: 'RSI 极端反向(超买反空/超卖反多)',
RSI_EXTREME_REVERSE_ONLY_NEUTRAL_4H: 'RSI 反向仅允许 4H 中性', RSI_EXTREME_REVERSE_ONLY_NEUTRAL_4H: 'RSI 反向仅允许 4H 中性',
MIN_RR_FOR_TP1: '第一目标最小盈亏比(相对止损)', MIN_RR_FOR_TP1: '第一目标最小盈亏比(相对止损)',
AUTO_TRADE_ONLY_TRENDING: '仅趋势市自动交易',
AUTO_TRADE_ALLOW_RANGING: '允许震荡市自动交易',
AUTO_TRADE_ALLOW_UNKNOWN: '允许未判定市场自动交易',
AUTO_TRADE_ALLOW_4H_NEUTRAL: '允许 4H 中性时自动交易', AUTO_TRADE_ALLOW_4H_NEUTRAL: '允许 4H 中性时自动交易',
RANGING_MARKET_SIGNAL_BOOST: '震荡市额外信号门槛',
POSITION_SCALE_FACTOR: '仓位放大系数', POSITION_SCALE_FACTOR: '仓位放大系数',
SYNC_RECOVER_MISSING_POSITIONS: '同步时补建缺失持仓', SYNC_RECOVER_MISSING_POSITIONS: '同步时补建缺失持仓',
SYNC_RECOVER_ONLY_WHEN_HAS_SLTP: '仅当有止损/止盈单时补建', SYNC_RECOVER_ONLY_WHEN_HAS_SLTP: '仅当有止损/止盈单时补建',
@ -47,6 +51,14 @@ const KEY_LABELS = {
NIGHT_HOURS_END: '禁止开仓结束(时)', NIGHT_HOURS_END: '禁止开仓结束(时)',
NIGHT_HOURS_ONLY_SUNDAY: '仅周六21点周日06点', NIGHT_HOURS_ONLY_SUNDAY: '仅周六21点周日06点',
NO_OPEN_HOURS_BJ: '禁止开仓小时(北京)', NO_OPEN_HOURS_BJ: '禁止开仓小时(北京)',
MANUAL_REDUCED_SYMBOLS: '手动减仓交易对',
MANUAL_BLOCKED_SYMBOLS: '手动停做交易对',
MANUAL_REDUCED_SYMBOL_POSITION_FACTOR: '减仓名单仓位系数',
MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST: '减仓名单额外信号门槛',
LOCK_PROFIT_STAGE1_TRIGGER_PCT: '第一层锁盈触发阈值',
LOCK_PROFIT_STAGE1_PCT: '第一层锁盈比例',
LOCK_PROFIT_STAGE2_TRIGGER_PCT: '第二层锁盈触发阈值',
LOCK_PROFIT_STAGE2_PCT: '第二层锁盈比例',
SCAN_STAGGER_BY_ACCOUNT: '多账号错峰扫描', SCAN_STAGGER_BY_ACCOUNT: '多账号错峰扫描',
SCAN_STAGGER_RANDOM: '错峰随机模式', SCAN_STAGGER_RANDOM: '错峰随机模式',
SCAN_STAGGER_MIN_SEC: '错峰最小延迟(秒)', SCAN_STAGGER_MIN_SEC: '错峰最小延迟(秒)',
@ -83,7 +95,10 @@ const CONFIG_GUIDE_DETAILS = {
USE_TRAILING_STOP: '是否启用移动止损。启用后,当盈利达到激活阈值时,止损会自动跟踪价格,保护利润。适合趋势行情。建议:平衡和激进策略启用,保守策略可关闭。', USE_TRAILING_STOP: '是否启用移动止损。启用后,当盈利达到激活阈值时,止损会自动跟踪价格,保护利润。适合趋势行情。建议:平衡和激进策略启用,保守策略可关闭。',
SMART_ENTRY_ENABLED: '智能入场开关。开启时会进行"限价回调 + 追价 +(趋势强时)市价兜底";关闭时回归"纯限价单模式",更适合低频波段与控频。', SMART_ENTRY_ENABLED: '智能入场开关。开启时会进行"限价回调 + 追价 +(趋势强时)市价兜底";关闭时回归"纯限价单模式",更适合低频波段与控频。',
AUTO_TRADE_ONLY_TRENDING: '自动交易仅在市场状态=trending时执行ranging/unknown只生成推荐不自动下单。能显著降低震荡扫损但也会减少出单。', AUTO_TRADE_ONLY_TRENDING: '自动交易仅在市场状态=trending时执行ranging/unknown只生成推荐不自动下单。能显著降低震荡扫损但也会减少出单。',
AUTO_TRADE_ALLOW_RANGING: '当不强制 only_trending 时,是否允许在震荡市自动交易。默认关闭,避免横盘来回扫损。',
AUTO_TRADE_ALLOW_UNKNOWN: '当 marketRegime=unknownK线不足或未判定是否允许自动交易。默认关闭更稳健。',
AUTO_TRADE_ALLOW_4H_NEUTRAL: '是否允许4H趋势=neutral时自动交易。关闭可减少震荡扫损开启会显著增加出单需配合更严格的信号门槛。', AUTO_TRADE_ALLOW_4H_NEUTRAL: '是否允许4H趋势=neutral时自动交易。关闭可减少震荡扫损开启会显著增加出单需配合更严格的信号门槛。',
RANGING_MARKET_SIGNAL_BOOST: '震荡市额外提高的最小信号门槛。比如 2 表示在基础 MIN_SIGNAL_STRENGTH 上再 +2只保留更强的震荡市信号。',
LIMIT_ORDER_OFFSET_PCT: '限价入场偏移(%。BUY 挂在当前价下方SELL 挂在当前价上方。值越小越贴近当前价更容易成交值越大更保守。建议0.05%-0.20%。', LIMIT_ORDER_OFFSET_PCT: '限价入场偏移(%。BUY 挂在当前价下方SELL 挂在当前价上方。值越小越贴近当前价更容易成交值越大更保守。建议0.05%-0.20%。',
ENTRY_CONFIRM_TIMEOUT_SEC: '下单后等待成交的确认超时。在纯限价模式下超时未成交会撤单并跳过。建议60-180秒。', ENTRY_CONFIRM_TIMEOUT_SEC: '下单后等待成交的确认超时。在纯限价模式下超时未成交会撤单并跳过。建议60-180秒。',
ENTRY_CHASE_MAX_STEPS: '智能入场最大追价步数。步数越多越不容易错过,但也更接近追价;建议 2-4 步。', ENTRY_CHASE_MAX_STEPS: '智能入场最大追价步数。步数越多越不容易错过,但也更接近追价;建议 2-4 步。',
@ -111,6 +126,14 @@ const CONFIG_GUIDE_DETAILS = {
NIGHT_HOURS_END: '禁止开仓结束小时(北京),不含', NIGHT_HOURS_END: '禁止开仓结束小时(北京),不含',
NIGHT_HOURS_ONLY_SUNDAY: 'True=仅周六21:00周日06:00禁止False=每天21:0006:00禁止', NIGHT_HOURS_ONLY_SUNDAY: 'True=仅周六21:00周日06:00禁止False=每天21:0006:00禁止',
NO_OPEN_HOURS_BJ: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。', NO_OPEN_HOURS_BJ: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。',
MANUAL_REDUCED_SYMBOLS: '手动减仓交易对列表,支持逗号、空格或换行分隔。命中后仅缩小仓位并提高自动交易门槛,不会完全停做。',
MANUAL_BLOCKED_SYMBOLS: '手动停做交易对列表,支持逗号、空格或换行分隔。命中后直接禁止自动开仓,仅保留平仓与手动干预。',
MANUAL_REDUCED_SYMBOL_POSITION_FACTOR: '手动减仓交易对的仓位系数。比如 0.5 表示此类币只按正常仓位的 50% 下单。',
MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST: '手动减仓交易对额外提高的最小信号门槛。比如 1 表示在原门槛基础上再 +1用于减少边缘信号开仓。',
LOCK_PROFIT_STAGE1_TRIGGER_PCT: '分层锁盈第一层触发阈值(相对保证金,如 0.08=8%)。达到后会把止损从保本抬到小幅锁利。',
LOCK_PROFIT_STAGE1_PCT: '分层锁盈第一层锁利比例(相对保证金,如 0.02=2%)。',
LOCK_PROFIT_STAGE2_TRIGGER_PCT: '分层锁盈第二层触发阈值(相对保证金,如 0.15=15%)。达到后进一步抬高止损,减少利润回吐。',
LOCK_PROFIT_STAGE2_PCT: '分层锁盈第二层锁利比例(相对保证金,如 0.05=5%)。',
BINANCE_API_KEY: '币安API密钥。用于访问币安账户的凭证需要启用"合约交易"权限。请妥善保管,不要泄露。', BINANCE_API_KEY: '币安API密钥。用于访问币安账户的凭证需要启用"合约交易"权限。请妥善保管,不要泄露。',
BINANCE_API_SECRET: '币安API密钥Secret。与API Key配对使用用于签名验证。请妥善保管不要泄露。', BINANCE_API_SECRET: '币安API密钥Secret。与API Key配对使用用于签名验证。请妥善保管不要泄露。',
USE_TESTNET: '是否使用币安测试网。true表示模拟交易无真实资金false表示真实交易。', USE_TESTNET: '是否使用币安测试网。true表示模拟交易无真实资金false表示真实交易。',
@ -585,7 +608,11 @@ const GlobalConfig = () => {
SYMBOL_LOSS_COOLDOWN_SEC: { value: 3600, type: 'number', category: 'strategy', description: '连续亏损后的冷却时间默认1小时。' }, SYMBOL_LOSS_COOLDOWN_SEC: { value: 3600, type: 'number', category: 'strategy', description: '连续亏损后的冷却时间默认1小时。' },
MIN_RR_FOR_TP1: { value: 1.5, type: 'number', category: 'strategy', description: '第一目标止盈相对止损的最小盈亏比(如 1.5 表示 TP1 至少为止损距离的 1.5 倍。2026-02-12 新增。' }, MIN_RR_FOR_TP1: { value: 1.5, type: 'number', category: 'strategy', description: '第一目标止盈相对止损的最小盈亏比(如 1.5 表示 TP1 至少为止损距离的 1.5 倍。2026-02-12 新增。' },
POSITION_SCALE_FACTOR: { value: 1.0, type: 'number', category: 'risk', description: '仓位放大系数1.0=正常1.2=+20% 仓位1.5=+50%,上限 2.0。盈利时适度调高可扩大收益,仍受单笔上限约束。' }, POSITION_SCALE_FACTOR: { value: 1.0, type: 'number', category: 'risk', description: '仓位放大系数1.0=正常1.2=+20% 仓位1.5=+50%,上限 2.0。盈利时适度调高可扩大收益,仍受单笔上限约束。' },
AUTO_TRADE_ONLY_TRENDING: { value: true, type: 'boolean', category: 'strategy', description: '仅在趋势市自动交易。' },
AUTO_TRADE_ALLOW_RANGING: { value: false, type: 'boolean', category: 'strategy', description: '当不强制 only_trending 时,是否允许在震荡市自动交易。' },
AUTO_TRADE_ALLOW_UNKNOWN: { value: false, type: 'boolean', category: 'strategy', description: '当市场状态未判定时,是否允许自动交易。' },
AUTO_TRADE_ALLOW_4H_NEUTRAL: { value: false, type: 'boolean', category: 'strategy', description: '是否允许 4H 趋势为中性时自动交易。关闭可减少震荡扫损、提升质量(建议关闭)。' }, AUTO_TRADE_ALLOW_4H_NEUTRAL: { value: false, type: 'boolean', category: 'strategy', description: '是否允许 4H 趋势为中性时自动交易。关闭可减少震荡扫损、提升质量(建议关闭)。' },
RANGING_MARKET_SIGNAL_BOOST: { value: 2, type: 'number', category: 'strategy', description: '震荡市额外提高的最小信号门槛。' },
BETA_FILTER_ENABLED: { value: true, type: 'boolean', category: 'strategy', description: '大盘共振过滤BTC/ETH 下跌时屏蔽多单。' }, BETA_FILTER_ENABLED: { value: true, type: 'boolean', category: 'strategy', description: '大盘共振过滤BTC/ETH 下跌时屏蔽多单。' },
BETA_FILTER_THRESHOLD: { value: -0.005, type: 'number', category: 'strategy', description: '大盘共振阈值(比例,如 -0.005 表示 -0.5%)。' }, BETA_FILTER_THRESHOLD: { value: -0.005, type: 'number', category: 'strategy', description: '大盘共振阈值(比例,如 -0.005 表示 -0.5%)。' },
RSI_EXTREME_REVERSE_ENABLED: { value: false, type: 'boolean', category: 'strategy', description: '开启后:原信号做多但 RSI 超买(≥做多上限)时改为做空;原信号做空但 RSI 超卖(≤做空下限)时改为做多。属均值回归思路,可填补超买超卖时不下单的空置;默认关闭。' }, RSI_EXTREME_REVERSE_ENABLED: { value: false, type: 'boolean', category: 'strategy', description: '开启后:原信号做多但 RSI 超买(≥做多上限)时改为做空;原信号做空但 RSI 超卖(≤做空下限)时改为做多。属均值回归思路,可填补超买超卖时不下单的空置;默认关闭。' },
@ -630,6 +657,14 @@ const GlobalConfig = () => {
NIGHT_HOURS_END: { value: 6, type: 'number', category: 'risk', description: '禁止开仓结束小时(北京),不含。' }, NIGHT_HOURS_END: { value: 6, type: 'number', category: 'risk', description: '禁止开仓结束小时(北京),不含。' },
NIGHT_HOURS_ONLY_SUNDAY: { value: true, type: 'boolean', category: 'risk', description: 'True=仅周六21:00周日06:00禁止False=每天21:0006:00禁止。' }, NIGHT_HOURS_ONLY_SUNDAY: { value: true, type: 'boolean', category: 'risk', description: 'True=仅周六21:00周日06:00禁止False=每天21:0006:00禁止。' },
NO_OPEN_HOURS_BJ: { value: '', type: 'string', category: 'risk', description: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。' }, NO_OPEN_HOURS_BJ: { value: '', type: 'string', category: 'risk', description: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。' },
MANUAL_REDUCED_SYMBOLS: { value: '', type: 'string', category: 'strategy', description: '手动减仓交易对列表,支持逗号、空格或换行分隔。' },
MANUAL_BLOCKED_SYMBOLS: { value: '', type: 'string', category: 'strategy', description: '手动停做交易对列表,支持逗号、空格或换行分隔。' },
MANUAL_REDUCED_SYMBOL_POSITION_FACTOR: { value: 0.5, type: 'number', category: 'strategy', description: '手动减仓交易对的仓位系数。0.5 表示只开半仓。' },
MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST: { value: 1, type: 'number', category: 'strategy', description: '手动减仓交易对额外提高的最小信号门槛。' },
LOCK_PROFIT_STAGE1_TRIGGER_PCT: { value: 0.08, type: 'number', category: 'strategy', description: '分层锁盈第一层触发阈值(如 0.08=8%)。' },
LOCK_PROFIT_STAGE1_PCT: { value: 0.02, type: 'number', category: 'strategy', description: '分层锁盈第一层锁利比例(如 0.02=2%)。' },
LOCK_PROFIT_STAGE2_TRIGGER_PCT: { value: 0.15, type: 'number', category: 'strategy', description: '分层锁盈第二层触发阈值(如 0.15=15%)。' },
LOCK_PROFIT_STAGE2_PCT: { value: 0.05, type: 'number', category: 'strategy', description: '分层锁盈第二层锁利比例(如 0.05=5%)。' },
} }
const loadConfigs = async () => { const loadConfigs = async () => {

View File

@ -323,6 +323,11 @@ DEFAULT_TRADING_CONFIG = {
# 盈利保护:保本含手续费,避免“保本”实为小亏(在 PROFIT_PROTECTION_ENABLED 为 True 时生效) # 盈利保护:保本含手续费,避免“保本”实为小亏(在 PROFIT_PROTECTION_ENABLED 为 True 时生效)
'FEE_BUFFER_PCT': 0.0015, # 保本价相对入场价缓冲0.15% 覆盖开平双向手续费) 'FEE_BUFFER_PCT': 0.0015, # 保本价相对入场价缓冲0.15% 覆盖开平双向手续费)
'LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT': 0.03, # 盈利达3%保证金时移至保本建议3%5%平衡「少亏」与「拿住趋势」0=关闭) 'LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT': 0.03, # 盈利达3%保证金时移至保本建议3%5%平衡「少亏」与「拿住趋势」0=关闭)
# 分层锁盈:在“保本”和“全量移动止损”之间增加两级台阶,减少盈利回吐
'LOCK_PROFIT_STAGE1_TRIGGER_PCT': 0.08, # 盈利达8%保证金后,进入第一层锁盈
'LOCK_PROFIT_STAGE1_PCT': 0.02, # 第一层锁住约2%保证金利润
'LOCK_PROFIT_STAGE2_TRIGGER_PCT': 0.15, # 盈利达15%保证金后,进入第二层锁盈
'LOCK_PROFIT_STAGE2_PCT': 0.05, # 第二层锁住约5%保证金利润
# 最小持仓时间锁立即取消山寨币30分钟可能暴涨暴跌50% # 最小持仓时间锁立即取消山寨币30分钟可能暴涨暴跌50%
'MIN_HOLD_TIME_SEC': 0, # 取消持仓时间锁 'MIN_HOLD_TIME_SEC': 0, # 取消持仓时间锁
@ -331,8 +336,14 @@ DEFAULT_TRADING_CONFIG = {
# ===== 自动交易过滤(用于提升胜率/控频)===== # ===== 自动交易过滤(用于提升胜率/控频)=====
# 是否仅在 marketRegime=trending 时才自动交易;否则只生成推荐 # 是否仅在 marketRegime=trending 时才自动交易;否则只生成推荐
'AUTO_TRADE_ONLY_TRENDING': True, 'AUTO_TRADE_ONLY_TRENDING': True,
# 当不强制 only_trending 时,是否允许在震荡市自动交易;默认关闭,避免震荡扫损
'AUTO_TRADE_ALLOW_RANGING': False,
# 当 marketRegime=unknownK线不足/未判定)时,是否允许自动交易;默认关闭
'AUTO_TRADE_ALLOW_UNKNOWN': False,
# 是否允许 4H 趋势为 neutral 时自动交易山寨短线默认开4H 中性也开单以多抓机会;偏保守可改为 False # 是否允许 4H 趋势为 neutral 时自动交易山寨短线默认开4H 中性也开单以多抓机会;偏保守可改为 False
'AUTO_TRADE_ALLOW_4H_NEUTRAL': True, 'AUTO_TRADE_ALLOW_4H_NEUTRAL': True,
# 震荡市额外提高的最小信号门槛0-3 建议范围);默认 +2减少边缘信号
'RANGING_MARKET_SIGNAL_BOOST': 2,
# ===== 趋势入场过滤(防止追在半山腰)===== # ===== 趋势入场过滤(防止追在半山腰)=====
# 是否启用基于趋势状态的入场过滤: # 是否启用基于趋势状态的入场过滤:

View File

@ -1032,6 +1032,7 @@ class PositionManager:
'stagnationExitTriggered': False, # 是否已执行滞涨分批减仓+抬止损(只执行一次) 'stagnationExitTriggered': False, # 是否已执行滞涨分批减仓+抬止损(只执行一次)
'trailingStopActivated': False, # 移动止损是否已激活 'trailingStopActivated': False, # 移动止损是否已激活
'breakevenStopSet': False, # 是否已执行“盈利达阈值后移至保本” 'breakevenStopSet': False, # 是否已执行“盈利达阈值后移至保本”
'profitLockStage': 0, # 分层锁盈阶段0=未触发1/2=已触发对应层
'account_id': self.account_id 'account_id': self.account_id
} }
@ -1531,6 +1532,134 @@ class PositionManager:
return entry_price + lock_amount / quantity return entry_price + lock_amount / quantity
return entry_price - lock_amount / quantity return entry_price - lock_amount / quantity
def _normalize_ratio_config(self, value: Optional[float], default: float) -> float:
"""兼容 0.08 / 8 两种写法,统一转为比例值。"""
try:
ratio = float(value if value is not None else default)
except (TypeError, ValueError):
ratio = float(default)
if ratio > 1:
ratio = ratio / 100.0
return max(0.0, ratio)
async def _apply_profit_lock_stage(
self,
symbol: str,
position_info: Dict,
entry_price: float,
quantity: float,
leverage: float,
pnl_amount: float,
pnl_percent_margin: float,
current_price: float,
source: str,
) -> bool:
"""
分层锁盈
- 第一层盈利达到阈值后先从纯保本抬到小幅锁利
- 第二层盈利进一步扩大后将止损抬到更积极的锁利位置
"""
side = position_info.get("side")
if side not in ("BUY", "SELL"):
return False
stage1_trigger = self._normalize_ratio_config(
config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE1_TRIGGER_PCT"), 0.08
)
stage1_lock = self._normalize_ratio_config(
config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE1_PCT"), 0.02
)
stage2_trigger = self._normalize_ratio_config(
config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE2_TRIGGER_PCT"), 0.15
)
stage2_lock = self._normalize_ratio_config(
config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE2_PCT"), 0.05
)
target_stage = 0
target_lock_ratio = 0.0
if stage2_trigger > 0 and stage2_lock > 0 and pnl_percent_margin >= stage2_trigger * 100:
target_stage = 2
target_lock_ratio = stage2_lock
elif stage1_trigger > 0 and stage1_lock > 0 and pnl_percent_margin >= stage1_trigger * 100:
target_stage = 1
target_lock_ratio = stage1_lock
if target_stage <= 0 or target_lock_ratio <= 0:
return False
lock_quantity = float(position_info.get("remainingQuantity", quantity) or quantity)
if lock_quantity <= 0:
lock_quantity = float(quantity or 0)
if lock_quantity <= 0:
return False
lock_margin = (entry_price * lock_quantity) / leverage if leverage > 0 else (entry_price * lock_quantity)
target_lock_pct_number = target_lock_ratio * 100.0
new_stop_loss = self._stop_price_to_lock_pct(
entry_price, side, lock_margin, lock_quantity, target_lock_pct_number
)
breakeven = self._breakeven_stop_price(entry_price, side)
current_sl_raw = position_info.get("stopLoss")
current_sl = float(current_sl_raw) if current_sl_raw is not None else None
should_update = False
if side == "BUY":
should_update = (
new_stop_loss >= breakeven
and new_stop_loss < current_price
and (current_sl is None or new_stop_loss > current_sl)
)
else:
should_update = (
new_stop_loss <= breakeven
and new_stop_loss > current_price
and (current_sl is None or new_stop_loss < current_sl)
)
if not should_update:
return False
position_info["stopLoss"] = new_stop_loss
prev_stage = int(position_info.get("profitLockStage", 0) or 0)
position_info["profitLockStage"] = max(prev_stage, target_stage)
logger.info(
f"{symbol} [{source}] 分层锁盈生效:第{target_stage}层,止损上调至 {new_stop_loss:.4f} "
f"(当前盈利 {pnl_percent_margin:.2f}% of margin目标至少锁住 {target_lock_pct_number:.1f}%利润)"
)
_log_trailing_stop_event(
self.account_id,
symbol,
"profit_lock_stage",
source=source,
stage=target_stage,
pnl_pct=pnl_percent_margin,
new_stop_loss=new_stop_loss,
target_lock_pct=target_lock_pct_number,
)
try:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
_log_trailing_stop_event(
self.account_id,
symbol,
"profit_lock_stage_sync_ok",
source=source,
stage=target_stage,
new_stop_loss=new_stop_loss,
)
except Exception as sync_e:
_log_trailing_stop_event(
self.account_id,
symbol,
"profit_lock_stage_sync_fail",
source=source,
stage=target_stage,
new_stop_loss=new_stop_loss,
error=str(sync_e),
)
logger.warning(f"{symbol} [{source}] 分层锁盈同步至交易所失败: {sync_e}", exc_info=False)
return True
async def _ensure_exchange_sltp_orders(self, symbol: str, position_info: Dict, current_price: Optional[float] = None) -> None: async def _ensure_exchange_sltp_orders(self, symbol: str, position_info: Dict, current_price: Optional[float] = None) -> None:
""" """
在币安侧挂止损/止盈保护单STOP_MARKET + TAKE_PROFIT_MARKET 在币安侧挂止损/止盈保护单STOP_MARKET + TAKE_PROFIT_MARKET
@ -2239,6 +2368,17 @@ class PositionManager:
f"{symbol} [定时检查] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}", f"{symbol} [定时检查] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False, exc_info=False,
) )
await self._apply_profit_lock_stage(
symbol=symbol,
position_info=position_info,
entry_price=entry_price,
quantity=quantity,
leverage=leverage,
pnl_amount=pnl_amount,
pnl_percent_margin=pnl_percent_margin,
current_price=current_price,
source="定时检查",
)
# 盈利超过阈值后(相对于保证金),激活移动止损 # 盈利超过阈值后(相对于保证金),激活移动止损
if pnl_percent_margin > trailing_activation * 100: if pnl_percent_margin > trailing_activation * 100:
position_info['trailingStopActivated'] = True position_info['trailingStopActivated'] = True
@ -4095,7 +4235,8 @@ class PositionManager:
'atr': None, 'atr': None,
'maxProfit': 0.0, 'maxProfit': 0.0,
'trailingStopActivated': False, 'trailingStopActivated': False,
'breakevenStopSet': False 'breakevenStopSet': False,
'profitLockStage': 0,
} }
# 订单统一由自动下单入 DB此处仅做内存监控不创建 DB 记录 # 订单统一由自动下单入 DB此处仅做内存监控不创建 DB 记录
if not sync_create_manual and DB_AVAILABLE and Trade and not config.TRADING_CONFIG.get("ONLY_AUTO_TRADE_CREATES_RECORDS", True): if not sync_create_manual and DB_AVAILABLE and Trade and not config.TRADING_CONFIG.get("ONLY_AUTO_TRADE_CREATES_RECORDS", True):
@ -4489,6 +4630,17 @@ class PositionManager:
f"[账号{self.account_id}] {symbol} [实时监控] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}", f"[账号{self.account_id}] {symbol} [实时监控] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False, exc_info=False,
) )
await self._apply_profit_lock_stage(
symbol=symbol,
position_info=position_info,
entry_price=entry_price,
quantity=quantity,
leverage=leverage,
pnl_amount=pnl_amount,
pnl_percent_margin=pnl_percent_margin,
current_price=current_price_float,
source="实时监控",
)
# 盈利超过阈值后(相对于保证金),激活移动止损 # 盈利超过阈值后(相对于保证金),激活移动止损
if pnl_percent_margin > trailing_activation * 100: if pnl_percent_margin > trailing_activation * 100:
position_info['trailingStopActivated'] = True position_info['trailingStopActivated'] = True

View File

@ -10,10 +10,12 @@ try:
from .binance_client import BinanceClient from .binance_client import BinanceClient
from . import config from . import config
from .atr_strategy import ATRStrategy from .atr_strategy import ATRStrategy
from .symbol_policy import resolve_symbol_trading_policy
except ImportError: except ImportError:
from binance_client import BinanceClient from binance_client import BinanceClient
import config import config
from atr_strategy import ATRStrategy from atr_strategy import ATRStrategy
from symbol_policy import resolve_symbol_trading_policy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -780,21 +782,20 @@ class RiskManager:
except (TypeError, ValueError): except (TypeError, ValueError):
position_scale = 1.0 position_scale = 1.0
# 基于 7 天统计的 symbol / 小时软黑名单:差标的 & 差时段进一步缩小仓位 # 基于 symbol 分层与时段过滤进一步缩小仓位:
# - reduced symbol缩小仓位
# - bad hour缩小仓位
stats_scale = 1.0 stats_scale = 1.0
try: try:
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from config_manager import GlobalStrategyConfigManager from config_manager import GlobalStrategyConfigManager
mgr = GlobalStrategyConfigManager() mgr = GlobalStrategyConfigManager()
stats_symbol = mgr.get("STATS_SYMBOL_FILTERS", {}) or {}
stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {} stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {}
bl = stats_symbol.get("blacklist") or [] symbol_policy = resolve_symbol_trading_policy(symbol)
if symbol and isinstance(bl, list): if symbol_policy.get("reduced"):
if any((item.get("symbol") or "").upper() == symbol.upper() for item in bl): stats_scale *= float(symbol_policy.get("position_factor") or 1.0)
# 软黑名单:默认 0.5 倍仓位
stats_scale *= 0.5
# 小时过滤 # 小时过滤
try: try:
@ -871,6 +872,16 @@ class RiskManager:
logger.info(f"{symbol} 自动交易已关闭AUTO_TRADE_ENABLED=false跳过") logger.info(f"{symbol} 自动交易已关闭AUTO_TRADE_ENABLED=false跳过")
return False return False
# 交易对分层:停做名单直接拒绝自动开仓。
try:
symbol_policy = resolve_symbol_trading_policy(symbol)
if symbol_policy.get("blocked"):
sources = ",".join(symbol_policy.get("sources") or []) or "blocked"
logger.info(f"{symbol} 命中停做名单({sources}),跳过自动开仓")
return False
except Exception as e:
logger.debug(f"{symbol} 检查交易对分层策略失败(忽略): {e}")
# 检查最小涨跌幅阈值 # 检查最小涨跌幅阈值
if abs(change_percent) < config.TRADING_CONFIG['MIN_CHANGE_PERCENT']: if abs(change_percent) < config.TRADING_CONFIG['MIN_CHANGE_PERCENT']:
logger.debug(f"{symbol} 涨跌幅 {change_percent:.2f}% 小于阈值") logger.debug(f"{symbol} 涨跌幅 {change_percent:.2f}% 小于阈值")

View File

@ -12,12 +12,14 @@ try:
from .risk_manager import RiskManager from .risk_manager import RiskManager
from .position_manager import PositionManager from .position_manager import PositionManager
from . import config from . import config
from .symbol_policy import resolve_symbol_trading_policy
except ImportError: except ImportError:
from binance_client import BinanceClient from binance_client import BinanceClient
from market_scanner import MarketScanner from market_scanner import MarketScanner
from risk_manager import RiskManager from risk_manager import RiskManager
from position_manager import PositionManager from position_manager import PositionManager
import config import config
from symbol_policy import resolve_symbol_trading_policy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -175,12 +177,27 @@ class TradingStrategy:
# 提升胜率:可配置的“仅 trending 自动交易”过滤 # 提升胜率:可配置的“仅 trending 自动交易”过滤
only_trending = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ONLY_TRENDING", True)) only_trending = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ONLY_TRENDING", True))
allow_ranging = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_RANGING", False))
allow_unknown = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_UNKNOWN", False))
if only_trending and market_regime != 'trending': if only_trending and market_regime != 'trending':
logger.info( logger.info(
f"{symbol} 市场状态={market_regime},跳过自动交易(仅生成推荐)" f"{symbol} 市场状态={market_regime},跳过自动交易(仅生成推荐)"
f"原因AUTO_TRADE_ONLY_TRENDING=true" f"原因AUTO_TRADE_ONLY_TRENDING=true"
) )
continue continue
if not only_trending:
if market_regime == 'ranging' and not allow_ranging:
logger.info(
f"{symbol} 市场状态={market_regime},跳过自动交易(仅生成推荐)"
f"原因AUTO_TRADE_ALLOW_RANGING=false"
)
continue
if market_regime not in ('trending', 'ranging') and not allow_unknown:
logger.info(
f"{symbol} 市场状态={market_regime},跳过自动交易(仅生成推荐)"
f"原因AUTO_TRADE_ALLOW_UNKNOWN=false"
)
continue
# 检查是否应该自动交易(已有持仓则跳过自动交易,但推荐已生成) # 检查是否应该自动交易(已有持仓则跳过自动交易,但推荐已生成)
if not await self.risk_manager.should_trade(symbol, change_percent): if not await self.risk_manager.should_trade(symbol, change_percent):
@ -702,30 +719,25 @@ class TradingStrategy:
# 判断是否应该交易(信号强度 >= 有效 MIN_SIGNAL_STRENGTH 才交易;低波动期自动提高门槛) # 判断是否应该交易(信号强度 >= 有效 MIN_SIGNAL_STRENGTH 才交易;低波动期自动提高门槛)
base_min_signal_strength = config.get_effective_config('MIN_SIGNAL_STRENGTH', 7) base_min_signal_strength = config.get_effective_config('MIN_SIGNAL_STRENGTH', 7)
# 基于全局 7 天统计的 symbol / 小时过滤: # 基于交易对分层与按小时过滤:
# - 硬黑名单:直接禁止自动交易(仅允许平仓) # - blocked:直接禁止自动交易(仅允许平仓)
# - 软黑名单 & 差时段:提高信号门槛 # - reduced / 差时段:提高信号门槛
extra_signal_boost = 0 extra_signal_boost = 0
blocked_by_hard_blacklist = False blocked_by_hard_blacklist = False
symbol_policy_sources = []
try: try:
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from config_manager import GlobalStrategyConfigManager from config_manager import GlobalStrategyConfigManager
mgr = GlobalStrategyConfigManager() mgr = GlobalStrategyConfigManager()
stats_symbol = mgr.get("STATS_SYMBOL_FILTERS", {}) or {}
stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {} stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {}
# symbol 黑名单:硬黑名单直接拒绝;软黑名单在基础门槛上 +1 symbol_policy = resolve_symbol_trading_policy(symbol)
bl = stats_symbol.get("blacklist") or [] symbol_policy_sources = symbol_policy.get("sources") or []
if symbol and isinstance(bl, list): if symbol_policy.get("blocked"):
for item in bl: blocked_by_hard_blacklist = True
if (item.get("symbol") or "").upper() == symbol.upper(): elif symbol_policy.get("reduced"):
mode = (item.get("mode") or "reduced").lower() extra_signal_boost += int(symbol_policy.get("signal_boost") or 0)
if mode == "hard":
blocked_by_hard_blacklist = True
else:
extra_signal_boost += 1
break
# 按小时过滤bad 小时再 +1 # 按小时过滤bad 小时再 +1
try: try:
@ -744,6 +756,13 @@ class TradingStrategy:
extra_signal_boost = 0 extra_signal_boost = 0
blocked_by_hard_blacklist = False blocked_by_hard_blacklist = False
mr = (market_regime or 'unknown').strip().lower()
if mr == 'ranging':
try:
extra_signal_boost += int(config.TRADING_CONFIG.get('RANGING_MARKET_SIGNAL_BOOST', 2) or 0)
except Exception:
extra_signal_boost += 2
min_signal_strength = max(0, min(10, int(base_min_signal_strength) + int(extra_signal_boost))) min_signal_strength = max(0, min(10, int(base_min_signal_strength) + int(extra_signal_boost)))
# 强度上限归一到 0-10避免出现 12/10 这种误导显示 # 强度上限归一到 0-10避免出现 12/10 这种误导显示
try: try:
@ -764,7 +783,10 @@ class TradingStrategy:
reasons.append("采用扫描器综合信号(弱)") reasons.append("采用扫描器综合信号(弱)")
should_trade = (not blocked_by_hard_blacklist) and signal_strength >= min_signal_strength and direction is not None should_trade = (not blocked_by_hard_blacklist) and signal_strength >= min_signal_strength and direction is not None
if blocked_by_hard_blacklist: if blocked_by_hard_blacklist:
reasons.append("❌ 命中硬黑名单7天持续净亏自动交易禁用仅允许手动/解封后再交易") src = " / ".join(symbol_policy_sources) if symbol_policy_sources else "hard blacklist"
reasons.append(f"❌ 命中停做名单({src}),自动交易禁用,仅允许手动/解封后再交易")
elif extra_signal_boost > 0 and direction is not None:
reasons.append(f"命中减仓观察名单/差时段/震荡市过滤,最低信号门槛提高 +{extra_signal_boost}")
# ===== RSI / 24h 涨跌幅过滤:做多不追高、做空不杀跌;可选 RSI 极端反向(超买反空/超卖反多)===== # ===== RSI / 24h 涨跌幅过滤:做多不追高、做空不杀跌;可选 RSI 极端反向(超买反空/超卖反多)=====
# 反向属于“逆短期超买超卖”的均值回归在强趋势里逆势风险大可选“仅4H中性时反向”以降低风险。 # 反向属于“逆短期超买超卖”的均值回归在强趋势里逆势风险大可选“仅4H中性时反向”以降低风险。

View File

@ -0,0 +1,134 @@
"""
交易对分层策略
- 正常按原策略处理
- reduced缩小仓位并在策略侧提高信号门槛
- blocked直接禁止自动开仓
"""
import logging
import re
from typing import Any, Dict, Set
logger = logging.getLogger(__name__)
def _parse_symbol_list(raw: Any) -> Set[str]:
"""兼容 string/list/dict 三种配置格式,统一转成大写 symbol 集合。"""
if raw is None:
return set()
if isinstance(raw, dict):
if "symbols" in raw:
return _parse_symbol_list(raw.get("symbols"))
if "items" in raw:
return _parse_symbol_list(raw.get("items"))
return set()
if isinstance(raw, str):
parts = re.split(r"[\s,;\n\r\t]+", raw)
return {p.strip().upper() for p in parts if p and p.strip()}
if isinstance(raw, list):
items: Set[str] = set()
for item in raw:
if isinstance(item, str):
sym = item.strip()
elif isinstance(item, dict):
sym = str(item.get("symbol") or item.get("value") or "").strip()
else:
sym = ""
if sym:
items.add(sym.upper())
return items
return set()
def _safe_float(value: Any, default: float) -> float:
try:
return float(value)
except (TypeError, ValueError):
return default
def _safe_int(value: Any, default: int) -> int:
try:
return int(value)
except (TypeError, ValueError):
return default
def resolve_symbol_trading_policy(symbol: str) -> Dict[str, Any]:
"""
合并手动名单与统计名单返回统一的交易对分层结果
返回字段
- blocked: 是否禁止自动开仓
- reduced: 是否进入减仓观察名单
- position_factor: reduced 时建议的仓位系数
- signal_boost: reduced 时建议提高的最小信号门槛
- sources: 命中的来源便于日志定位
"""
policy = {
"blocked": False,
"reduced": False,
"position_factor": 1.0,
"signal_boost": 0,
"sources": [],
}
symbol = (symbol or "").strip().upper()
if not symbol:
return policy
try:
from config_manager import GlobalStrategyConfigManager
mgr = GlobalStrategyConfigManager()
stats_symbol = mgr.get("STATS_SYMBOL_FILTERS", {}) or {}
manual_blocked = _parse_symbol_list(mgr.get("MANUAL_BLOCKED_SYMBOLS", ""))
manual_reduced = _parse_symbol_list(mgr.get("MANUAL_REDUCED_SYMBOLS", ""))
manual_position_factor = _safe_float(
mgr.get("MANUAL_REDUCED_SYMBOL_POSITION_FACTOR", 0.5), 0.5
)
manual_position_factor = max(0.05, min(manual_position_factor, 1.0))
manual_signal_boost = _safe_int(
mgr.get("MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST", 1), 1
)
manual_signal_boost = max(0, min(manual_signal_boost, 5))
if symbol in manual_blocked:
policy["blocked"] = True
policy["sources"].append("manual_blocked")
if symbol in manual_reduced:
policy["reduced"] = True
policy["position_factor"] = min(policy["position_factor"], manual_position_factor)
policy["signal_boost"] = max(policy["signal_boost"], manual_signal_boost)
policy["sources"].append("manual_reduced")
for item in (stats_symbol.get("blacklist") or []):
if (item.get("symbol") or "").strip().upper() != symbol:
continue
mode = (item.get("mode") or "reduced").strip().lower()
if mode == "hard":
policy["blocked"] = True
policy["sources"].append("stats_hard")
else:
policy["reduced"] = True
policy["position_factor"] = min(policy["position_factor"], 0.5)
policy["signal_boost"] = max(policy["signal_boost"], 1)
policy["sources"].append("stats_reduced")
break
except Exception as e:
logger.debug(f"{symbol} 读取交易对分层策略失败: {e}")
if policy["blocked"]:
policy["reduced"] = False
policy["position_factor"] = 1.0
policy["signal_boost"] = 0
# 去重并保留顺序,便于日志阅读。
policy["sources"] = list(dict.fromkeys(policy["sources"]))
return policy