feat(config): 添加4H趋势过滤配置以优化交易策略
在配置管理中新增 `BLOCK_SHORT_WHEN_4H_UP` 参数,允许在4H上涨时禁止开空,增强策略灵活性与风险控制。同时,更新前端组件以展示该配置,提升用户体验。此改动确保在不同市场条件下,策略能够更有效地避免逆势操作。
This commit is contained in:
parent
cbf778d560
commit
cddcf35481
|
|
@ -951,6 +951,7 @@ class ConfigManager:
|
|||
# 市场状态方案(便于在不同行情间切换)
|
||||
'MARKET_SCHEME': str(eff_get('MARKET_SCHEME', 'normal') or 'normal').lower(),
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': eff_get('BLOCK_LONG_WHEN_4H_DOWN', False), # 4H 下跌时禁止开多(熊市/保守用)
|
||||
'BLOCK_SHORT_WHEN_4H_UP': eff_get('BLOCK_SHORT_WHEN_4H_UP', True), # 4H 上涨时禁止开空(默认 True,避免逆势做空)
|
||||
}
|
||||
|
||||
# 根据市场方案覆盖关键参数(便于快速切换熊市/牛市/保守等预设)
|
||||
|
|
@ -960,12 +961,14 @@ class ConfigManager:
|
|||
'MAX_POSITION_PERCENT': 0.12,
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.5,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': False,
|
||||
'BLOCK_SHORT_WHEN_4H_UP': True, # 4H 上涨不开空
|
||||
},
|
||||
'bear': {
|
||||
'MIN_STOP_LOSS_PRICE_PCT': 0.05, # 放宽止损约 -5%
|
||||
'MAX_POSITION_PERCENT': 0.08, # 单仓 ≤ 8%
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.5,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': True, # 4H 下跌不开多
|
||||
'BLOCK_SHORT_WHEN_4H_UP': True, # 4H 上涨不开空
|
||||
'BETA_FILTER_ENABLED': True,
|
||||
},
|
||||
'bull': {
|
||||
|
|
@ -973,12 +976,14 @@ class ConfigManager:
|
|||
'MAX_POSITION_PERCENT': 0.12,
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.0,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': False,
|
||||
'BLOCK_SHORT_WHEN_4H_UP': True, # 4H 上涨不开空(牛市尤需)
|
||||
},
|
||||
'conservative': {
|
||||
'MIN_STOP_LOSS_PRICE_PCT': 0.06, # 最宽松止损
|
||||
'MAX_POSITION_PERCENT': 0.06, # 最小仓位
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.5,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': True,
|
||||
'BLOCK_SHORT_WHEN_4H_UP': True,
|
||||
'BETA_FILTER_ENABLED': True,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ DEFAULTS_TO_SYNC = [
|
|||
"description": "市场方案:normal / bear / bull / conservative。切换后自动覆盖止损、仓位、趋势过滤等参数。"},
|
||||
{"config_key": "BLOCK_LONG_WHEN_4H_DOWN", "config_value": "false", "config_type": "boolean", "category": "strategy",
|
||||
"description": "4H 趋势下跌时禁止开多。bear / conservative 方案下自动为 true。"},
|
||||
{"config_key": "BLOCK_SHORT_WHEN_4H_UP", "config_value": "true", "config_type": "boolean", "category": "strategy",
|
||||
"description": "4H 趋势上涨时禁止开空。默认 true,避免逆势做空导致止损。"},
|
||||
{"config_key": "AUTO_MARKET_SCHEME_ENABLED", "config_value": "false", "config_type": "boolean", "category": "strategy",
|
||||
"description": "开启后,crontab 定时运行 scripts/update_market_scheme.py --apply 时自动更新 MARKET_SCHEME(根据 BTC 行情识别牛/熊/正常)。"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ const GlobalConfig = () => {
|
|||
// 市场状态方案:便于在不同行情间一键切换(熊市/正常/牛市/保守)
|
||||
scheme_bear: {
|
||||
name: '熊市',
|
||||
desc: '【熊市专用】放宽止损5%、单仓≤8%、4H下跌禁止开多、大盘过滤开启。避免逆势抄底、减少回撤。',
|
||||
desc: '【熊市专用】放宽止损5%、单仓≤8%、4H下跌禁止开多、4H上涨禁止开空、大盘过滤开启。避免逆势抄底/做空、减少回撤。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'bear',
|
||||
|
|
@ -392,12 +392,13 @@ const GlobalConfig = () => {
|
|||
MAX_POSITION_PERCENT: 0.08,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.5,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: true,
|
||||
BLOCK_SHORT_WHEN_4H_UP: true,
|
||||
BETA_FILTER_ENABLED: true,
|
||||
},
|
||||
},
|
||||
scheme_normal: {
|
||||
name: '正常',
|
||||
desc: '【常规市场】止损3%、单仓12%、4H下跌可开多。适合震荡或温和行情。',
|
||||
desc: '【常规市场】止损3%、单仓12%、4H下跌可开多、4H上涨禁止开空。适合震荡或温和行情。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'normal',
|
||||
|
|
@ -405,11 +406,12 @@ const GlobalConfig = () => {
|
|||
MAX_POSITION_PERCENT: 0.12,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.5,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: false,
|
||||
BLOCK_SHORT_WHEN_4H_UP: true,
|
||||
},
|
||||
},
|
||||
scheme_bull: {
|
||||
name: '牛市',
|
||||
desc: '【牛市专用】止损3%、单仓12%、4H下跌可开多、ATR倍数略低。偏积极。',
|
||||
desc: '【牛市专用】止损3%、单仓12%、4H下跌可开多、4H上涨禁止开空、ATR倍数略低。偏积极。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'bull',
|
||||
|
|
@ -417,11 +419,12 @@ const GlobalConfig = () => {
|
|||
MAX_POSITION_PERCENT: 0.12,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.0,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: false,
|
||||
BLOCK_SHORT_WHEN_4H_UP: true,
|
||||
},
|
||||
},
|
||||
scheme_conservative: {
|
||||
name: '保守',
|
||||
desc: '【极保守】止损6%、单仓6%、4H下跌禁止开多。最小仓位、最严趋势过滤。',
|
||||
desc: '【极保守】止损6%、单仓6%、4H下跌禁止开多、4H上涨禁止开空。最小仓位、最严趋势过滤。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'conservative',
|
||||
|
|
@ -429,6 +432,7 @@ const GlobalConfig = () => {
|
|||
MAX_POSITION_PERCENT: 0.06,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.5,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: true,
|
||||
BLOCK_SHORT_WHEN_4H_UP: true,
|
||||
BETA_FILTER_ENABLED: true,
|
||||
},
|
||||
},
|
||||
|
|
@ -498,6 +502,7 @@ const GlobalConfig = () => {
|
|||
SYSTEM_ORDER_ID_PREFIX: { value: 'SYS', type: 'string', category: 'position', description: '系统单标识:下单时写入 newClientOrderId 前缀,同步时仅对「开仓订单 clientOrderId 以此前缀开头」的持仓补建;设空则用「是否有止损止盈单」判断。' },
|
||||
MARKET_SCHEME: { value: 'normal', type: 'string', category: 'strategy', description: '市场方案:normal / bear / bull / conservative。切换后自动覆盖止损、仓位、4H趋势过滤等参数。' },
|
||||
BLOCK_LONG_WHEN_4H_DOWN: { value: false, type: 'boolean', category: 'strategy', description: '4H 趋势下跌时禁止开多。bear / conservative 方案下自动为 true。' },
|
||||
BLOCK_SHORT_WHEN_4H_UP: { value: true, type: 'boolean', category: 'strategy', description: '4H 趋势上涨时禁止开空。默认 true,避免逆势做空导致止损。' },
|
||||
AUTO_MARKET_SCHEME_ENABLED: { value: false, type: 'boolean', category: 'strategy', description: '开启后,crontab 定时运行 update_market_scheme.py --apply 时自动更新 MARKET_SCHEME(根据 BTC 行情识别牛/熊/正常)。' },
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -660,6 +660,8 @@ class MarketScanner:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
ema50_4h = None
|
||||
macd_4h = None
|
||||
use_cached_indicators = False
|
||||
if cached_indicators and cached_indicators.get('last_kline_time') == last_kline_time:
|
||||
# 缓存命中,使用缓存的技术指标
|
||||
|
|
@ -672,6 +674,8 @@ class MarketScanner:
|
|||
ema20 = cached_indicators.get('ema20')
|
||||
ema50 = cached_indicators.get('ema50')
|
||||
ema20_4h = cached_indicators.get('ema20_4h')
|
||||
ema50_4h = cached_indicators.get('ema50_4h')
|
||||
macd_4h = cached_indicators.get('macd_4h')
|
||||
market_regime = cached_indicators.get('marketRegime')
|
||||
else:
|
||||
# 缓存未命中,重新计算技术指标
|
||||
|
|
@ -683,8 +687,10 @@ class MarketScanner:
|
|||
ema20 = TechnicalIndicators.calculate_ema(close_prices, period=20)
|
||||
ema50 = TechnicalIndicators.calculate_ema(close_prices, period=50)
|
||||
|
||||
# 计算4H周期的EMA20用于多周期共振检查
|
||||
# 计算4H周期的EMA、MACD用于多周期共振检查
|
||||
ema20_4h = TechnicalIndicators.calculate_ema(close_prices_4h, period=20) if len(close_prices_4h) >= 20 else None
|
||||
ema50_4h = TechnicalIndicators.calculate_ema(close_prices_4h, period=50) if len(close_prices_4h) >= 50 else None
|
||||
macd_4h = TechnicalIndicators.calculate_macd(close_prices_4h) if len(close_prices_4h) >= 35 else None
|
||||
|
||||
# 判断市场状态
|
||||
market_regime = TechnicalIndicators.detect_market_regime(close_prices)
|
||||
|
|
@ -700,6 +706,8 @@ class MarketScanner:
|
|||
'ema20': ema20,
|
||||
'ema50': ema50,
|
||||
'ema20_4h': ema20_4h,
|
||||
'ema50_4h': ema50_4h,
|
||||
'macd_4h': macd_4h,
|
||||
'marketRegime': market_regime,
|
||||
}
|
||||
await self.client.redis_cache.set(cache_key_indicators, indicators_cache, ttl=30)
|
||||
|
|
@ -750,7 +758,15 @@ class MarketScanner:
|
|||
# 获取4H周期当前价格(用于判断4H趋势)
|
||||
price_4h = close_prices_4h[-1] if len(close_prices_4h) > 0 else current_price
|
||||
|
||||
# 判断4H周期趋势方向
|
||||
# 判断4H周期趋势方向(优先从 Redis 缓存读取,与 strategy 共用)
|
||||
try:
|
||||
from .trend_4h_cache import get_trend_4h_cached
|
||||
trend_4h = await get_trend_4h_cached(
|
||||
self.client.redis_cache if self.client else None,
|
||||
symbol, price_4h, ema20_4h, ema50_4h, macd_4h,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"{symbol} trend_4h 缓存获取失败: {e}")
|
||||
trend_4h = None
|
||||
if ema20_4h is not None:
|
||||
if price_4h > ema20_4h:
|
||||
|
|
@ -881,6 +897,8 @@ class MarketScanner:
|
|||
'ema20': ema20,
|
||||
'ema50': ema50,
|
||||
'ema20_4h': ema20_4h, # 4H周期EMA20,用于多周期共振
|
||||
'ema50_4h': ema50_4h, # 4H周期EMA50
|
||||
'macd_4h': macd_4h, # 4H周期MACD
|
||||
'price_4h': close_prices_4h[-1] if len(close_prices_4h) > 0 else current_price, # 4H周期当前价格
|
||||
'marketRegime': market_regime,
|
||||
'signalScore': signal_score, # 保留用于兼容性
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ TTL_KLINES_REST = 1800 # REST 拉取的 K 线默认 30 分钟
|
|||
TTL_KLINES_REST_OLD = 300 # 旧格式 klines:{s}:{i}:{limit} 默认 5 分钟
|
||||
TTL_LISTEN_KEY = 55 * 60 # 55 分钟(listenKey 缓存)
|
||||
TTL_TREND_STATE = 3600
|
||||
TTL_TREND_4H = 600 # 10 分钟(trend_4h 基于 4H K 线,同根 K 线内变化缓慢)
|
||||
TTL_INDICATORS = 30
|
||||
TTL_RECO_SNAPSHOT = 7200
|
||||
TTL_RECO_ITEM = 3600
|
||||
|
|
|
|||
|
|
@ -205,6 +205,13 @@ class TradingStrategy:
|
|||
f"{symbol} 4H 趋势下跌,禁止开多(BLOCK_LONG_WHEN_4H_DOWN=true),跳过"
|
||||
)
|
||||
continue
|
||||
# 4H 上涨禁止开空(对称过滤:避免逆势做空,减少上涨行情中空单止损)
|
||||
block_short_4h_up = bool(config.TRADING_CONFIG.get("BLOCK_SHORT_WHEN_4H_UP", True))
|
||||
if block_short_4h_up and trade_direction == "SELL" and trade_signal.get("trend_4h") == "up":
|
||||
logger.info(
|
||||
f"{symbol} 4H 趋势上涨,禁止开空(BLOCK_SHORT_WHEN_4H_UP=true),跳过"
|
||||
)
|
||||
continue
|
||||
# 开仓前资金费率过滤:避免在费率极端不利于己方时进场
|
||||
if not await self._check_funding_rate_filter(symbol, trade_direction):
|
||||
logger.info(f"{symbol} 资金费率过滤未通过,跳过开仓")
|
||||
|
|
@ -553,7 +560,15 @@ class TradingStrategy:
|
|||
ema50_4h = symbol_info.get('ema50_4h')
|
||||
macd_4h = symbol_info.get('macd_4h')
|
||||
|
||||
# 判断4H周期趋势方向(多指标投票)
|
||||
# 优先从 Redis 缓存读取 trend_4h(基于 WS K 线,同根 4H 内减少重复计算)
|
||||
try:
|
||||
from .trend_4h_cache import get_trend_4h_cached
|
||||
redis_cache = self.client.redis_cache if self.client else None
|
||||
trend_4h = await get_trend_4h_cached(
|
||||
redis_cache, symbol, price_4h, ema20_4h, ema50_4h, macd_4h
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"{symbol} trend_4h 缓存获取失败,回退本地计算: {e}")
|
||||
trend_4h = self._judge_trend_4h(price_4h, ema20_4h, ema50_4h, macd_4h)
|
||||
|
||||
signal_strength = 0
|
||||
|
|
|
|||
96
trading_system/trend_4h_cache.py
Normal file
96
trading_system/trend_4h_cache.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
trend_4h 缓存模块
|
||||
|
||||
基于已有 WS K 线缓存,将计算得到的 trend_4h(up/down/neutral)写入 Redis,
|
||||
供 strategy、market_scanner 等复用,减少重复 EMA/MACD 计算。
|
||||
Redis Key: trend_4h:{symbol},TTL 10 分钟。
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
KEY_PREFIX = "trend_4h:"
|
||||
try:
|
||||
from .redis_ttl import TTL_TREND_4H
|
||||
except ImportError:
|
||||
TTL_TREND_4H = 600 # 10 分钟
|
||||
|
||||
|
||||
def _judge_trend_4h(
|
||||
price_4h: float,
|
||||
ema20_4h: Optional[float],
|
||||
ema50_4h: Optional[float],
|
||||
macd_4h: Optional[Dict],
|
||||
) -> str:
|
||||
"""
|
||||
多指标投票判断 4H 趋势(与 strategy._judge_trend_4h 一致)。
|
||||
Returns: 'up' | 'down' | 'neutral'
|
||||
"""
|
||||
if ema20_4h is None:
|
||||
return 'neutral'
|
||||
score = 0
|
||||
if price_4h > ema20_4h:
|
||||
score += 1
|
||||
elif price_4h < ema20_4h:
|
||||
score -= 1
|
||||
if ema50_4h is not None:
|
||||
if ema20_4h > ema50_4h:
|
||||
score += 1
|
||||
elif ema20_4h < ema50_4h:
|
||||
score -= 1
|
||||
if macd_4h and isinstance(macd_4h, dict):
|
||||
macd_hist = macd_4h.get('histogram', 0)
|
||||
if macd_hist > 0:
|
||||
score += 1
|
||||
elif macd_hist < 0:
|
||||
score -= 1
|
||||
if score >= 2:
|
||||
return 'up'
|
||||
if score <= -2:
|
||||
return 'down'
|
||||
return 'neutral'
|
||||
|
||||
|
||||
async def get_trend_4h_cached(
|
||||
redis_cache: Any,
|
||||
symbol: str,
|
||||
price_4h: float,
|
||||
ema20_4h: Optional[float],
|
||||
ema50_4h: Optional[float],
|
||||
macd_4h: Optional[Dict],
|
||||
ttl_sec: int = None,
|
||||
) -> str:
|
||||
"""
|
||||
优先从 Redis 读取 trend_4h,未命中则计算并写入缓存。
|
||||
|
||||
Args:
|
||||
redis_cache: RedisCache 实例(可为 None,则直接计算不缓存)
|
||||
symbol: 交易对
|
||||
price_4h, ema20_4h, ema50_4h, macd_4h: 计算 trend_4h 所需输入
|
||||
ttl_sec: 缓存 TTL,默认使用 redis_ttl.TTL_TREND_4H(10 分钟)
|
||||
|
||||
Returns:
|
||||
'up' | 'down' | 'neutral'
|
||||
"""
|
||||
if ttl_sec is None:
|
||||
ttl_sec = TTL_TREND_4H
|
||||
key = f"{KEY_PREFIX}{symbol}"
|
||||
if redis_cache:
|
||||
try:
|
||||
await redis_cache.connect()
|
||||
cached = await redis_cache.get(key)
|
||||
if cached and cached in ('up', 'down', 'neutral'):
|
||||
return cached
|
||||
except Exception as e:
|
||||
logger.debug(f"trend_4h 缓存读取失败 {symbol}: {e}")
|
||||
|
||||
trend = _judge_trend_4h(price_4h, ema20_4h, ema50_4h, macd_4h)
|
||||
|
||||
if redis_cache:
|
||||
try:
|
||||
await redis_cache.set(key, trend, ttl=ttl_sec)
|
||||
except Exception as e:
|
||||
logger.debug(f"trend_4h 缓存写入失败 {symbol}: {e}")
|
||||
|
||||
return trend
|
||||
Loading…
Reference in New Issue
Block a user