diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index a37dd96..0b2414d 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -46,6 +46,7 @@ USER_RISK_KNOBS = { "NIGHT_HOURS_START", "NIGHT_HOURS_END", "NIGHT_HOURS_ONLY_SUNDAY", + "NO_OPEN_HOURS_BJ", # 策略筛选(允许用户调整) "TOP_N_SYMBOLS", "MIN_SIGNAL_STRENGTH", @@ -73,6 +74,7 @@ USER_VISIBLE_DEFAULTS = { "NIGHT_HOURS_START": {"value": 21, "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=每天"}, + "NO_OPEN_HOURS_BJ": {"value": "", "type": "string", "category": "risk", "description": "禁止开仓小时(北京),逗号分隔如17,19,22;空则不限制"}, "TOP_N_SYMBOLS": {"value": 8, "type": "number", "category": "scan", "description": "每次扫描后优先处理的交易对数量"}, "MIN_SIGNAL_STRENGTH": {"value": 8, "type": "number", "category": "scan", "description": "最小信号强度(0-10)"}, "MIN_VOLUME_24H": {"value": 30_000_000, "type": "number", "category": "scan", "description": "24h 成交量下限(USD)"}, @@ -123,6 +125,7 @@ RISK_KNOBS_DEFAULTS = { "NIGHT_HOURS_START": {"value": 21, "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=每天。"}, + "NO_OPEN_HOURS_BJ": {"value": "", "type": "string", "category": "risk", "description": "禁止开仓小时(北京),逗号分隔如 17,19,22 表示 17/19/22 时不开新仓;空则不限制。按历史亏损时段配置。"}, } # API key/secret 脱敏 diff --git a/backend/config_manager.py b/backend/config_manager.py index 3011ef3..98f2073 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -836,6 +836,7 @@ class ConfigManager: 'NIGHT_HOURS_START': eff_get('NIGHT_HOURS_START', 21), 'NIGHT_HOURS_END': eff_get('NIGHT_HOURS_END', 6), 'NIGHT_HOURS_ONLY_SUNDAY': eff_get('NIGHT_HOURS_ONLY_SUNDAY', True), + 'NO_OPEN_HOURS_BJ': (eff_get('NO_OPEN_HOURS_BJ', '') or '').strip(), # 禁止开仓小时(北京),逗号分隔如 "17,19,22",空则不限制 # 同步/系统单标识(全局配置,账号可覆盖) 'ONLY_AUTO_TRADE_CREATES_RECORDS': eff_get('ONLY_AUTO_TRADE_CREATES_RECORDS', True), # True=不补建「仅币安有仓」;False 时配合 SYNC_RECOVER 可补建 diff --git a/docs/减少亏损_配置操作步骤.md b/docs/减少亏损_配置操作步骤.md index c2a9ccc..b71f858 100644 --- a/docs/减少亏损_配置操作步骤.md +++ b/docs/减少亏损_配置操作步骤.md @@ -68,6 +68,18 @@ --- +### 6. 禁止开仓小时(按历史亏损时段) + +若导出数据里**按小时**统计后,某些整点(如 17、19、22 时北京)亏损集中,可单独禁止这些整点开新仓: + +| 配置项 | 建议值 | 说明 | +|--------|--------|------| +| **NO_OPEN_HOURS_BJ** | **17,19,22** | 北京 17/19/22 时不开新仓;逗号分隔,空则不限制。 | + +配置后,交易进程在对应整点会跳过开仓并打日志「当前北京 X 时在禁止开仓时段内」。 + +--- + ## 三、操作清单(复制自查) - [ ] **BLOCK_SHORT_WHEN_4H_UP** = true @@ -76,6 +88,7 @@ - [ ] **MIN_SIGNAL_STRENGTH** = 6~8 - [ ] (可选)**SUNDAY_MAX_OPENS** = 2~3,**SUNDAY_MIN_SIGNAL_STRENGTH** = 8 - [ ] (可选)**NIGHT_HOURS_NO_OPEN_ENABLED** = true +- [ ] (可选)**NO_OPEN_HOURS_BJ** = **17,19,22**(按你的亏损时段改) 改完后在配置页点**保存**;配置会写入数据库/Redis,交易进程下一次读配置即生效,一般**无需重启**(若你部署方式有缓存,可按需重启一次交易服务)。 diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index 942f3ab..478dfb9 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -46,6 +46,7 @@ const KEY_LABELS = { NIGHT_HOURS_START: '禁止开仓开始(时)', NIGHT_HOURS_END: '禁止开仓结束(时)', NIGHT_HOURS_ONLY_SUNDAY: '仅周六21点~周日06点', + NO_OPEN_HOURS_BJ: '禁止开仓小时(北京)', SCAN_STAGGER_BY_ACCOUNT: '多账号错峰扫描', SCAN_STAGGER_RANDOM: '错峰随机模式', SCAN_STAGGER_MIN_SEC: '错峰最小延迟(秒)', @@ -109,6 +110,7 @@ const CONFIG_GUIDE_DETAILS = { NIGHT_HOURS_START: '禁止开仓开始小时(北京),含', NIGHT_HOURS_END: '禁止开仓结束小时(北京),不含', NIGHT_HOURS_ONLY_SUNDAY: 'True=仅周六21:00~周日06:00禁止;False=每天21:00~06:00禁止', + NO_OPEN_HOURS_BJ: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。', BINANCE_API_KEY: '币安API密钥。用于访问币安账户的凭证,需要启用"合约交易"权限。请妥善保管,不要泄露。', BINANCE_API_SECRET: '币安API密钥Secret。与API Key配对使用,用于签名验证。请妥善保管,不要泄露。', USE_TESTNET: '是否使用币安测试网。true表示模拟交易(无真实资金),false表示真实交易。', @@ -627,6 +629,7 @@ const GlobalConfig = () => { NIGHT_HOURS_START: { value: 21, type: 'number', category: 'risk', description: '禁止开仓开始小时(北京),含。从周六晚 21 点开始控制。' }, 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:00~06:00禁止。' }, + NO_OPEN_HOURS_BJ: { value: '', type: 'string', category: 'risk', description: '禁止开仓小时(北京),逗号分隔如 17,19,22 表示该整点不开新仓;空则不限制。按历史亏损时段配置。' }, } const loadConfigs = async () => { diff --git a/trading_system/config.py b/trading_system/config.py index a6fc674..17312c5 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -194,6 +194,8 @@ DEFAULT_TRADING_CONFIG = { 'NIGHT_HOURS_START': 21, 'NIGHT_HOURS_END': 6, 'NIGHT_HOURS_ONLY_SUNDAY': True, + # 禁止开仓小时(北京):逗号分隔,如 "17,19,22" 表示 17/19/22 时不开新仓;空则不限制。按历史亏损时段配置 + 'NO_OPEN_HOURS_BJ': '', 'ONE_WAY_POSITION_ONLY': True, # 账号固定为单向持仓模式,不传 positionSide,不检测对冲模式(避免 -4061) 'MAX_POSITION_PERCENT': 0.20, # 单笔仓位上限20%(作为风控熔断,实际仓位由固定风险模型决定) diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 7d6bbc8..ceb87bd 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -923,6 +923,26 @@ class RiskManager: except Exception: pass + # 禁止开仓小时(北京):按历史亏损时段配置,如 "17,19,22" 表示 17/19/22 时不开新仓 + try: + no_open_hours_raw = config.TRADING_CONFIG.get("NO_OPEN_HOURS_BJ") or "" + if no_open_hours_raw and isinstance(no_open_hours_raw, str): + bj = timezone(timedelta(hours=8)) + now_bj = datetime.now(bj) + h = now_bj.hour + parts = [x.strip() for x in no_open_hours_raw.split(",") if x.strip()] + no_open_set = set() + for p in parts: + try: + no_open_set.add(int(p)) + except ValueError: + continue + if no_open_set and h in no_open_set: + logger.info(f"{symbol} 当前北京{h}时在禁止开仓时段内(NO_OPEN_HOURS_BJ={no_open_hours_raw}),跳过") + return False + except Exception: + pass + # 每日开仓次数限制(Redis 计数;无 Redis 时降级为内存计数) try: max_daily = int(config.TRADING_CONFIG.get("MAX_DAILY_ENTRIES", 0) or 0)