2026-07-27 09:49:05 +08:00
|
|
|
|
"""连接配置与运行旋钮:全部从环境变量/.env 读取,不硬编码任何主机。
|
2026-07-24 14:20:54 +08:00
|
|
|
|
|
|
|
|
|
|
三处连接:
|
2026-07-27 09:49:05 +08:00
|
|
|
|
AKG_PG_* —— astock-kg 基座 PostgreSQL(读只读视图,只读账号即可)
|
2026-07-24 14:20:54 +08:00
|
|
|
|
HEAT_MYSQL_* —— 153 代理 MySQL(读 stock_fund_heat_scores 热度)
|
|
|
|
|
|
FACTOR_MYSQL_* —— 平台 PROXY_DB_URL 指向的 MySQL(写 t_factor_* + factor_metadata)
|
|
|
|
|
|
现价来源 PRICE_MYSQL_*(upside 用)默认复用 FACTOR_MYSQL_* 同实例。
|
|
|
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _req(k: str) -> str:
|
|
|
|
|
|
v = os.environ.get(k)
|
|
|
|
|
|
if not v:
|
|
|
|
|
|
raise RuntimeError(f".env 缺配置: {k}")
|
|
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _opt(k: str, fallback: str) -> str:
|
|
|
|
|
|
return os.environ.get(k) or fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class Conn:
|
|
|
|
|
|
host: str
|
|
|
|
|
|
port: int
|
|
|
|
|
|
user: str
|
|
|
|
|
|
password: str
|
|
|
|
|
|
db: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def akg_pg() -> Conn:
|
|
|
|
|
|
return Conn(_req("AKG_PG_HOST"), int(os.environ.get("AKG_PG_PORT", 5432)),
|
|
|
|
|
|
_req("AKG_PG_USER"), os.environ.get("AKG_PG_PASSWORD", ""), _req("AKG_PG_DB"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def heat_mysql() -> Conn:
|
|
|
|
|
|
return Conn(_req("HEAT_MYSQL_HOST"), int(os.environ.get("HEAT_MYSQL_PORT", 3306)),
|
|
|
|
|
|
_req("HEAT_MYSQL_USER"), os.environ.get("HEAT_MYSQL_PASSWORD", ""), _req("HEAT_MYSQL_DB"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def factor_mysql() -> Conn:
|
|
|
|
|
|
return Conn(_req("FACTOR_MYSQL_HOST"), int(os.environ.get("FACTOR_MYSQL_PORT", 3306)),
|
|
|
|
|
|
_req("FACTOR_MYSQL_USER"), os.environ.get("FACTOR_MYSQL_PASSWORD", ""), _req("FACTOR_MYSQL_DB"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def price_mysql() -> Conn:
|
|
|
|
|
|
"""现价(gp_day_data)来源,默认与因子库同实例。"""
|
|
|
|
|
|
return Conn(_opt("PRICE_MYSQL_HOST", _req("FACTOR_MYSQL_HOST")),
|
|
|
|
|
|
int(_opt("PRICE_MYSQL_PORT", os.environ.get("FACTOR_MYSQL_PORT", "3306"))),
|
|
|
|
|
|
_opt("PRICE_MYSQL_USER", _req("FACTOR_MYSQL_USER")),
|
|
|
|
|
|
_opt("PRICE_MYSQL_PASSWORD", os.environ.get("FACTOR_MYSQL_PASSWORD", "")),
|
|
|
|
|
|
_opt("PRICE_MYSQL_DB", _req("FACTOR_MYSQL_DB")))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-27 09:49:05 +08:00
|
|
|
|
# gp_day_data 股票代码列名(平台实测 = symbol;代码仍会自动兜底逐个试)
|
2026-07-24 14:20:54 +08:00
|
|
|
|
PRICE_CODE_COL = os.environ.get("PRICE_CODE_COL", "ts_code")
|
|
|
|
|
|
FACTOR_API_BASE = os.environ.get("FACTOR_API_BASE", "").rstrip("/")
|
2026-07-27 09:49:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
# 运行旋钮(2026-07-26 评审引入)。全部有默认值,不填也能跑。
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
# --- 子因子是否受覆盖池限制:pool(现状)| market(评审 §4 建议)------------
|
|
|
|
|
|
# industry_pools 只存最新态、每周一 refresh_pools 自动长大。用它过滤**历史**子因子
|
|
|
|
|
|
# 会引入成员性前视(值点时正确、成员性前视——这是 §2.2 批传导用的同一条论证,
|
|
|
|
|
|
# 上升了一层),且截面统计量每周一结构性跳变。
|
|
|
|
|
|
# 子因子表的定位是「可独立观察的仪表」,过滤应发生在消费端(akg_score),不在这里。
|
|
|
|
|
|
# 传导不受此开关影响——它天生就是池内语义。
|
|
|
|
|
|
SUBFACTOR_UNIVERSE = os.environ.get("SUBFACTOR_UNIVERSE", "pool").strip().lower()
|
|
|
|
|
|
|
|
|
|
|
|
# --- 行情读取按月分块 -------------------------------------------------------
|
|
|
|
|
|
# 原来一次拉全区间,`--mode history --start 2006-01-01` 会把千万级行拉进 pandas。
|
|
|
|
|
|
PRICE_CHUNK_DAYS = int(os.environ.get("PRICE_CHUNK_DAYS", "31"))
|
|
|
|
|
|
|
|
|
|
|
|
# --- 因子表写入分块行数 -----------------------------------------------------
|
|
|
|
|
|
# 热度全史约 322×1674≈54 万行,原来单事务 executemany 走 ShardingSphere 代理有风险。
|
|
|
|
|
|
WRITE_CHUNK_ROWS = int(os.environ.get("WRITE_CHUNK_ROWS", "50000"))
|
|
|
|
|
|
|
|
|
|
|
|
# --- 事件来源白名单与单文档封顶(评审 §6.5:年报污染)----------------------
|
|
|
|
|
|
# documents.source_type 取值(astock-kg db/postgres/init.sql:12 已核实):
|
|
|
|
|
|
# annual_report / research_report / announcement / news / interactive_qa
|
|
|
|
|
|
# 年报也带 company_ts_code 文档锚,而一份年报能抽十几条 EVENT,且含**历史**诉讼/
|
|
|
|
|
|
# 处罚——会在年报披露日形成巨大负值尖峰。S2 若用 akg_event < −θ_e 做否决闸,
|
|
|
|
|
|
# 会因为一份年报提到历史诉讼而否决掉一只好股。默认只认公告。
|
|
|
|
|
|
EVENT_SOURCE_TYPES = {
|
|
|
|
|
|
s.strip() for s in
|
|
|
|
|
|
os.environ.get("EVENT_SOURCE_TYPES", "announcement").split(",") if s.strip()}
|
|
|
|
|
|
EVENT_MAX_PER_DOC = int(os.environ.get("EVENT_MAX_PER_DOC", "3"))
|
|
|
|
|
|
|
|
|
|
|
|
# --- 输入冻结落点(freeze.py)----------------------------------------------
|
|
|
|
|
|
# 容器内路径;docker-compose 已把仓库根挂到 /app,故默认落在仓库的 data/frozen/。
|
|
|
|
|
|
FROZEN_ROOT = os.environ.get("FROZEN_ROOT", "/app/data/frozen")
|
|
|
|
|
|
|
|
|
|
|
|
# --- 上游截断体检阈值(评审 硬伤1)-----------------------------------------
|
|
|
|
|
|
# 基座 graph_store.topic_context 的 Cypher 是 `LIMIT $cap`(默认 30)且无 ORDER BY;
|
|
|
|
|
|
# transmission.scan 落库时又有 `quiet[:12]`。命中这两个数就说明上游被截断了,
|
|
|
|
|
|
# 该行的 moved_ratio 建立在任意抽样上、覆盖也被展示逻辑锁住。
|
2026-07-28 09:40:05 +08:00
|
|
|
|
# 07-28 语义更新:基座二批后 topic_context cap=1000、scan 大主题闸=600、quiet 全量落库。
|
|
|
|
|
|
# 本值对齐基座"大主题闸":members_total 超过它 = 基座制度回退,桥侧告警(factors.py)。
|
|
|
|
|
|
UPSTREAM_MEMBER_CAP = int(os.environ.get("UPSTREAM_MEMBER_CAP", "600"))
|