feat(strategy_overview): 添加策略执行概览功能以优化策略分析

在 `market_overview.py` 中新增 `get_strategy_execution_overview` 函数,生成当前策略执行方案与配置项的易读概览,供全局配置页展示。更新后端API以支持该功能,并在前端组件中展示策略执行概览,提升用户对策略执行标准与机制的理解。此改动增强了系统的可用性与用户体验。
This commit is contained in:
薇薇安 2026-02-25 09:39:33 +08:00
parent 9086c15f2e
commit 41e53755ea
3 changed files with 237 additions and 44 deletions

View File

@ -1298,6 +1298,22 @@ async def market_overview(_admin: Dict[str, Any] = Depends(require_system_admin)
"MARKET_SCHEME": market_scheme, "MARKET_SCHEME": market_scheme,
} }
data["beta_filter_triggered"] = triggered data["beta_filter_triggered"] = triggered
# 策略执行概览:当前执行方案与配置项执行情况(易读文字)
try:
from market_overview import get_strategy_execution_overview
except ImportError:
try:
from backend.market_overview import get_strategy_execution_overview
except ImportError:
get_strategy_execution_overview = None
if get_strategy_execution_overview is not None:
try:
data["strategy_execution_overview"] = get_strategy_execution_overview()
except Exception as e:
data["strategy_execution_overview"] = {"sections": [{"title": "加载失败", "content": str(e)}]}
else:
data["strategy_execution_overview"] = {"sections": []}
return data return data

View File

@ -180,3 +180,162 @@ def _simple_trend_4h(klines: list) -> str:
if price < avg20 * 0.998: if price < avg20 * 0.998:
return "down" return "down"
return "neutral" return "neutral"
def _g(key: str, default: Any, cfg: dict) -> Any:
"""从配置字典取键,支持 bool/数字/字符串。"""
v = cfg.get(key, default)
if v is None:
return default
if isinstance(default, bool):
return str(v).lower() in ("true", "1", "yes")
return v
def get_strategy_execution_overview() -> Dict[str, Any]:
"""
生成策略执行概览当前执行方案配置项执行情况用易读文字描述整体策略执行标准与机制
供全局配置页策略执行概览展示
返回格式{ "sections": [ { "title": "小节标题", "content": "正文" } ] }
"""
sections = []
cfg = {}
try:
from config_manager import GlobalStrategyConfigManager
mgr = GlobalStrategyConfigManager()
mgr.reload_from_redis()
for key in (
"AUTO_TRADE_ENABLED", "AUTO_TRADE_ONLY_TRENDING", "AUTO_TRADE_ALLOW_4H_NEUTRAL",
"MIN_SIGNAL_STRENGTH", "LOW_VOLATILITY_MIN_SIGNAL_STRENGTH", "MARKET_REGIME_AUTO",
"TOP_N_SYMBOLS", "SCAN_INTERVAL", "PRIMARY_INTERVAL", "CONFIRM_INTERVAL",
"MAX_OPEN_POSITIONS", "MAX_DAILY_ENTRIES", "FIXED_RISK_PERCENT", "USE_FIXED_RISK_SIZING",
"BETA_FILTER_ENABLED", "BETA_FILTER_THRESHOLD", "MARKET_SCHEME",
"USE_ATR_STOP_LOSS", "ATR_STOP_LOSS_MULTIPLIER", "STOP_LOSS_PERCENT",
"TAKE_PROFIT_1_PERCENT", "TAKE_PROFIT_PERCENT", "USE_TRAILING_STOP",
"TRAILING_STOP_ACTIVATION", "TRAILING_STOP_PROTECT", "PROFIT_PROTECTION_ENABLED",
"SMART_ENTRY_ENABLED", "USE_TREND_ENTRY_FILTER", "MAX_TREND_MOVE_BEFORE_ENTRY",
"MAX_RSI_FOR_LONG", "MIN_RSI_FOR_SHORT", "MAX_CHANGE_PERCENT_FOR_LONG", "MAX_CHANGE_PERCENT_FOR_SHORT",
"MIN_VOLUME_24H", "MIN_VOLATILITY", "MIN_HOLD_TIME_SEC",
):
cfg[key] = mgr.get(key)
except Exception as e:
logger.debug("get_strategy_execution_overview 加载配置失败: %s", e)
def pct(x):
if x is None:
return ""
try:
f = float(x)
if abs(f) < 1 and abs(f) > 0:
return f"{f * 100:.2f}%"
return f"{f}%"
except (TypeError, ValueError):
return str(x)
# ---------- 1. 总开关与自动交易条件 ----------
auto_on = _g("AUTO_TRADE_ENABLED", True, cfg)
only_trending = _g("AUTO_TRADE_ONLY_TRENDING", True, cfg)
allow_4h_neutral = _g("AUTO_TRADE_ALLOW_4H_NEUTRAL", False, cfg)
min_strength = _g("MIN_SIGNAL_STRENGTH", 8, cfg)
low_vol_strength = _g("LOW_VOLATILITY_MIN_SIGNAL_STRENGTH", 9, cfg)
regime_auto = _g("MARKET_REGIME_AUTO", True, cfg)
c1 = []
c1.append("自动交易总开关:" + ("开启" if auto_on else "关闭"))
if not auto_on:
c1.append("关闭时仅生成推荐,不会自动下单。")
else:
c1.append("自动下单条件(需同时满足):")
c1.append("• 信号强度 ≥ " + str(min_strength) + "(技术指标综合评分);低波动期自动提高至 " + str(low_vol_strength) + "" + ("已开启" if regime_auto else "未开启") + "市场节奏识别)。")
c1.append("• 市场状态:仅当「仅做趋势市」开启时,要求市场状态为 trending 才下单ranging/unknown 只生成推荐、不自动下单。当前「仅做趋势市」=" + ("" if only_trending else "") + "")
c1.append("• 4H 趋势:允许 4H 中性时自动交易 = " + ("" if allow_4h_neutral else "") + ";为否时 4H 为中性会跳过自动下单。")
sections.append({
"title": "一、总开关与自动交易条件",
"content": "\n".join(c1),
})
# ---------- 2. 扫描与候选池 ----------
top_n = _g("TOP_N_SYMBOLS", 30, cfg)
scan_interval = _g("SCAN_INTERVAL", 900, cfg)
primary = _g("PRIMARY_INTERVAL", "4h", cfg)
confirm = _g("CONFIRM_INTERVAL", "1d", cfg)
min_vol = _g("MIN_VOLUME_24H", 30000000, cfg)
min_vol_str = f"{min_vol / 1e6:.0f} 万 USDT" if isinstance(min_vol, (int, float)) and min_vol >= 1e6 else str(min_vol)
vol_pct = _g("MIN_VOLATILITY", 0.03, cfg)
vol_pct_str = f"{float(vol_pct) * 100:.1f}%" if isinstance(vol_pct, (int, float)) else str(vol_pct)
c2 = []
c2.append("每次扫描取涨跌幅最大的前 " + str(top_n) + " 个交易对进行详细分析;扫描间隔 " + str(scan_interval) + " 秒。")
c2.append("主周期 " + str(primary) + ",确认周期 " + str(confirm) + "24h 成交额 ≥ " + min_vol_str + ",最小波动率 " + vol_pct_str + "")
sections.append({
"title": "二、扫描与候选池",
"content": "\n".join(c2),
})
# ---------- 3. 仓位与风控 ----------
max_pos = _g("MAX_OPEN_POSITIONS", 4, cfg)
max_daily = _g("MAX_DAILY_ENTRIES", 15, cfg)
fixed_risk = _g("USE_FIXED_RISK_SIZING", True, cfg)
risk_pct = _g("FIXED_RISK_PERCENT", 0.01, cfg)
risk_pct_str = pct(risk_pct) if isinstance(risk_pct, (int, float)) and risk_pct <= 1 else f"{float(risk_pct)}%"
c3 = []
c3.append("同时持仓上限 " + str(max_pos) + " 个,每日最多开仓 " + str(max_daily) + " 笔。")
c3.append("固定风险 sizing" + ("开启" if fixed_risk else "关闭") + ";每笔最大亏损 " + risk_pct_str + " 账户资金。")
sections.append({
"title": "三、仓位与风控",
"content": "\n".join(c3),
})
# ---------- 4. 大盘与市场方案 ----------
beta_on = _g("BETA_FILTER_ENABLED", True, cfg)
beta_th = _g("BETA_FILTER_THRESHOLD", -0.005, cfg)
scheme = str(_g("MARKET_SCHEME", "normal", cfg) or "normal")
c4 = []
c4.append("大盘共振过滤:" + ("开启" if beta_on else "关闭") + "BTC/ETH 短周期跌逾 " + pct(beta_th) + " 时屏蔽多单。")
c4.append("当前市场方案:" + scheme + "(用于参数预设)。")
sections.append({
"title": "四、大盘与市场方案",
"content": "\n".join(c4),
})
# ---------- 5. 止损止盈与保护 ----------
use_atr = _g("USE_ATR_STOP_LOSS", True, cfg)
atr_mult = _g("ATR_STOP_LOSS_MULTIPLIER", 2.0, cfg)
sl_pct = _g("STOP_LOSS_PERCENT", 0.05, cfg)
tp1 = _g("TAKE_PROFIT_1_PERCENT", 0.12, cfg)
tp2 = _g("TAKE_PROFIT_PERCENT", 0.25, cfg)
trail = _g("USE_TRAILING_STOP", True, cfg)
trail_act = _g("TRAILING_STOP_ACTIVATION", 0.10, cfg)
trail_prot = _g("TRAILING_STOP_PROTECT", 0.02, cfg)
profit_prot = _g("PROFIT_PROTECTION_ENABLED", True, cfg)
c5 = []
c5.append("止损ATR 动态止损 " + ("开启" if use_atr else "关闭") + (",倍数 " + str(atr_mult) if use_atr else "") + ";固定止损 " + pct(sl_pct) + "")
c5.append("止盈:第一目标 " + pct(tp1) + ",第二目标 " + pct(tp2) + "")
c5.append("盈利保护总开关:" + ("开启" if profit_prot else "关闭") + ";移动止损 " + ("开启" if trail else "关闭") + (",盈利 " + pct(trail_act) + " 激活、保护 " + pct(trail_prot) + " 利润" if trail else "") + "")
sections.append({
"title": "五、止损止盈与保护",
"content": "\n".join(c5),
})
# ---------- 6. 入场与过滤 ----------
smart = _g("SMART_ENTRY_ENABLED", True, cfg)
trend_filter = _g("USE_TREND_ENTRY_FILTER", True, cfg)
max_trend = _g("MAX_TREND_MOVE_BEFORE_ENTRY", 0.04, cfg)
max_rsi_long = _g("MAX_RSI_FOR_LONG", 65, cfg)
min_rsi_short = _g("MIN_RSI_FOR_SHORT", 30, cfg)
max_ch_long = _g("MAX_CHANGE_PERCENT_FOR_LONG", 25, cfg)
max_ch_short = _g("MAX_CHANGE_PERCENT_FOR_SHORT", 10, cfg)
c6 = []
c6.append("智能入场(限价+追价+市价兜底):" + ("开启" if smart else "关闭") + "")
c6.append("趋势入场过滤:" + ("开启" if trend_filter else "关闭") + ";信号方向已走超 " + pct(max_trend) + " 则不再入场。")
c6.append("做多RSI ≤ " + str(max_rsi_long) + "24h 涨跌幅 ≤ " + str(max_ch_long) + "%。做空RSI ≥ " + str(min_rsi_short) + "24h 涨跌幅 ≤ " + str(max_ch_short) + "%")
sections.append({
"title": "六、入场与过滤",
"content": "\n".join(c6),
})
return {"sections": sections}

View File

@ -1254,53 +1254,71 @@ const GlobalConfig = () => {
</div> </div>
</div> </div>
{marketOverview ? ( {marketOverview ? (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '12px', fontSize: '14px' }}> <>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '12px', fontSize: '14px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>BTC 24h</div> <div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ fontWeight: 600, color: marketOverview.btc_24h_change_pct >= 0 ? '#4caf50' : '#f44336' }}> <div style={{ color: '#666', fontSize: '12px' }}>BTC 24h</div>
{marketOverview.btc_24h_change_pct != null ? `${marketOverview.btc_24h_change_pct}%` : '—'} <div style={{ fontWeight: 600, color: marketOverview.btc_24h_change_pct >= 0 ? '#4caf50' : '#f44336' }}>
</div> {marketOverview.btc_24h_change_pct != null ? `${marketOverview.btc_24h_change_pct}%` : '—'}
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>ETH 24h</div>
<div style={{ fontWeight: 600, color: marketOverview.eth_24h_change_pct >= 0 ? '#4caf50' : '#f44336' }}>
{marketOverview.eth_24h_change_pct != null ? `${marketOverview.eth_24h_change_pct}%` : '—'}
</div>
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>BTC 15m / 1h</div>
<div style={{ fontWeight: 500 }}>
{marketOverview.btc_15m_change_pct != null ? marketOverview.btc_15m_change_pct : '—'}% / {marketOverview.btc_1h_change_pct != null ? marketOverview.btc_1h_change_pct : ''}%
</div>
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>ETH 15m / 1h</div>
<div style={{ fontWeight: 500 }}>
{marketOverview.eth_15m_change_pct != null ? marketOverview.eth_15m_change_pct : '—'}% / {marketOverview.eth_1h_change_pct != null ? marketOverview.eth_1h_change_pct : ''}%
</div>
</div>
<div style={{ padding: '10px', background: marketOverview.beta_filter_triggered ? '#ffebee' : '#e8f5e9', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>大盘共振过滤</div>
<div style={{ fontWeight: 600, color: marketOverview.beta_filter_triggered ? '#c62828' : '#2e7d32' }}>
{marketOverview.beta_filter_triggered ? '已触发(屏蔽多单)' : '未触发'}
</div>
{marketOverview.config && (
<div style={{ fontSize: '11px', color: '#888', marginTop: '4px' }}>
阈值 {marketOverview.config.BETA_FILTER_THRESHOLD_PCT}% · {marketOverview.config.BETA_FILTER_ENABLED ? '已开启' : '已关闭'}
</div> </div>
)} </div>
</div> <div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}> <div style={{ color: '#666', fontSize: '12px' }}>ETH 24h</div>
<div style={{ color: '#666', fontSize: '12px' }}>市场状态 / 4H</div> <div style={{ fontWeight: 600, color: marketOverview.eth_24h_change_pct >= 0 ? '#4caf50' : '#f44336' }}>
<div style={{ fontWeight: 500 }}> {marketOverview.eth_24h_change_pct != null ? `${marketOverview.eth_24h_change_pct}%` : '—'}
{marketOverview.market_regime || '—'} / {marketOverview.btc_trend_4h || '—'} </div>
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>BTC 15m / 1h</div>
<div style={{ fontWeight: 500 }}>
{marketOverview.btc_15m_change_pct != null ? marketOverview.btc_15m_change_pct : '—'}% / {marketOverview.btc_1h_change_pct != null ? marketOverview.btc_1h_change_pct : ''}%
</div>
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>ETH 15m / 1h</div>
<div style={{ fontWeight: 500 }}>
{marketOverview.eth_15m_change_pct != null ? marketOverview.eth_15m_change_pct : '—'}% / {marketOverview.eth_1h_change_pct != null ? marketOverview.eth_1h_change_pct : ''}%
</div>
</div>
<div style={{ padding: '10px', background: marketOverview.beta_filter_triggered ? '#ffebee' : '#e8f5e9', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>大盘共振过滤</div>
<div style={{ fontWeight: 600, color: marketOverview.beta_filter_triggered ? '#c62828' : '#2e7d32' }}>
{marketOverview.beta_filter_triggered ? '已触发(屏蔽多单)' : '未触发'}
</div>
{marketOverview.config && (
<div style={{ fontSize: '11px', color: '#888', marginTop: '4px' }}>
阈值 {marketOverview.config.BETA_FILTER_THRESHOLD_PCT}% · {marketOverview.config.BETA_FILTER_ENABLED ? '已开启' : '已关闭'}
</div>
)}
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>市场状态 / 4H</div>
<div style={{ fontWeight: 500 }}>
{marketOverview.market_regime || '—'} / {marketOverview.btc_trend_4h || '—'}
</div>
</div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>当前方案</div>
<div style={{ fontWeight: 600 }}>{marketOverview.config?.MARKET_SCHEME || '—'}</div>
</div> </div>
</div> </div>
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
<div style={{ color: '#666', fontSize: '12px' }}>当前方案</div> {/* 策略执行概览:当前执行方案与配置项执行情况(易读文字) */}
<div style={{ fontWeight: 600 }}>{marketOverview.config?.MARKET_SCHEME || '—'}</div> {marketOverview.strategy_execution_overview?.sections?.length > 0 && (
</div> <div className="strategy-execution-overview" style={{ marginTop: '20px', padding: '16px', background: '#fafafa', borderRadius: '8px', border: '1px solid #eee' }}>
</div> <h4 style={{ margin: '0 0 12px 0', fontSize: '15px', fontWeight: 600, color: '#333' }}>策略执行概览</h4>
<p style={{ margin: '0 0 14px 0', fontSize: '13px', color: '#666', lineHeight: 1.5 }}>
以下为当前生效的整体策略执行标准与机制说明由数据库/Redis 配置生成刷新市场行情时一并更新
</p>
{marketOverview.strategy_execution_overview.sections.map((sec, idx) => (
<div key={idx} style={{ marginBottom: idx < marketOverview.strategy_execution_overview.sections.length - 1 ? '14px' : 0 }}>
<div style={{ fontSize: '13px', fontWeight: 600, color: '#1976d2', marginBottom: '4px' }}>{sec.title}</div>
<div style={{ fontSize: '13px', color: '#444', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{sec.content}</div>
</div>
))}
</div>
)}
</>
) : ( ) : (
<div style={{ color: '#888', fontSize: '14px' }}>加载中或拉取失败请点击刷新</div> <div style={{ color: '#888', fontSize: '14px' }}>加载中或拉取失败请点击刷新</div>
)} )}