diff --git a/.gitignore b/.gitignore index 9e289f2..7fba4b6 100644 --- a/.gitignore +++ b/.gitignore @@ -45,14 +45,11 @@ backend/data/** !backend/data/.gitkeep data/** !data/.gitkeep -# 扩展数据: 仅放行 ext_gn_ths(概念)与 ext_hy_ths(行业)两个数据源 -# 含 config.json 表结构 + part.parquet 数据文件;其余 ext_data 子目录均不入库 -# (gitignore 否定规则特性: 要放行嵌套文件,需逐级放行目录 + /** 放行内部文件) -!data/ext_data/ -!data/ext_data/ext_gn_ths/ -!data/ext_data/ext_gn_ths/** -!data/ext_data/ext_hy_ths/ -!data/ext_data/ext_hy_ths/** +# data/ 下所有内容(含 ext_data 概念/行业)均不入库: +# - 用户运行时数据 (K线/财务/自选/回测等) 本就不该进 git +# - 概念/行业改为「首次启动自动从远程接口拉取」(见 app/services/ext_presets.py) +# 作者更新数据只需改接口 JSON, 用户下次拉取自动同步, 无需发版、无需碰 git。 +# 这样 git pull 物理上无法影响用户本地 data, 杜绝「更新后数据丢失」。 # ===== Secrets ===== .env diff --git a/backend/app/config.py b/backend/app/config.py index 16ee8f9..e0542d9 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -17,21 +17,29 @@ _IS_FROZEN = getattr(sys, "frozen", False) def _user_data_root() -> Path: - """桌面版用户数据根目录 (跨平台持久可写)。 + """桌面版用户数据根目录。 - Windows: %LOCALAPPDATA%/TickFlowStockPanel/TickFlowStockPanel - macOS: ~/Library/Application Support/TickFlowStockPanel - Linux: ~/.local/share/TickFlowStockPanel + 定位策略 (按优先级): + 1. 环境变量 DATA_DIR (pydantic-settings 自动注入到 settings.data_dir, 不在此处理) + 2. exe 同级的独立数据目录 <安装目录>/../TickFlowStockPanel_Data/ + —— 在 {app} 外, Inno Setup 覆盖安装/卸载都碰不到; 与 exe 同盘符, + 用户可直接看到, 不占 C 盘。 + 3. 非 frozen (开发模式): 项目根 data/ - 注意: platformdirs 已含应用名, 切勿再拼一层。 + 为什么不用 platformdirs 默认 (%LOCALAPPDATA%) 作为主路径: + - 落在 C 盘系统目录, 用户不易察觉, 占系统盘空间 + - 用户期望「数据跟随程序」(便于备份/迁移) + 为什么不直接放 {app}/data (exe 旁的 data/): + - {app} 在安装目录内, Inno Setup 覆盖安装会覆盖、卸载会清空 → 数据丢失 + 折中: 放 {app} 的同级目录 (兄弟文件夹), 既跟随 exe 又不在 {app} 内。 """ - try: - from platformdirs import user_data_dir + # 打包桌面版: exe 同级的独立数据目录 (../TickFlowStockPanel_Data/) + if _IS_FROZEN: + exe_dir = Path(sys.executable).resolve().parent + return exe_dir.parent / "TickFlowStockPanel_Data" - return Path(user_data_dir("TickFlowStockPanel")) - except Exception: # noqa: BLE001 - # platformdirs 不可用时兜底: 可执行文件旁的 data/ - return Path(sys.executable).resolve().parent / "data" + # 开发模式: 项目根 data/ + return _PROJECT_ROOT / "data" def _resource_root() -> Path: @@ -84,8 +92,9 @@ class Settings(BaseSettings): log_level: str = "INFO" backtest_range_guard: bool = False - # Data — frozen: 用户数据目录; 非 frozen: 项目根目录的 data/ (可被 DATA_DIR 覆盖) - data_dir: Path = _user_data_root() if _IS_FROZEN else (_PROJECT_ROOT / "data") + # Data — frozen: exe 同级 TickFlowStockPanel_Data/; 非 frozen: 项目根 data/ + # (均可被环境变量 DATA_DIR 覆盖, pydantic-settings 自动注入) + data_dir: Path = _user_data_root() # tiers.yaml 路径 — frozen: 资源目录内; 非 frozen: 项目根目录 tiers_yaml: Path = _RESOURCE_ROOT / "tiers.yaml" if _IS_FROZEN else _PROJECT_ROOT / "tiers.yaml" diff --git a/data/ext_data/ext_gn_ths/config.json b/data/ext_data/ext_gn_ths/config.json deleted file mode 100644 index c935093..0000000 --- a/data/ext_data/ext_gn_ths/config.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "ext_gn_ths", - "label": "扩展概念", - "mode": "snapshot", - "fields": [ - { - "name": "symbol", - "dtype": "string", - "label": "标的代码" - }, - { - "name": "code", - "dtype": "string", - "label": "代码" - }, - { - "name": "股票代码", - "dtype": "string", - "label": "股票代码" - }, - { - "name": "股票简称", - "dtype": "string", - "label": "股票简称" - }, - { - "name": "所属概念", - "dtype": "string", - "label": "所属概念" - } - ], - "description": "", - "symbol_map": { - "type": "mapped", - "col": "股票代码" - }, - "code_map": { - "type": "computed", - "from": "symbol", - "method": "strip_exchange" - }, - "created_at": "2026-06-27T12:18:23.629390", - "updated_at": "2026-06-27T12:18:23.629390" -} \ No newline at end of file diff --git a/data/ext_data/ext_gn_ths/part.parquet b/data/ext_data/ext_gn_ths/part.parquet deleted file mode 100644 index 1525760..0000000 Binary files a/data/ext_data/ext_gn_ths/part.parquet and /dev/null differ diff --git a/data/ext_data/ext_hy_ths/config.json b/data/ext_data/ext_hy_ths/config.json deleted file mode 100644 index 97dabc8..0000000 --- a/data/ext_data/ext_hy_ths/config.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "id": "ext_hy_ths", - "label": "扩展行业", - "mode": "snapshot", - "fields": [ - { - "name": "symbol", - "dtype": "string", - "label": "标的代码" - }, - { - "name": "code", - "dtype": "string", - "label": "代码" - }, - { - "name": "股票代码", - "dtype": "string", - "label": "股票代码" - }, - { - "name": "股票简称", - "dtype": "string", - "label": "股票简称" - }, - { - "name": "所属同花顺行业", - "dtype": "string", - "label": "所属同花顺行业" - }, - { - "name": "thscode", - "dtype": "string", - "label": "thscode" - }, - { - "name": "time", - "dtype": "string", - "label": "time" - } - ], - "description": "", - "symbol_map": { - "type": "mapped", - "col": "股票代码" - }, - "code_map": { - "type": "computed", - "from": "symbol", - "method": "strip_exchange" - }, - "created_at": "2026-06-27T12:18:42.205794", - "updated_at": "2026-06-27T12:18:42.205794" -} \ No newline at end of file diff --git a/data/ext_data/ext_hy_ths/part.parquet b/data/ext_data/ext_hy_ths/part.parquet deleted file mode 100644 index 018630b..0000000 Binary files a/data/ext_data/ext_hy_ths/part.parquet and /dev/null differ diff --git a/packaging/tickflow.iss b/packaging/tickflow.iss index 57cdabd..9673324 100644 --- a/packaging/tickflow.iss +++ b/packaging/tickflow.iss @@ -118,21 +118,23 @@ begin end; // ── 卸载时询问是否删除用户数据 ───────────────────────────────── -// 用户数据在 %LOCALAPPDATA%\TickFlowStockPanel\ (策略/选股/回测/监控) -// 默认保留 (重装不丢), 但给用户彻底清除的选项 +// 用户数据在 {app} 同级的 TickFlowStockPanel_Data\ (策略/选股/回测/监控/行情) +// 注意: 该目录在 {app} 外, 覆盖安装和常规卸载都不会动它, 重装后数据自动恢复。 +// 这里仅在用户明确「彻底卸载」时才清理。 procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var DataDir: String; begin if CurUninstallStep = usPostUninstall then begin - DataDir := ExpandConstant('{localappdata}\TickFlowStockPanel'); + // {app}\..\TickFlowStockPanel_Data = 与安装目录同级的独立数据目录 + DataDir := ExpandConstant('{app}\..\TickFlowStockPanel_Data'); if DirExists(DataDir) then begin if SuppressibleMsgBox( '是否同时删除用户数据?' + #13#10 + #13#10 + '位置: ' + DataDir + #13#10 + - '内容: 策略、选股结果、回测记录、监控规则等' + #13#10 + #13#10 + + '内容: 行情数据、策略、选股结果、回测记录、监控规则等' + #13#10 + #13#10 + '选「是」彻底卸载, 选「否」保留数据(重装后可恢复)。', mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then begin