feat(config): 添加市场状态方案以优化交易策略
在配置管理中引入市场状态方案,允许在不同市场条件下快速切换策略(如熊市、牛市、正常、保守)。更新相关参数以自动覆盖止损、仓位和趋势过滤设置,增强策略灵活性。同时,前端组件更新以支持市场状态方案的展示与选择,提升用户体验。
This commit is contained in:
parent
3b0526f392
commit
452e40bdf5
|
|
@ -814,7 +814,7 @@ class ConfigManager:
|
|||
short_filter_default = False if is_fast else True
|
||||
max_trend_move_default = 0.08 if is_fast else 0.05
|
||||
|
||||
return {
|
||||
result = {
|
||||
# 仓位控制
|
||||
'MAX_POSITION_PERCENT': eff_get('MAX_POSITION_PERCENT', 0.12), # 单笔最大保证金占比(12%,加大单笔盈利空间)
|
||||
'MAX_TOTAL_POSITION_PERCENT': eff_get('MAX_TOTAL_POSITION_PERCENT', 0.40), # 总保证金占比上限
|
||||
|
|
@ -947,8 +947,47 @@ class ConfigManager:
|
|||
'SYMBOL_LOSS_COOLDOWN_SEC': eff_get('SYMBOL_LOSS_COOLDOWN_SEC', 3600),
|
||||
# 第一目标止盈最小盈亏比(相对止损距离)
|
||||
'MIN_RR_FOR_TP1': eff_get('MIN_RR_FOR_TP1', 1.5), # 2026-02-12:保证 TP1 至少 1.5 倍止损距离,改善盈亏比
|
||||
|
||||
# 市场状态方案(便于在不同行情间切换)
|
||||
'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 下跌时禁止开多(熊市/保守用)
|
||||
}
|
||||
|
||||
# 根据市场方案覆盖关键参数(便于快速切换熊市/牛市/保守等预设)
|
||||
_SCHEME_PRESETS = {
|
||||
'normal': {
|
||||
'MIN_STOP_LOSS_PRICE_PCT': 0.03,
|
||||
'MAX_POSITION_PERCENT': 0.12,
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.5,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': False,
|
||||
},
|
||||
'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 下跌不开多
|
||||
'BETA_FILTER_ENABLED': True,
|
||||
},
|
||||
'bull': {
|
||||
'MIN_STOP_LOSS_PRICE_PCT': 0.03,
|
||||
'MAX_POSITION_PERCENT': 0.12,
|
||||
'ATR_STOP_LOSS_MULTIPLIER': 2.0,
|
||||
'BLOCK_LONG_WHEN_4H_DOWN': False,
|
||||
},
|
||||
'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,
|
||||
'BETA_FILTER_ENABLED': True,
|
||||
},
|
||||
}
|
||||
scheme = result.get('MARKET_SCHEME', 'normal') or 'normal'
|
||||
if scheme in _SCHEME_PRESETS:
|
||||
for k, v in _SCHEME_PRESETS[scheme].items():
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
def _sync_to_redis(self):
|
||||
"""将配置同步到Redis缓存(账号维度)"""
|
||||
if not self._redis_connected or not self._redis_client:
|
||||
|
|
|
|||
|
|
@ -57,6 +57,13 @@ DEFAULTS_TO_SYNC = [
|
|||
"description": "止盈按保证金封顶,避免过远"},
|
||||
{"config_key": "USE_MARGIN_CAP_FOR_SL", "config_value": "true", "config_type": "boolean", "category": "risk",
|
||||
"description": "止损按保证金封顶,避免扛单"},
|
||||
# 市场状态方案(2026-02 三项优化 + 方案切换)
|
||||
{"config_key": "MARKET_SCHEME", "config_value": "normal", "config_type": "string", "category": "strategy",
|
||||
"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": "AUTO_MARKET_SCHEME_ENABLED", "config_value": "false", "config_type": "boolean", "category": "strategy",
|
||||
"description": "开启后,crontab 定时运行 scripts/update_market_scheme.py --apply 时自动更新 MARKET_SCHEME(根据 BTC 行情识别牛/熊/正常)。"},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -381,6 +381,57 @@ const GlobalConfig = () => {
|
|||
FIXED_RISK_PERCENT: 0.01, // 1% 风险
|
||||
},
|
||||
},
|
||||
// 市场状态方案:便于在不同行情间一键切换(熊市/正常/牛市/保守)
|
||||
scheme_bear: {
|
||||
name: '熊市',
|
||||
desc: '【熊市专用】放宽止损5%、单仓≤8%、4H下跌禁止开多、大盘过滤开启。避免逆势抄底、减少回撤。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'bear',
|
||||
MIN_STOP_LOSS_PRICE_PCT: 0.05,
|
||||
MAX_POSITION_PERCENT: 0.08,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.5,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: true,
|
||||
BETA_FILTER_ENABLED: true,
|
||||
},
|
||||
},
|
||||
scheme_normal: {
|
||||
name: '正常',
|
||||
desc: '【常规市场】止损3%、单仓12%、4H下跌可开多。适合震荡或温和行情。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'normal',
|
||||
MIN_STOP_LOSS_PRICE_PCT: 0.03,
|
||||
MAX_POSITION_PERCENT: 0.12,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.5,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: false,
|
||||
},
|
||||
},
|
||||
scheme_bull: {
|
||||
name: '牛市',
|
||||
desc: '【牛市专用】止损3%、单仓12%、4H下跌可开多、ATR倍数略低。偏积极。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: 'bull',
|
||||
MIN_STOP_LOSS_PRICE_PCT: 0.03,
|
||||
MAX_POSITION_PERCENT: 0.12,
|
||||
ATR_STOP_LOSS_MULTIPLIER: 2.0,
|
||||
BLOCK_LONG_WHEN_4H_DOWN: false,
|
||||
},
|
||||
},
|
||||
scheme_conservative: {
|
||||
name: '保守',
|
||||
desc: '【极保守】止损6%、单仓6%、4H下跌禁止开多。最小仓位、最严趋势过滤。',
|
||||
signatureKeys: ['MARKET_SCHEME'],
|
||||
configs: {
|
||||
MARKET_SCHEME: '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,
|
||||
BETA_FILTER_ENABLED: true,
|
||||
},
|
||||
},
|
||||
aggressive: {
|
||||
name: '激进短线(高风险)',
|
||||
desc: '针对晚间高波动:2%风险/单,交易频率高,止损较紧但止盈合理。注意:回撤风险较高。',
|
||||
|
|
@ -445,6 +496,9 @@ const GlobalConfig = () => {
|
|||
SYNC_RECOVER_MISSING_POSITIONS: { value: true, type: 'boolean', category: 'position', description: '同步时补建「币安有仓、DB 无记录」的交易记录。' },
|
||||
SYNC_RECOVER_ONLY_WHEN_HAS_SLTP: { value: true, type: 'boolean', category: 'position', description: '仅当该持仓存在止损/止盈单时才补建(未配置 SYSTEM_ORDER_ID_PREFIX 时生效)。' },
|
||||
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。' },
|
||||
AUTO_MARKET_SCHEME_ENABLED: { value: false, type: 'boolean', category: 'strategy', description: '开启后,crontab 定时运行 update_market_scheme.py --apply 时自动更新 MARKET_SCHEME(根据 BTC 行情识别牛/熊/正常)。' },
|
||||
}
|
||||
|
||||
const loadConfigs = async () => {
|
||||
|
|
@ -967,9 +1021,13 @@ const GlobalConfig = () => {
|
|||
swing: { group: 'backup', tag: '纯限价' },
|
||||
strict: { group: 'backup', tag: '精选低频' },
|
||||
conservative: { group: 'backup', tag: '保守' },
|
||||
scheme_bear: { group: 'scheme', tag: '熊市' },
|
||||
scheme_normal: { group: 'scheme', tag: '正常' },
|
||||
scheme_bull: { group: 'scheme', tag: '牛市' },
|
||||
scheme_conservative: { group: 'scheme', tag: '保守' },
|
||||
}
|
||||
|
||||
// 快速方案:当前推荐(含增加机会/盈利放大/之前盈利)+ 备选
|
||||
// 快速方案:当前推荐(含增加机会/盈利放大/之前盈利)+ 备选 + 市场状态方案
|
||||
const presetGroups = [
|
||||
{
|
||||
key: 'altcoin',
|
||||
|
|
@ -983,6 +1041,12 @@ const GlobalConfig = () => {
|
|||
desc: '需要更少出单或纯限价时可选:波段回归、精选低频、保守传统。',
|
||||
presetKeys: ['swing', 'strict', 'conservative'],
|
||||
},
|
||||
{
|
||||
key: 'scheme',
|
||||
title: '市场状态方案',
|
||||
desc: '按当前行情切换:熊市(放宽止损、降仓、4H 下跌不开多)→ 正常 → 牛市 → 保守(最小仓位、最严过滤)。',
|
||||
presetKeys: ['scheme_bear', 'scheme_normal', 'scheme_bull', 'scheme_conservative'],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
62
scripts/UPDATE_MARKET_SCHEME_README.md
Normal file
62
scripts/UPDATE_MARKET_SCHEME_README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 市场行情自动识别与 MARKET_SCHEME 更新
|
||||
|
||||
## 1. 功能说明
|
||||
|
||||
`update_market_scheme.py` 基于 Binance 公开 API 自动识别当前市场行情(牛/熊/正常),并可选更新全局配置 `MARKET_SCHEME`,从而自动切换止损、仓位、4H 趋势过滤等参数。
|
||||
|
||||
**数据来源(无需 API Key):**
|
||||
|
||||
- 24h 涨跌幅:`/fapi/v1/ticker/24hr`
|
||||
- 资金费率:`/fapi/v1/premiumIndex`
|
||||
- 大户多空比:`/futures/data/topLongShortPositionRatio`
|
||||
- 4H K 线:`/fapi/v1/klines`(计算 trend_4h)
|
||||
|
||||
**打分规则:**
|
||||
|
||||
| 指标 | 偏多 (+1) | 偏空 (-1) |
|
||||
|--------------|-------------|--------------|
|
||||
| 24h 涨跌幅 | ≥ 2% | ≤ -2% |
|
||||
| 4H 趋势 | up | down |
|
||||
| 资金费率 | ≥ 0.00005 | ≤ -0.00005 |
|
||||
| 大户多空比 | ≥ 1.1 | ≤ 0.9 |
|
||||
|
||||
总分 ≥ 2 → bull,≤ -2 → bear,否则 normal。
|
||||
|
||||
## 2. 使用方式
|
||||
|
||||
```bash
|
||||
# 仅检测并打印(不更新 DB)
|
||||
python scripts/update_market_scheme.py
|
||||
|
||||
# 若开启 AUTO_MARKET_SCHEME_ENABLED,则更新 DB
|
||||
python scripts/update_market_scheme.py --apply
|
||||
|
||||
# 强制更新(忽略 AUTO_MARKET_SCHEME_ENABLED 开关)
|
||||
python scripts/update_market_scheme.py --apply --force
|
||||
```
|
||||
|
||||
## 3. 启用自动更新
|
||||
|
||||
1. 在「全局配置」中设置 `AUTO_MARKET_SCHEME_ENABLED` 为 `true`
|
||||
2. 或执行一次同步默认配置:`cd backend && python sync_global_config_defaults.py`,然后在全局配置页手动改为 true
|
||||
3. 配置 crontab 定时执行 `update_market_scheme.py --apply`
|
||||
|
||||
## 4. Crontab 配置示例
|
||||
|
||||
每 4 小时执行一次(0 点、4 点、8 点…):
|
||||
|
||||
```cron
|
||||
0 */4 * * * cd /path/to/auto_trade_sys && /path/to/.venv/bin/python scripts/update_market_scheme.py --apply >> /tmp/update_market_scheme.log 2>&1
|
||||
```
|
||||
|
||||
或每 6 小时:
|
||||
|
||||
```cron
|
||||
0 */6 * * * cd /path/to/auto_trade_sys && /path/to/.venv/bin/python scripts/update_market_scheme.py --apply >> /tmp/update_market_scheme.log 2>&1
|
||||
```
|
||||
|
||||
## 5. 注意事项
|
||||
|
||||
- 脚本会清除 `global_strategy_config_v5` Redis 缓存,使交易进程下次读取时从 DB 获取最新配置
|
||||
- 若希望立即生效,可在前端点击「清除全局缓存」并「重启所有账号交易」
|
||||
- 若不开启 `AUTO_MARKET_SCHEME_ENABLED`,脚本仅打印识别结果,不修改 DB;可用 `--force` 临时强制更新
|
||||
117
scripts/update_market_scheme.py
Normal file
117
scripts/update_market_scheme.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
定时任务:自动识别市场行情并更新 MARKET_SCHEME(需开启 AUTO_MARKET_SCHEME_ENABLED)。
|
||||
|
||||
用法:
|
||||
cd /path/to/auto_trade_sys && python scripts/update_market_scheme.py # 仅检测并打印
|
||||
python scripts/update_market_scheme.py --apply # 若开启自动模式则更新 DB
|
||||
python scripts/update_market_scheme.py --apply --force # 强制更新(忽略 AUTO 开关)
|
||||
|
||||
crontab 示例(每 4 小时执行):
|
||||
0 */4 * * * cd /path/to/auto_trade_sys && python scripts/update_market_scheme.py --apply >> /tmp/update_market_scheme.log 2>&1
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 项目根目录
|
||||
proj = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(proj / "backend"))
|
||||
sys.path.insert(0, str(proj))
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_auto_enabled() -> bool:
|
||||
"""从 DB 读取 AUTO_MARKET_SCHEME_ENABLED,默认 False。"""
|
||||
try:
|
||||
from database.models import GlobalStrategyConfig
|
||||
val = GlobalStrategyConfig.get_value("AUTO_MARKET_SCHEME_ENABLED", False)
|
||||
if isinstance(val, bool):
|
||||
return val
|
||||
if isinstance(val, str):
|
||||
return val.lower() in ("true", "1", "yes")
|
||||
return bool(val)
|
||||
except Exception as e:
|
||||
logger.warning(f"读取 AUTO_MARKET_SCHEME_ENABLED 失败: {e},默认不自动更新")
|
||||
return False
|
||||
|
||||
|
||||
def _update_market_scheme(scheme: str) -> bool:
|
||||
"""更新 MARKET_SCHEME 到 DB,并清除全局配置 Redis 缓存。"""
|
||||
try:
|
||||
from database.models import GlobalStrategyConfig
|
||||
except ImportError as e:
|
||||
logger.error(f"导入 GlobalStrategyConfig 失败: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
GlobalStrategyConfig.set(
|
||||
"MARKET_SCHEME",
|
||||
scheme,
|
||||
"string",
|
||||
"strategy",
|
||||
"市场方案:normal / bear / bull / conservative。由 update_market_scheme 定时任务自动更新。",
|
||||
updated_by="update_market_scheme",
|
||||
)
|
||||
logger.info(f"已更新 MARKET_SCHEME = {scheme}")
|
||||
|
||||
# 清除全局配置 Redis 缓存,让 trading_system 下次读取时从 DB 获取
|
||||
try:
|
||||
import redis
|
||||
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
|
||||
redis_use_tls = os.getenv("REDIS_USE_TLS", "False").lower() == "true"
|
||||
redis_username = os.getenv("REDIS_USERNAME", None)
|
||||
redis_password = os.getenv("REDIS_PASSWORD", None)
|
||||
if redis_use_tls and not redis_url.startswith("rediss://"):
|
||||
redis_url = redis_url.replace("redis://", "rediss://", 1)
|
||||
r = redis.from_url(
|
||||
redis_url,
|
||||
username=redis_username,
|
||||
password=redis_password,
|
||||
decode_responses=True,
|
||||
)
|
||||
r.ping()
|
||||
r.delete("global_strategy_config_v5")
|
||||
logger.info("已清除全局配置 Redis 缓存")
|
||||
except Exception as e:
|
||||
logger.warning(f"清除 Redis 缓存失败(不影响 DB 更新): {e}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"更新 MARKET_SCHEME 失败: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="自动识别市场行情并可选更新 MARKET_SCHEME")
|
||||
parser.add_argument("--apply", action="store_true", help="若开启自动模式则更新 DB")
|
||||
parser.add_argument("--force", action="store_true", help="强制更新(忽略 AUTO_MARKET_SCHEME_ENABLED)")
|
||||
args = parser.parse_args()
|
||||
|
||||
from trading_system.market_regime_detector import detect_market_regime
|
||||
|
||||
regime, details = detect_market_regime()
|
||||
logger.info(f"市场行情识别: {regime} | 详情: {details}")
|
||||
|
||||
if not args.apply:
|
||||
print(f"REGIME={regime} DETAILS={details}")
|
||||
return 0
|
||||
|
||||
if not args.force and not _get_auto_enabled():
|
||||
logger.info("AUTO_MARKET_SCHEME_ENABLED 未开启,跳过更新。如需强制更新请加 --force")
|
||||
return 0
|
||||
|
||||
ok = _update_market_scheme(regime)
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
257
trading_system/market_regime_detector.py
Normal file
257
trading_system/market_regime_detector.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
"""
|
||||
市场行情自动识别模块
|
||||
|
||||
基于 Binance 公开 API 判断当前市场状态(牛/熊/正常),用于自动切换 MARKET_SCHEME。
|
||||
可被 crontab 定时任务调用(如每 4 小时执行一次)。
|
||||
|
||||
使用的接口(无需 API Key):
|
||||
- GET /fapi/v1/ticker/24hr:24h 涨跌幅
|
||||
- GET /fapi/v1/premiumIndex:资金费率
|
||||
- GET /futures/data/topLongShortPositionRatio:大户多空比
|
||||
- GET /fapi/v1/klines:4H K 线(计算 trend_4h)
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import ssl
|
||||
import urllib.request
|
||||
from typing import Optional, Tuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BINANCE_FUTURES_BASE = "https://fapi.binance.com"
|
||||
BINANCE_FUTURES_DATA = "https://fapi.binance.com/futures/data"
|
||||
REQUEST_TIMEOUT = 15
|
||||
|
||||
|
||||
def _http_get(url: str, params: Optional[dict] = None) -> Optional[dict]:
|
||||
"""发起 GET 请求,返回 JSON 或 None。"""
|
||||
if params:
|
||||
qs = "&".join(f"{k}={v}" for k, v in params.items())
|
||||
url = f"{url}?{qs}"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
||||
ctx = ssl.create_default_context()
|
||||
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT, context=ctx) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
data = json.loads(raw)
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"HTTP GET 失败 {url[:80]}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def fetch_btc_24h_change() -> Optional[float]:
|
||||
"""获取 BTCUSDT 24h 涨跌幅百分比。"""
|
||||
data = _http_get(f"{BINANCE_FUTURES_BASE}/fapi/v1/ticker/24hr", {"symbol": "BTCUSDT"})
|
||||
if not data or not isinstance(data, dict):
|
||||
return None
|
||||
try:
|
||||
return float(data.get("priceChangePercent", 0))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def fetch_btc_funding_rate() -> Optional[float]:
|
||||
"""获取 BTCUSDT 最近资金费率。正=多头付费,负=空头付费。"""
|
||||
data = _http_get(f"{BINANCE_FUTURES_BASE}/fapi/v1/premiumIndex", {"symbol": "BTCUSDT"})
|
||||
if not data or not isinstance(data, dict):
|
||||
return None
|
||||
try:
|
||||
return float(data.get("lastFundingRate", 0))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def fetch_btc_long_short_ratio(period: str = "1d", limit: int = 1) -> Optional[float]:
|
||||
"""获取 BTCUSDT 大户持仓多空比。>1 偏多,<1 偏空。"""
|
||||
data = _http_get(
|
||||
f"{BINANCE_FUTURES_DATA}/topLongShortPositionRatio",
|
||||
{"symbol": "BTCUSDT", "period": period, "limit": limit},
|
||||
)
|
||||
if not data or not isinstance(data, list) or len(data) == 0:
|
||||
return None
|
||||
try:
|
||||
return float(data[-1].get("longShortRatio", 1))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def fetch_btc_klines_4h(limit: int = 60) -> Optional[list]:
|
||||
"""获取 BTCUSDT 4H K 线。"""
|
||||
data = _http_get(
|
||||
f"{BINANCE_FUTURES_BASE}/fapi/v1/klines",
|
||||
{"symbol": "BTCUSDT", "interval": "4h", "limit": limit},
|
||||
)
|
||||
if not data or not isinstance(data, list):
|
||||
return None
|
||||
return data
|
||||
|
||||
|
||||
def _judge_trend_4h(
|
||||
price_4h: float,
|
||||
ema20_4h: Optional[float],
|
||||
ema50_4h: Optional[float],
|
||||
macd_hist: Optional[float],
|
||||
) -> str:
|
||||
"""根据 4H 价格、EMA、MACD 判断趋势方向。"""
|
||||
if ema20_4h is None:
|
||||
return "neutral"
|
||||
|
||||
score = 0
|
||||
total = 0
|
||||
|
||||
if price_4h > ema20_4h:
|
||||
score += 1
|
||||
elif price_4h < ema20_4h:
|
||||
score -= 1
|
||||
total += 1
|
||||
|
||||
if ema50_4h is not None:
|
||||
if ema20_4h > ema50_4h:
|
||||
score += 1
|
||||
elif ema20_4h < ema50_4h:
|
||||
score -= 1
|
||||
total += 1
|
||||
|
||||
if macd_hist is not None:
|
||||
if macd_hist > 0:
|
||||
score += 1
|
||||
elif macd_hist < 0:
|
||||
score -= 1
|
||||
total += 1
|
||||
|
||||
if total == 0:
|
||||
return "neutral"
|
||||
if score >= 2:
|
||||
return "up"
|
||||
if score <= -2:
|
||||
return "down"
|
||||
return "neutral"
|
||||
|
||||
|
||||
def compute_trend_4h_from_klines(klines: list) -> str:
|
||||
"""从 4H K 线计算 trend_4h。"""
|
||||
try:
|
||||
from .indicators import TechnicalIndicators
|
||||
except ImportError:
|
||||
from indicators import TechnicalIndicators
|
||||
|
||||
closes = [float(k[4]) for k in klines] # 收盘价
|
||||
if len(closes) < 50:
|
||||
return "neutral"
|
||||
|
||||
ema20 = TechnicalIndicators.calculate_ema(closes, 20)
|
||||
ema50 = TechnicalIndicators.calculate_ema(closes, 50)
|
||||
macd = TechnicalIndicators.calculate_macd(closes, 12, 26, 9)
|
||||
macd_hist = macd.get("histogram") if macd else None
|
||||
|
||||
price_4h = closes[-1]
|
||||
return _judge_trend_4h(price_4h, ema20, ema50, macd_hist)
|
||||
|
||||
|
||||
def detect_regime(
|
||||
change_24h: Optional[float] = None,
|
||||
funding_rate: Optional[float] = None,
|
||||
long_short_ratio: Optional[float] = None,
|
||||
trend_4h: Optional[str] = None,
|
||||
*,
|
||||
change_bull: float = 2.0,
|
||||
change_bear: float = -2.0,
|
||||
funding_bull: float = 0.00005,
|
||||
funding_bear: float = -0.00005,
|
||||
lsr_bull: float = 1.1,
|
||||
lsr_bear: float = 0.9,
|
||||
) -> Tuple[str, dict]:
|
||||
"""
|
||||
根据各项指标打分,综合判断市场行情。
|
||||
|
||||
Returns:
|
||||
(regime, details): regime 为 'bull'|'bear'|'normal',details 为各项得分详情。
|
||||
"""
|
||||
score = 0
|
||||
details = {}
|
||||
|
||||
# 1. 24h 涨跌幅
|
||||
if change_24h is not None:
|
||||
details["change_24h"] = round(change_24h, 2)
|
||||
if change_24h >= change_bull:
|
||||
score += 1
|
||||
details["change_score"] = 1
|
||||
elif change_24h <= change_bear:
|
||||
score -= 1
|
||||
details["change_score"] = -1
|
||||
else:
|
||||
details["change_score"] = 0
|
||||
|
||||
# 2. 4H 趋势
|
||||
if trend_4h:
|
||||
details["trend_4h"] = trend_4h
|
||||
if trend_4h == "up":
|
||||
score += 1
|
||||
details["trend_score"] = 1
|
||||
elif trend_4h == "down":
|
||||
score -= 1
|
||||
details["trend_score"] = -1
|
||||
else:
|
||||
details["trend_score"] = 0
|
||||
|
||||
# 3. 资金费率
|
||||
if funding_rate is not None:
|
||||
details["funding_rate"] = round(funding_rate, 6)
|
||||
if funding_rate >= funding_bull:
|
||||
score += 1
|
||||
details["funding_score"] = 1
|
||||
elif funding_rate <= funding_bear:
|
||||
score -= 1
|
||||
details["funding_score"] = -1
|
||||
else:
|
||||
details["funding_score"] = 0
|
||||
|
||||
# 4. 大户多空比
|
||||
if long_short_ratio is not None:
|
||||
details["long_short_ratio"] = round(long_short_ratio, 4)
|
||||
if long_short_ratio >= lsr_bull:
|
||||
score += 1
|
||||
details["lsr_score"] = 1
|
||||
elif long_short_ratio <= lsr_bear:
|
||||
score -= 1
|
||||
details["lsr_score"] = -1
|
||||
else:
|
||||
details["lsr_score"] = 0
|
||||
|
||||
details["total_score"] = score
|
||||
|
||||
if score >= 2:
|
||||
return "bull", details
|
||||
if score <= -2:
|
||||
return "bear", details
|
||||
return "normal", details
|
||||
|
||||
|
||||
def detect_market_regime(symbol: str = "BTCUSDT") -> Tuple[str, dict]:
|
||||
"""
|
||||
主入口:拉取 Binance 数据并判断市场行情。
|
||||
|
||||
Args:
|
||||
symbol: 基准交易对,默认 BTCUSDT
|
||||
|
||||
Returns:
|
||||
(regime, details): regime 为 'bull'|'bear'|'normal'
|
||||
"""
|
||||
if symbol != "BTCUSDT":
|
||||
logger.warning("当前仅支持 BTCUSDT,将使用 BTCUSDT")
|
||||
symbol = "BTCUSDT"
|
||||
|
||||
change_24h = fetch_btc_24h_change()
|
||||
funding_rate = fetch_btc_funding_rate()
|
||||
long_short_ratio = fetch_btc_long_short_ratio(period="1d", limit=1)
|
||||
klines = fetch_btc_klines_4h(limit=60)
|
||||
trend_4h = compute_trend_4h_from_klines(klines) if klines else None
|
||||
|
||||
regime, details = detect_regime(
|
||||
change_24h=change_24h,
|
||||
funding_rate=funding_rate,
|
||||
long_short_ratio=long_short_ratio,
|
||||
trend_4h=trend_4h,
|
||||
)
|
||||
return regime, details
|
||||
|
|
@ -198,6 +198,13 @@ class TradingStrategy:
|
|||
|
||||
# 确定交易方向(基于技术指标)
|
||||
trade_direction = trade_signal['direction']
|
||||
# 4H 下跌禁止开多(熊市/保守方案:避免逆势抄底)
|
||||
block_long_4h_down = bool(config.TRADING_CONFIG.get("BLOCK_LONG_WHEN_4H_DOWN", False))
|
||||
if block_long_4h_down and trade_direction == "BUY" and trade_signal.get("trend_4h") == "down":
|
||||
logger.info(
|
||||
f"{symbol} 4H 趋势下跌,禁止开多(BLOCK_LONG_WHEN_4H_DOWN=true),跳过"
|
||||
)
|
||||
continue
|
||||
# 开仓前资金费率过滤:避免在费率极端不利于己方时进场
|
||||
if not await self._check_funding_rate_filter(symbol, trade_direction):
|
||||
logger.info(f"{symbol} 资金费率过滤未通过,跳过开仓")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user