diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index ca5462d..5f01af3 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -20,6 +20,49 @@ from api.auth_deps import get_current_user, get_account_id, require_admin, requi logger = logging.getLogger(__name__) router = APIRouter() +# 全局策略账号(管理员统一维护策略核心)。默认 1,可用环境变量覆盖。 +def _global_strategy_account_id() -> int: + try: + return int((__import__("os").getenv("ATS_GLOBAL_STRATEGY_ACCOUNT_ID") or "1").strip() or "1") + except Exception: + return 1 + +# 产品模式:平台兜底(策略核心由管理员统一控制),普通用户仅能调“风险旋钮” +# - admin:可修改所有配置 +# - 非 admin(account owner):只允许修改少量风险类配置 + 账号私有密钥/测试网 +USER_RISK_KNOBS = { + # 风险暴露(保证金占用比例/最小保证金) + "MIN_MARGIN_USDT", + "MIN_POSITION_PERCENT", + "MAX_POSITION_PERCENT", + "MAX_TOTAL_POSITION_PERCENT", + # 行为控制(傻瓜化) + "AUTO_TRADE_ENABLED", # 总开关:关闭则只生成推荐不自动下单 + "MAX_OPEN_POSITIONS", # 同时持仓数量上限 + "MAX_DAILY_ENTRIES", # 每日最多开仓次数 +} + +RISK_KNOBS_DEFAULTS = { + "AUTO_TRADE_ENABLED": { + "value": True, + "type": "boolean", + "category": "risk", + "description": "自动交易总开关:关闭后仅生成推荐,不会自动下单(适合先观察/体验)。", + }, + "MAX_OPEN_POSITIONS": { + "value": 3, + "type": "number", + "category": "risk", + "description": "同时持仓数量上限(防止仓位过多/难管理)。建议 1-5。", + }, + "MAX_DAILY_ENTRIES": { + "value": 8, + "type": "number", + "category": "risk", + "description": "每日最多开仓次数(防止高频下单/过度交易)。建议 3-15。", + }, +} + # API key/secret 脱敏 def _mask(s: str) -> str: s = "" if s is None else str(s) @@ -163,6 +206,18 @@ async def get_all_configs( for k, meta in AUTO_TRADE_FILTER_DEFAULTS.items(): if k not in result: result[k] = meta + + for k, meta in RISK_KNOBS_DEFAULTS.items(): + if k not in result: + result[k] = meta + + # 普通用户:只展示风险旋钮 + 账号密钥(尽量傻瓜化,避免改坏策略) + # 管理员:若当前不是“全局策略账号”,同样只展示风险旋钮,避免误以为这里改策略能生效 + is_admin = (user.get("role") or "user") == "admin" + gid = _global_strategy_account_id() + if (not is_admin) or (is_admin and int(account_id) != int(gid)): + allowed = set(USER_RISK_KNOBS) | {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"} + result = {k: v for k, v in result.items() if k in allowed} return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -170,6 +225,7 @@ async def get_all_configs( @router.get("/feasibility-check") async def check_config_feasibility( + user: Dict[str, Any] = Depends(get_current_user), account_id: int = Depends(get_account_id), ): """ @@ -194,13 +250,27 @@ async def check_config_feasibility( "suggestions": [] } - # 获取当前配置 - min_margin_usdt = TradingConfig.get_value('MIN_MARGIN_USDT', 5.0, account_id=account_id) - min_position_percent = TradingConfig.get_value('MIN_POSITION_PERCENT', 0.02, account_id=account_id) - max_position_percent = TradingConfig.get_value('MAX_POSITION_PERCENT', 0.08, account_id=account_id) - base_leverage = TradingConfig.get_value('LEVERAGE', 10, account_id=account_id) - max_leverage = TradingConfig.get_value('MAX_LEVERAGE', 15, account_id=account_id) - use_dynamic_leverage = TradingConfig.get_value('USE_DYNAMIC_LEVERAGE', True, account_id=account_id) + # 获取当前“有效配置”(平台兜底:策略核心可能来自全局账号) + try: + import config_manager as _cfg_mgr # type: ignore + + mgr = _cfg_mgr.ConfigManager.for_account(int(account_id)) if hasattr(_cfg_mgr, "ConfigManager") else None + tc = mgr.get_trading_config() if mgr else {} + except Exception: + tc = {} + + def _tc(key: str, default): + try: + return tc.get(key, default) + except Exception: + return default + + min_margin_usdt = float(_tc('MIN_MARGIN_USDT', 5.0)) + min_position_percent = float(_tc('MIN_POSITION_PERCENT', 0.02)) + max_position_percent = float(_tc('MAX_POSITION_PERCENT', 0.08)) + base_leverage = int(_tc('LEVERAGE', 10)) + max_leverage = int(_tc('MAX_LEVERAGE', 15)) + use_dynamic_leverage = bool(_tc('USE_DYNAMIC_LEVERAGE', True)) # 检查所有可能的杠杆倍数(考虑动态杠杆) leverage_to_check = [base_leverage] @@ -425,6 +495,13 @@ async def check_config_feasibility( "leverage_results": leverage_results }) + # 普通用户:只返回可调整的风险旋钮建议,避免前端一键应用时触发 403 + if (user.get("role") or "user") != "admin": + try: + suggestions = [s for s in (suggestions or []) if s.get("config_key") in USER_RISK_KNOBS] + except Exception: + suggestions = [] + return { "feasible": is_feasible, "account_balance": available_balance, @@ -506,6 +583,18 @@ async def update_config( if (user.get("role") or "user") != "admin": require_account_owner(account_id, user) + # 管理员:若不是全局策略账号,则禁止修改策略核心(避免误操作) + if (user.get("role") or "user") == "admin": + gid = _global_strategy_account_id() + if int(account_id) != int(gid): + if key not in (USER_RISK_KNOBS | {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}): + raise HTTPException(status_code=403, detail=f"该配置由全局策略账号 #{gid} 统一管理,请切换到该账号修改") + + # 产品模式:普通用户只能改“风险旋钮”与账号私有密钥/测试网 + if (user.get("role") or "user") != "admin": + if key not in (USER_RISK_KNOBS | {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}): + raise HTTPException(status_code=403, detail="该配置由平台统一管理(仅管理员可修改)") + # API Key/Secret/Testnet:写入 accounts 表(账号私有) if key in {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}: if (user.get("role") or "user") != "admin": @@ -534,7 +623,7 @@ async def update_config( description = item.description or existing['description'] else: # 允许创建新配置(用于新功能首次上线,DB 里还没有 key 的情况) - meta = SMART_ENTRY_CONFIG_DEFAULTS.get(key) or AUTO_TRADE_FILTER_DEFAULTS.get(key) + meta = SMART_ENTRY_CONFIG_DEFAULTS.get(key) or AUTO_TRADE_FILTER_DEFAULTS.get(key) or RISK_KNOBS_DEFAULTS.get(key) config_type = item.type or (meta.get("type") if meta else "string") category = item.category or (meta.get("category") if meta else "strategy") description = item.description or (meta.get("description") if meta else f"{key}配置") @@ -602,11 +691,31 @@ async def update_configs_batch( # 非管理员:必须是该账号 owner 才允许修改配置 if (user.get("role") or "user") != "admin": require_account_owner(account_id, user) + + # 管理员:若不是全局策略账号,则批量只允许风险旋钮/密钥 + if (user.get("role") or "user") == "admin": + gid = _global_strategy_account_id() + if int(account_id) != int(gid): + # 直接过滤掉不允许的项(给出 errors,避免“部分成功但实际无效”的错觉) + pass updated_count = 0 errors = [] for item in configs: try: + if (user.get("role") or "user") == "admin": + gid = _global_strategy_account_id() + if int(account_id) != int(gid): + if item.key not in (USER_RISK_KNOBS | {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}): + errors.append(f"{item.key}: 该配置由全局策略账号 #{gid} 统一管理,请切换账号修改") + continue + + # 产品模式:普通用户只能改“风险旋钮”与账号私有密钥/测试网 + if (user.get("role") or "user") != "admin": + if item.key not in (USER_RISK_KNOBS | {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}): + errors.append(f"{item.key}: 该配置由平台统一管理(仅管理员可修改)") + continue + if item.key in {"BINANCE_API_KEY", "BINANCE_API_SECRET", "USE_TESTNET"}: if (user.get("role") or "user") != "admin": require_account_owner(account_id, user) @@ -660,3 +769,15 @@ async def update_configs_batch( } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/meta") +async def get_config_meta(user: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]: + gid = _global_strategy_account_id() + is_admin = (user.get("role") or "user") == "admin" + return { + "global_strategy_account_id": int(gid), + "is_admin": bool(is_admin), + "user_risk_knobs": sorted(list(USER_RISK_KNOBS)), + "note": "平台兜底模式:策略核心由全局策略账号统一管理;普通用户仅可调整风险旋钮。", + } diff --git a/backend/config_manager.py b/backend/config_manager.py index c19e5cd..07e55b2 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -47,6 +47,24 @@ import logging logger = logging.getLogger(__name__) +# 平台兜底:策略核心使用全局账号配置(默认 account_id=1),普通用户账号只允许调整“风险旋钮” +# - 风险旋钮:每个账号独立(仓位/频次等) +# - 其它策略参数:统一从全局账号读取,避免每个用户乱改导致策略不可控 +try: + GLOBAL_STRATEGY_ACCOUNT_ID = int(os.getenv("ATS_GLOBAL_STRATEGY_ACCOUNT_ID") or "1") +except Exception: + GLOBAL_STRATEGY_ACCOUNT_ID = 1 + +RISK_KNOBS_KEYS = { + "MIN_MARGIN_USDT", + "MIN_POSITION_PERCENT", + "MAX_POSITION_PERCENT", + "MAX_TOTAL_POSITION_PERCENT", + "AUTO_TRADE_ENABLED", + "MAX_OPEN_POSITIONS", + "MAX_DAILY_ENTRIES", +} + # 尝试导入同步Redis客户端(用于配置缓存) try: import redis @@ -474,71 +492,105 @@ class ConfigManager: def get_trading_config(self): """获取交易配置字典(兼容原有config.py的TRADING_CONFIG)""" + # 全局策略配置管理器(避免递归:当 self 就是全局账号时,不做跨账号读取) + global_mgr = None + if self.account_id != int(GLOBAL_STRATEGY_ACCOUNT_ID or 1): + try: + global_mgr = ConfigManager.for_account(int(GLOBAL_STRATEGY_ACCOUNT_ID or 1)) + except Exception: + global_mgr = None + # 预热全局 cache:避免每个 key 都 HGET 一次 + if global_mgr is not None: + try: + global_mgr.reload_from_redis() + except Exception: + pass + + def eff_get(key: str, default: Any): + """ + 策略核心:默认从全局账号读取(GLOBAL_STRATEGY_ACCOUNT_ID)。 + 风险旋钮:从当前账号读取。 + """ + # API key/secret/testnet 永远按账号读取(在 get() 内部已处理) + if key in RISK_KNOBS_KEYS or global_mgr is None: + return self.get(key, default) + try: + if key in global_mgr._cache: # noqa: SLF001 + return global_mgr._cache.get(key, default) # noqa: SLF001 + return global_mgr.get(key, default) + except Exception: + return self.get(key, default) + return { # 仓位控制 - 'MAX_POSITION_PERCENT': self.get('MAX_POSITION_PERCENT', 0.08), # 提高单笔仓位到8% - 'MAX_TOTAL_POSITION_PERCENT': self.get('MAX_TOTAL_POSITION_PERCENT', 0.40), # 提高总仓位到40% - 'MIN_POSITION_PERCENT': self.get('MIN_POSITION_PERCENT', 0.02), # 提高最小仓位到2% - 'MIN_MARGIN_USDT': self.get('MIN_MARGIN_USDT', 5.0), # 提高最小保证金到5美元 + 'MAX_POSITION_PERCENT': eff_get('MAX_POSITION_PERCENT', 0.08), # 单笔最大保证金占比 + 'MAX_TOTAL_POSITION_PERCENT': eff_get('MAX_TOTAL_POSITION_PERCENT', 0.40), # 总保证金占比上限 + 'MIN_POSITION_PERCENT': eff_get('MIN_POSITION_PERCENT', 0.02), # 最小保证金占比 + 'MIN_MARGIN_USDT': eff_get('MIN_MARGIN_USDT', 5.0), # 最小保证金(USDT) + + # 用户风险旋钮:自动交易开关/频次控制 + 'AUTO_TRADE_ENABLED': eff_get('AUTO_TRADE_ENABLED', True), + 'MAX_OPEN_POSITIONS': eff_get('MAX_OPEN_POSITIONS', 3), + 'MAX_DAILY_ENTRIES': eff_get('MAX_DAILY_ENTRIES', 8), # 涨跌幅阈值 - 'MIN_CHANGE_PERCENT': self.get('MIN_CHANGE_PERCENT', 2.0), - 'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10), + 'MIN_CHANGE_PERCENT': eff_get('MIN_CHANGE_PERCENT', 2.0), + 'TOP_N_SYMBOLS': eff_get('TOP_N_SYMBOLS', 10), # 风险控制 - 'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.10), # 默认10% - 'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.30), # 默认30%(盈亏比3:1) - 'MIN_STOP_LOSS_PRICE_PCT': self.get('MIN_STOP_LOSS_PRICE_PCT', 0.02), # 默认2% - 'MIN_TAKE_PROFIT_PRICE_PCT': self.get('MIN_TAKE_PROFIT_PRICE_PCT', 0.03), # 默认3% - 'USE_ATR_STOP_LOSS': self.get('USE_ATR_STOP_LOSS', True), # 是否使用ATR动态止损 - 'ATR_STOP_LOSS_MULTIPLIER': self.get('ATR_STOP_LOSS_MULTIPLIER', 1.8), # ATR止损倍数(1.5-2倍) - 'ATR_TAKE_PROFIT_MULTIPLIER': self.get('ATR_TAKE_PROFIT_MULTIPLIER', 3.0), # ATR止盈倍数(3倍ATR) - 'RISK_REWARD_RATIO': self.get('RISK_REWARD_RATIO', 3.0), # 盈亏比(止损距离的倍数) - 'ATR_PERIOD': self.get('ATR_PERIOD', 14), # ATR计算周期 - 'USE_DYNAMIC_ATR_MULTIPLIER': self.get('USE_DYNAMIC_ATR_MULTIPLIER', False), # 是否根据波动率动态调整ATR倍数 - 'ATR_MULTIPLIER_MIN': self.get('ATR_MULTIPLIER_MIN', 1.5), # 动态ATR倍数最小值 - 'ATR_MULTIPLIER_MAX': self.get('ATR_MULTIPLIER_MAX', 2.5), # 动态ATR倍数最大值 + 'STOP_LOSS_PERCENT': eff_get('STOP_LOSS_PERCENT', 0.10), # 默认10% + 'TAKE_PROFIT_PERCENT': eff_get('TAKE_PROFIT_PERCENT', 0.30), # 默认30%(盈亏比3:1) + 'MIN_STOP_LOSS_PRICE_PCT': eff_get('MIN_STOP_LOSS_PRICE_PCT', 0.02), # 默认2% + 'MIN_TAKE_PROFIT_PRICE_PCT': eff_get('MIN_TAKE_PROFIT_PRICE_PCT', 0.03), # 默认3% + 'USE_ATR_STOP_LOSS': eff_get('USE_ATR_STOP_LOSS', True), # 是否使用ATR动态止损 + 'ATR_STOP_LOSS_MULTIPLIER': eff_get('ATR_STOP_LOSS_MULTIPLIER', 1.8), # ATR止损倍数(1.5-2倍) + 'ATR_TAKE_PROFIT_MULTIPLIER': eff_get('ATR_TAKE_PROFIT_MULTIPLIER', 3.0), # ATR止盈倍数(3倍ATR) + 'RISK_REWARD_RATIO': eff_get('RISK_REWARD_RATIO', 3.0), # 盈亏比(止损距离的倍数) + 'ATR_PERIOD': eff_get('ATR_PERIOD', 14), # ATR计算周期 + 'USE_DYNAMIC_ATR_MULTIPLIER': eff_get('USE_DYNAMIC_ATR_MULTIPLIER', False), # 是否根据波动率动态调整ATR倍数 + 'ATR_MULTIPLIER_MIN': eff_get('ATR_MULTIPLIER_MIN', 1.5), # 动态ATR倍数最小值 + 'ATR_MULTIPLIER_MAX': eff_get('ATR_MULTIPLIER_MAX', 2.5), # 动态ATR倍数最大值 # 市场扫描(1小时主周期) - 'SCAN_INTERVAL': self.get('SCAN_INTERVAL', 3600), # 1小时 - 'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10), # 每次扫描后处理的交易对数量 - 'MAX_SCAN_SYMBOLS': self.get('MAX_SCAN_SYMBOLS', 500), # 扫描的最大交易对数量(0表示扫描所有) - 'KLINE_INTERVAL': self.get('KLINE_INTERVAL', '1h'), - 'PRIMARY_INTERVAL': self.get('PRIMARY_INTERVAL', '1h'), - 'CONFIRM_INTERVAL': self.get('CONFIRM_INTERVAL', '4h'), - 'ENTRY_INTERVAL': self.get('ENTRY_INTERVAL', '15m'), + 'SCAN_INTERVAL': eff_get('SCAN_INTERVAL', 3600), # 1小时 + 'TOP_N_SYMBOLS': eff_get('TOP_N_SYMBOLS', 10), # 每次扫描后处理的交易对数量 + 'MAX_SCAN_SYMBOLS': eff_get('MAX_SCAN_SYMBOLS', 500), # 扫描的最大交易对数量(0表示扫描所有) + 'KLINE_INTERVAL': eff_get('KLINE_INTERVAL', '1h'), + 'PRIMARY_INTERVAL': eff_get('PRIMARY_INTERVAL', '1h'), + 'CONFIRM_INTERVAL': eff_get('CONFIRM_INTERVAL', '4h'), + 'ENTRY_INTERVAL': eff_get('ENTRY_INTERVAL', '15m'), # 过滤条件 - 'MIN_VOLUME_24H': self.get('MIN_VOLUME_24H', 10000000), - 'MIN_VOLATILITY': self.get('MIN_VOLATILITY', 0.02), + 'MIN_VOLUME_24H': eff_get('MIN_VOLUME_24H', 10000000), + 'MIN_VOLATILITY': eff_get('MIN_VOLATILITY', 0.02), # 高胜率策略参数 - 'MIN_SIGNAL_STRENGTH': self.get('MIN_SIGNAL_STRENGTH', 5), - 'LEVERAGE': self.get('LEVERAGE', 10), - 'USE_DYNAMIC_LEVERAGE': self.get('USE_DYNAMIC_LEVERAGE', True), - 'MAX_LEVERAGE': self.get('MAX_LEVERAGE', 15), # 降低到15,更保守,配合更大的保证金 - 'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True), - 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.10), # 默认10%(给趋势更多空间) - 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.05), # 默认5%(保护更多利润) + 'MIN_SIGNAL_STRENGTH': eff_get('MIN_SIGNAL_STRENGTH', 5), + 'LEVERAGE': eff_get('LEVERAGE', 10), + 'USE_DYNAMIC_LEVERAGE': eff_get('USE_DYNAMIC_LEVERAGE', True), + 'MAX_LEVERAGE': eff_get('MAX_LEVERAGE', 15), # 降低到15,更保守,配合更大的保证金 + 'USE_TRAILING_STOP': eff_get('USE_TRAILING_STOP', True), + 'TRAILING_STOP_ACTIVATION': eff_get('TRAILING_STOP_ACTIVATION', 0.10), # 默认10%(给趋势更多空间) + 'TRAILING_STOP_PROTECT': eff_get('TRAILING_STOP_PROTECT', 0.05), # 默认5%(保护更多利润) # 自动交易过滤(用于提升胜率/控频) # 说明:这两个 key 需要出现在 TRADING_CONFIG 中,否则 trading_system 在每次 reload_from_redis 后会丢失它们, # 导致始终按默认值拦截自动交易(用户在配置页怎么开都没用)。 - 'AUTO_TRADE_ONLY_TRENDING': self.get('AUTO_TRADE_ONLY_TRENDING', True), - 'AUTO_TRADE_ALLOW_4H_NEUTRAL': self.get('AUTO_TRADE_ALLOW_4H_NEUTRAL', False), + 'AUTO_TRADE_ONLY_TRENDING': eff_get('AUTO_TRADE_ONLY_TRENDING', True), + 'AUTO_TRADE_ALLOW_4H_NEUTRAL': eff_get('AUTO_TRADE_ALLOW_4H_NEUTRAL', False), # 智能入场/限价偏移(部分逻辑会直接读取 TRADING_CONFIG) - 'LIMIT_ORDER_OFFSET_PCT': self.get('LIMIT_ORDER_OFFSET_PCT', 0.5), - 'SMART_ENTRY_ENABLED': self.get('SMART_ENTRY_ENABLED', False), - 'SMART_ENTRY_STRONG_SIGNAL': self.get('SMART_ENTRY_STRONG_SIGNAL', 8), - 'ENTRY_SYMBOL_COOLDOWN_SEC': self.get('ENTRY_SYMBOL_COOLDOWN_SEC', 120), - 'ENTRY_TIMEOUT_SEC': self.get('ENTRY_TIMEOUT_SEC', 180), - 'ENTRY_STEP_WAIT_SEC': self.get('ENTRY_STEP_WAIT_SEC', 15), - 'ENTRY_CHASE_MAX_STEPS': self.get('ENTRY_CHASE_MAX_STEPS', 4), - 'ENTRY_MARKET_FALLBACK_AFTER_SEC': self.get('ENTRY_MARKET_FALLBACK_AFTER_SEC', 45), - 'ENTRY_CONFIRM_TIMEOUT_SEC': self.get('ENTRY_CONFIRM_TIMEOUT_SEC', 30), - 'ENTRY_MAX_DRIFT_PCT_TRENDING': self.get('ENTRY_MAX_DRIFT_PCT_TRENDING', 0.6), - 'ENTRY_MAX_DRIFT_PCT_RANGING': self.get('ENTRY_MAX_DRIFT_PCT_RANGING', 0.3), + 'LIMIT_ORDER_OFFSET_PCT': eff_get('LIMIT_ORDER_OFFSET_PCT', 0.5), + 'SMART_ENTRY_ENABLED': eff_get('SMART_ENTRY_ENABLED', False), + 'SMART_ENTRY_STRONG_SIGNAL': eff_get('SMART_ENTRY_STRONG_SIGNAL', 8), + 'ENTRY_SYMBOL_COOLDOWN_SEC': eff_get('ENTRY_SYMBOL_COOLDOWN_SEC', 120), + 'ENTRY_TIMEOUT_SEC': eff_get('ENTRY_TIMEOUT_SEC', 180), + 'ENTRY_STEP_WAIT_SEC': eff_get('ENTRY_STEP_WAIT_SEC', 15), + 'ENTRY_CHASE_MAX_STEPS': eff_get('ENTRY_CHASE_MAX_STEPS', 4), + 'ENTRY_MARKET_FALLBACK_AFTER_SEC': eff_get('ENTRY_MARKET_FALLBACK_AFTER_SEC', 45), + 'ENTRY_CONFIRM_TIMEOUT_SEC': eff_get('ENTRY_CONFIRM_TIMEOUT_SEC', 30), + 'ENTRY_MAX_DRIFT_PCT_TRENDING': eff_get('ENTRY_MAX_DRIFT_PCT_TRENDING', 0.6), + 'ENTRY_MAX_DRIFT_PCT_RANGING': eff_get('ENTRY_MAX_DRIFT_PCT_RANGING', 0.3), } diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index 595db62..105537a 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -16,11 +16,22 @@ const ConfigPanel = ({ currentUser }) => { const [accountTradingStatus, setAccountTradingStatus] = useState(null) const [accountTradingErr, setAccountTradingErr] = useState('') const [currentAccountMeta, setCurrentAccountMeta] = useState(null) + const [configMeta, setConfigMeta] = useState(null) // 多账号:当前账号(仅用于配置页提示;全局切换器在顶部导航) const [accountId, setAccountId] = useState(getCurrentAccountId()) const isAdmin = (currentUser?.role || '') === 'admin' + const globalStrategyAccountId = parseInt(String(configMeta?.global_strategy_account_id || '1'), 10) || 1 + const isGlobalStrategyAccount = isAdmin && accountId === globalStrategyAccountId + const loadConfigMeta = async () => { + try { + const m = await api.getConfigMeta() + setConfigMeta(m || null) + } catch (e) { + setConfigMeta(null) + } + } // 账号管理(超管) const [accountsAdmin, setAccountsAdmin] = useState([]) @@ -394,6 +405,7 @@ const ConfigPanel = ({ currentUser }) => { } useEffect(() => { + loadConfigMeta() loadConfigs() checkFeasibility() if (isAdmin) { @@ -808,6 +820,46 @@ const ConfigPanel = ({ currentUser }) => {
AUTO_TRADE_ONLY_TRENDING 关掉、把 AUTO_TRADE_ALLOW_4H_NEUTRAL 打开。
+ LIMIT_ORDER_OFFSET_PCT、调大 ENTRY_CONFIRM_TIMEOUT_SEC。
+ AUTO_TRADE_ONLY_TRENDING 关掉、把 AUTO_TRADE_ALLOW_4H_NEUTRAL 打开。
- LIMIT_ORDER_OFFSET_PCT、调大 ENTRY_CONFIRM_TIMEOUT_SEC。
-