fix(live): max_pool 全链默认 30→0(0=不限)——08-24巡检定罪MVP限流泄漏生产,选股池=代码序前30只失真 [vps]
CI/CD / test (push) Failing after 12m2s
CI/CD / nas-deploy (push) Has been skipped
CI/CD / nas-verify (push) Has been skipped

根因(策略session 08-24 午休探针实证):runtime/live_strategy.py SANGUO_LIVE_MAX_POOL
默认30经env注入全部实盘+影子+paper实例,_stock_pool截断成份池为「代码序前30只」:
small_cap「全市场最小市值」实际在000001平安银行等30只固定代码里选(平安银行≈3800亿
出现在小市值买入=market_cap开盘NaN排序失效叠bug);momentum每行业RPS只在代码序前
30里排;value 0/30+零委托史同源。注释自曝「MVP验证用」=限流遗留泄漏生产,上线
首日起全部选股失真。

改动(9处默认位一致30→0;语义0=不限,与策略层max_pool>0才截断一致):
- sanguo_portfolio/live_strategy.py 适配器env默认+docstring
- sanguo_live/runner.py _portfolio_env_for(存量DB显式值不篡改,缺列/0→"0")
- sanguo_trader/shadow/supervisor.py 影子env默认
- sanguo_portfolio/runner_live.py live_env默认
- sanguo_trader/portfolio_paper.py + sanguo_api/routes_paper.py paper默认
- sanguo_api/routes_live.py create setdefault
- frontend live/paper New.vue 表单默认

测试:env mapping三态断言(缺列/0→"0",显式30不篡改)+live_env默认"0"
(RED→GREEN);CI范围642绿。存量实例DB仍存显式30,激活需配套DB迁移,必须与数据
session的get_security_info_batch SQL治本(101s→亚秒)同车部署——池放大×慢SQL=更糟。
This commit is contained in:
2026-08-24 13:34:01 +08:00
parent d575029788
commit f214e2f0e4
10 changed files with 18 additions and 11 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ const isPortfolio = computed(() => strategyType.value === 'portfolio')
const portfolioOptions = ref<string[]>([])
const portfolioStrategy = ref('all_weather')
const poolForm = ref({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
const poolForm = ref({ pool: 'all', max_pool: 0, benchmark: '000300.XSHG' })
const POOL_OPTIONS = [
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
{ label: '全市场', value: 'all' },
+1 -1
View File
@@ -27,7 +27,7 @@ const instancesOfCta = computed(() => {
const file = classToFile.value[form.value.strategies[0]?.name]
return file ? instanceOptions.value.filter((i) => i.code_file === file) : instanceOptions.value
})
const poolForm = reactive({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
const poolForm = reactive({ pool: 'all', max_pool: 0, benchmark: '000300.XSHG' })
const POOL_OPTIONS = [
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
{ label: '全市场', value: 'all' },
+1 -1
View File
@@ -139,7 +139,7 @@ def create_live(req: LiveCreateRequest):
if not payload.get("strategy_class"):
raise HTTPException(400, "组合实盘需选择策略(strategy_class)")
payload.setdefault("pool", "all")
payload.setdefault("max_pool", 30)
payload.setdefault("max_pool", 0)
payload.setdefault("benchmark", "000300.XSHG")
payload["vt_symbol"] = payload["pool"]
# 周期由前端下拉传(miniQMT 成品K线档位);空=默认日线
+1 -1
View File
@@ -62,7 +62,7 @@ class PaperCreateRequest(BaseModel):
# 撮合引擎(影子柜台 P1):eod_replay=日终回放(NAS 20:30) / shadow=影子柜台(VPS 盘中实时)
engine: str = "eod_replay"
pool: str = "all"
max_pool: int = 30
max_pool: int = 0
benchmark: str = "000300.XSHG"
# §12.6 实例做实:账户绑档案;空=发起即建档(自动创建实例再发起)
instance_id: int | None = None
+1 -1
View File
@@ -349,7 +349,7 @@ def _portfolio_env_for(account_row: dict[str, Any], db_path: str) -> dict[str, s
"SANGUO_QMT_ACCOUNT": account_row.get("account", ""),
"SANGUO_QMT_PATH": account_row.get("mini_path", ""),
"SANGUO_LIVE_STRATEGY": account_row.get("strategy_class", "all_weather"),
"SANGUO_LIVE_MAX_POOL": str(account_row.get("max_pool", 30) or 30),
"SANGUO_LIVE_MAX_POOL": str(account_row.get("max_pool", 0) or 0),
"SANGUO_LIVE_BENCHMARK": account_row.get("benchmark", "000300.XSHG"),
"SANGUO_LIVE_CASH": str(account_row.get("initial_capital", 1_000_000)),
"SANGUO_LIVE_DB": db_path,
+2 -2
View File
@@ -7,7 +7,7 @@ sanguo_portfolio 的 StrategyTemplate 策略挂到 run_daily/run_monthly 定时
配置从 env (supervisor 注入,或手动 set 后直跑 ``python -m sanguo_portfolio.runner_live``):
SANGUO_LIVE_STRATEGY all_weather / momentum_timing / value_selection / small_cap
SANGUO_LIVE_MAX_POOL 选股池上限(默认 30)
SANGUO_LIVE_MAX_POOL 选股池上限(默认 0=不限;>0 截断成份池前 N )
数据 provider runner_live ``set_data_provider`` 先行注入(miniQMT live 模式)
@@ -45,7 +45,7 @@ def _build_live_strategy(provider):
)
name = os.environ.get("SANGUO_LIVE_STRATEGY", "all_weather")
max_pool = int(os.environ.get("SANGUO_LIVE_MAX_POOL", "30") or 30)
max_pool = int(os.environ.get("SANGUO_LIVE_MAX_POOL", "0") or 0)
factories = {
"all_weather": lambda: AllWeatherStrategy(
provider=provider, config=AllWeatherConfig(max_pool=max_pool)),
+1 -1
View File
@@ -49,7 +49,7 @@ def live_env() -> Dict[str, str]:
"""解析 env 实盘配置(带默认值)。独立出来便于单测。"""
return {
"strategy": os.environ.get("SANGUO_LIVE_STRATEGY", "all_weather"),
"max_pool": os.environ.get("SANGUO_LIVE_MAX_POOL", "30"),
"max_pool": os.environ.get("SANGUO_LIVE_MAX_POOL", "0"),
"benchmark": os.environ.get("SANGUO_LIVE_BENCHMARK", "000300.XSHG"),
"cash": os.environ.get("SANGUO_LIVE_CASH", "1000000"),
"account": os.environ.get("SANGUO_QMT_ACCOUNT", ""),
+1 -1
View File
@@ -72,7 +72,7 @@ def run_portfolio_live_step(db_path: str, account_id: int, today: str | None = N
raise ValueError("portfolio 账户缺少策略配置")
strategy_name = strategies[0].get("name", "all_weather")
pool = (json.loads(acc["symbols"] or "[]") or ["hs300_subset"])[0]
max_pool = int(strategies[0].get("params", {}).get("max_pool", 30))
max_pool = int(strategies[0].get("params", {}).get("max_pool", 0))
benchmark = strategies[0].get("params", {}).get("benchmark", "000300.XSHG")
from sanguo_portfolio.runner_backtest import run_backtest_json
+1 -1
View File
@@ -51,7 +51,7 @@ def account_env(acc: dict[str, Any], db_path: str) -> Dict[str, str]:
env = dict(os.environ)
env.update({
"SANGUO_LIVE_STRATEGY": name,
"SANGUO_LIVE_MAX_POOL": _num(params.get("max_pool", 30)),
"SANGUO_LIVE_MAX_POOL": _num(params.get("max_pool", 0)),
"SANGUO_LIVE_BENCHMARK": str(params.get("benchmark", "000300.XSHG")),
"SANGUO_LIVE_CASH": _num(acc.get("initial_capital") or 1_000_000),
"SANGUO_SHADOW_DB": db_path,
+8 -1
View File
@@ -118,6 +118,13 @@ def test_portfolio_env_mapping():
assert env["SANGUO_LIVE_CASH"] == "2000000"
assert env["SANGUO_LIVE_DB"] == "live.db"
assert env["SANGUO_LIVE_ACCOUNT_ID"] == "7"
# 行缺 max_pool / 存 0 → env "0"(0=不限);显式 30 等存量值不篡改
no_pool = {k: v for k, v in acc.items() if k != "max_pool"}
assert live_runner._portfolio_env_for(no_pool, "live.db")["SANGUO_LIVE_MAX_POOL"] == "0"
zero_pool = dict(acc, max_pool=0)
assert live_runner._portfolio_env_for(zero_pool, "live.db")["SANGUO_LIVE_MAX_POOL"] == "0"
kept = dict(acc, max_pool=30)
assert live_runner._portfolio_env_for(kept, "live.db")["SANGUO_LIVE_MAX_POOL"] == "30"
def test_runner_live_env_defaults(monkeypatch):
@@ -129,7 +136,7 @@ def test_runner_live_env_defaults(monkeypatch):
monkeypatch.delenv(k, raising=False)
cfg = runner_live.live_env()
assert cfg["strategy"] == "all_weather"
assert cfg["max_pool"] == "30"
assert cfg["max_pool"] == "0" # 0=不限(2026-08-24 巡检:MVP 默认 30 泄漏生产致选股池失真)
assert cfg["benchmark"] == "000300.XSHG"
assert cfg["account"] == "" # 空 → run_live 拒绝启动(防误下单)