Skip to content

Commit a09e8d4

Browse files
author
Tonny@Home
committed
Refactor configuration management with unified loader and update all related scripts, tests, and documentation.
1 parent 7ea3886 commit a09e8d4

22 files changed

+264
-234
lines changed

docs/00_SYSTEM_OVERVIEW.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,9 @@ python quantpits/scripts/ensemble_fusion.py \
345345
| 文件 | 用途 | 相关模块 |
346346
|------|------|----------|
347347
| `model_registry.yaml` | 模型注册表:定义模型名、算法、数据集、标签 | 训练、预测 |
348-
| `model_config.json` | 日期参数:训练窗口、滑动/固定模式 | 训练、预测 |
348+
| `model_config.json` |环境与日期参数:训练窗口、滑动/固定模式、市场(market)、基准(benchmark) | 训练、预测、整体环境 |
349349
| `strategy_config.yaml` | 策略参数:订单生成规则 (TopK等),回测环境配置 | 订单生成、回测组合 |
350-
| `prod_config.json` | 实盘状态:持仓、现金、市场(market)、基准(benchmark) | Post-Trade、订单生成 |
350+
| `prod_config.json` | 实盘与状态层:持仓、现金、处理时间 | Post-Trade、订单生成 |
351351
| `cashflow.json` | 出入金记录:按日期的出入金 | Post-Trade |
352352
| `ensemble_config.json` | 多组合融合配置:combo 定义、权重、default | 融合预测、订单生成、信号排名 |
353353
| `workflow_config_*.yaml` | Qlib 工作流:各模型的训练配置 | 训练 |

docs/06_ORDER_GEN_GUIDE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ python quantpits/scripts/order_gen.py --verbose
3636

3737
## 配置要求
3838

39-
脚本运行需要 `config/prod_config.json` 中的以下关键字段
39+
脚本运行会通过 `config_loader` 统一合并工作区配置(包含 `prod_config.json`, `model_config.json`, `strategy_config.yaml`)。以下是影响脚本的核心逻辑字段
4040

41-
- **market**: (推荐) 指定市场(如 `csi300`, `csi1000`)。若缺失,脚本会尝试从预测标的中自动推断,但建议显式指定。
42-
- **benchmark**: (推荐) 指定基准指数代码(如 `SH000300`, `SH000852`)。
43-
- **current_cash**: 当前可用现金余额。
44-
- **current_holding**: 当前持仓列表 `[{"instrument": "...", "amount": "...", "value": "..."}, ...]`
41+
- **market**: (推荐) 交易市场界定(如 `csi300`, `csi1000`)。通常在 `model_config.json` 定义。若缺失,脚本会从预测文件中自动推断,但建议显式指定。
42+
- **benchmark**: (推荐) 交易基准(如 `SH000300`, `SH000852`)。通常在 `model_config.json` 定义。
43+
- **current_cash**: 当前可用现金余额。由 Post-Trade 脚本自动更新维护在 `prod_config.json` 中。
44+
- **current_holding**: 存量持仓状态 `[{"instrument": "...", "amount": "...", "value": "..."}, ...]`。由 Post-Trade 脚本自动更新维护在 `prod_config.json` 中。
45+
- **topk** / **n_drop** / **buy_suggestion_factor**: 订单生成策略参数。定义于 `strategy_config.yaml` 或兼容旧版由根配置管理。
4546

4647
> [!NOTE]
4748
> 脚本具备**自动容错**能力:即使 `market` 配置错误,也会根据预测文件中的标的列表自动获取对应的价格数据。

docs/en/00_SYSTEM_OVERVIEW.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,9 @@ latest_train_records.json prod_config.json (Update Pos/Cash)
343343
| File | Purpose | Related Module |
344344
|------|------|----------|
345345
| `model_registry.yaml` | Model Registry: Defines model names, algorithms, datasets, labels | Train, Predict |
346-
| `model_config.json` | Date Params: Training windows, sliding/fixed mode | Train, Predict |
346+
| `model_config.json` | Environment Params: Training windows, slicing mode, market, benchmark | Train, Predict, Environment |
347347
| `strategy_config.yaml` | Strategy Params: Order generation boundaries (TopK, etc.), Backtest environment config | Order Gen, Backtesting |
348-
| `prod_config.json` | Live State: Holdings, Cash, Market, Benchmark | Post-Trade, Order Gen |
348+
| `prod_config.json` | Live State: Holdings, Cash, Processing state | Post-Trade, Order Gen |
349349
| `cashflow.json` | Cashflow Records: Deposits/Withdrawals by date | Post-Trade |
350350
| `ensemble_config.json` | Multi-combo Config: Combo definitions, weights, defaults | Fusion, Order Gen, Ranking |
351351
| `workflow_config_*.yaml` | Qlib Workflows: Training configurations for each model | Train |

docs/en/06_ORDER_GEN_GUIDE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ python quantpits/scripts/order_gen.py --verbose
3636

3737
## Configuration Requirements
3838

39-
The script relies on the following key fields in `config/prod_config.json`:
39+
The script loads unified workspace parameters implicitly leveraging `config_loader` (which aggregates `prod_config.json`, `model_config.json`, and `strategy_config.yaml`). Below are the vital operative boundaries steering execution logic:
4040

41-
- **market**: (Recommended) Specifies the target universe (e.g., `csi300`, `csi1000`). If omitted, the script infers it from prediction data, but explicit definition is preferred.
42-
- **benchmark**: (Recommended) Reference index code (e.g., `SH000300`, `SH000852`).
43-
- **current_cash**: Current liquid balance for trade allocation.
44-
- **current_holding**: Array of active positions `[{"instrument": "...", "amount": "...", "value": "..."}, ...]`.
41+
- **market**: (Recommended) Target operational universe (e.g., `csi300`, `csi1000`). Customarily bounded centrally inside `model_config.json`. If omitted, the architecture auto-infers data scopes utilizing target prediction limits.
42+
- **benchmark**: (Recommended) Index metric boundaries (e.g., `SH000300`, `SH000852`). Built into `model_config.json`.
43+
- **current_cash**: Current liquid balance limits defining order sizes. Systematically maintained via Post-Trade executing upon `prod_config.json`.
44+
- **current_holding**: Active held component quantities arrays. Systematically maintained via Post-Trade executing upon `prod_config.json`.
45+
- **topk** / **n_drop** / **buy_suggestion_factor**: Operation positioning strategy boundaries natively registered executing inside `strategy_config.yaml`.
4546

4647
> [!NOTE]
4748
> The engine features **automatic resilience**: even if the `market` configuration is mismatched, it will dynamically fetch pricing for all assets identified in the prediction source.

quantpits/scripts/analysis/utils.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,18 @@
2525

2626
def load_market_config():
2727
"""
28-
从 model_config.json 读取 market 和 benchmark 配置。
29-
返回 (market, benchmark) 元组。
28+
使用 config_loader 加载统一配置并返回 market 和 benchmark。
3029
"""
31-
market = DEFAULT_MARKET
32-
benchmark = DEFAULT_BENCHMARK
33-
if os.path.exists(MODEL_CONFIG_FILE):
34-
try:
35-
with open(MODEL_CONFIG_FILE, 'r') as f:
36-
cfg = json.load(f)
37-
market = cfg.get("market", DEFAULT_MARKET)
38-
benchmark = cfg.get("benchmark", DEFAULT_BENCHMARK)
39-
except (json.JSONDecodeError, IOError):
40-
pass
41-
return market, benchmark
30+
from config_loader import load_workspace_config
31+
try:
32+
config = load_workspace_config(ROOT_DIR)
33+
34+
market = config.get("market", DEFAULT_MARKET)
35+
benchmark = config.get("benchmark", DEFAULT_BENCHMARK)
36+
return market, benchmark
37+
except Exception as e:
38+
print(f"Warning: Failed to load market config: {e}")
39+
return DEFAULT_MARKET, DEFAULT_BENCHMARK
4240

4341

4442
def init_qlib():

quantpits/scripts/brute_force_ensemble.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,15 @@ def init_qlib():
113113

114114

115115
def load_config(record_file="latest_train_records.json"):
116-
"""加载训练记录和模型配置"""
117-
with open(record_file, "r") as f:
118-
train_records = json.load(f)
116+
"""使用 config_loader 加载统一配置"""
117+
from config_loader import load_workspace_config
118+
if os.path.exists(record_file):
119+
with open(record_file, "r") as f:
120+
train_records = json.load(f)
121+
else:
122+
train_records = {"models": {}, "experiment_name": "unknown"}
119123

120-
with open("config/model_config.json", "r") as f:
121-
model_config = json.load(f)
124+
model_config = load_workspace_config(ROOT_DIR)
122125

123126
return train_records, model_config
124127

quantpits/scripts/brute_force_fast.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,15 @@ def init_qlib():
132132

133133

134134
def load_config(record_file="latest_train_records.json"):
135-
"""加载训练记录和模型配置"""
136-
with open(record_file, "r") as f:
137-
train_records = json.load(f)
135+
"""使用 config_loader 加载统一配置"""
136+
from config_loader import load_workspace_config
137+
if os.path.exists(record_file):
138+
with open(record_file, "r") as f:
139+
train_records = json.load(f)
140+
else:
141+
train_records = {"models": {}, "experiment_name": "unknown"}
138142

139-
with open("config/model_config.json", "r") as f:
140-
model_config = json.load(f)
143+
model_config = load_workspace_config(ROOT_DIR)
141144

142145
return train_records, model_config
143146

quantpits/scripts/config_loader.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import os
2+
import json
3+
import yaml
4+
from pathlib import Path
5+
6+
def load_workspace_config(workspace_path):
7+
"""
8+
Unified configuration loader for QuantPits workspaces.
9+
Merges model_config.json, prod_config.json, and strategy_config.yaml.
10+
11+
Returns a unified dict with all necessary parameters.
12+
"""
13+
workspace_path = Path(workspace_path)
14+
config_dir = workspace_path / "config"
15+
16+
# Files
17+
model_cfg_path = config_dir / "model_config.json"
18+
prod_cfg_path = config_dir / "prod_config.json"
19+
strat_cfg_path = config_dir / "strategy_config.yaml"
20+
21+
config = {}
22+
23+
# 1. Load Model Config (Base environment properties)
24+
if model_cfg_path.exists():
25+
with open(model_cfg_path, 'r') as f:
26+
config.update(json.load(f))
27+
28+
# 2. Load Strategy Config (Single Source of Truth for strategy params)
29+
if strat_cfg_path.exists():
30+
with open(strat_cfg_path, 'r') as f:
31+
strat_data = yaml.safe_load(f)
32+
if strat_data:
33+
config['strategy'] = strat_data.get('strategy', {})
34+
config['backtest'] = strat_data.get('backtest', {})
35+
36+
# Promote core strategy params to top-level for convenience/compatibility
37+
strat_params = config['strategy'].get('params', {})
38+
config['topk'] = strat_params.get('topk', config.get('TopK'))
39+
config['n_drop'] = strat_params.get('n_drop', config.get('DropN'))
40+
config['buy_suggestion_factor'] = strat_params.get('buy_suggestion_factor', config.get('buy_suggestion_factor'))
41+
42+
# Compatibility mapping (Upper case versions if they don't exist)
43+
if 'TopK' not in config: config['TopK'] = config['topk']
44+
if 'DropN' not in config: config['DropN'] = config['n_drop']
45+
46+
# 3. Load Prod Config (Current state - handles cash/holding)
47+
if prod_cfg_path.exists():
48+
with open(prod_cfg_path, 'r') as f:
49+
prod_data = json.load(f)
50+
# We only want State fields from prod_config, others should come from model/strategy
51+
state_fields = [
52+
'current_date', 'last_processed_date', 'initial_cash',
53+
'current_full_cash', 'initial_holding', 'current_cash',
54+
'current_holding', 'model', 'experiment_name',
55+
'current_train_record_id', 'current_pred_record_id'
56+
]
57+
for field in state_fields:
58+
if field in prod_data:
59+
config[field] = prod_data[field]
60+
61+
# Sanity checks / Cross-file consistency (Optional but recommended)
62+
# If market/benchmark exist in both, we prefer model_config but can log warnings if they mismatch
63+
64+
return config
65+
66+
if __name__ == "__main__":
67+
# Test loading
68+
import sys
69+
if len(sys.argv) > 1:
70+
c = load_workspace_config(sys.argv[1])
71+
print(json.dumps(c, indent=2))

quantpits/scripts/ensemble_fusion.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,16 @@ def init_qlib():
8484

8585

8686
def load_config(record_file="latest_train_records.json"):
87-
"""加载训练记录、模型配置和 ensemble 配置"""
88-
with open(record_file, "r") as f:
89-
train_records = json.load(f)
87+
"""使用 config_loader 加载统一配置"""
88+
from config_loader import load_workspace_config
89+
if os.path.exists(record_file):
90+
with open(record_file, "r") as f:
91+
train_records = json.load(f)
92+
else:
93+
train_records = {"models": {}, "experiment_name": "unknown"}
9094

91-
with open("config/model_config.json", "r") as f:
92-
model_config = json.load(f)
95+
# 使用 config_loader 加载工作区配置 (取代直接读取 model_config.json)
96+
model_config = load_workspace_config(ROOT_DIR)
9397

9498
ensemble_config = {}
9599
if os.path.exists("config/ensemble_config.json"):
@@ -331,7 +335,7 @@ def calculate_weights(norm_df, model_metrics, method, model_config,
331335
"""
332336
model_names = list(norm_df.columns)
333337

334-
top_k = model_config.get('TopK', 22)
338+
top_k = model_config.get('topk', model_config.get('TopK', 20))
335339
min_ic = ensemble_config.get('min_model_ic', 0.01)
336340

337341
print(f"\n{'='*60}")

quantpits/scripts/order_gen.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ def get_anchor_date():
7070

7171

7272
def load_configs():
73-
"""加载 prod_config.json 和 cashflow.json"""
74-
with open(CONFIG_FILE, 'r') as f:
75-
config = json.load(f)
73+
"""使用 config_loader 加载统一配置并读取 cashflow.json"""
74+
from config_loader import load_workspace_config
75+
config = load_workspace_config(ROOT_DIR)
7676

7777
cashflow_config = {}
7878
if os.path.exists(CASHFLOW_FILE):
@@ -626,18 +626,14 @@ def main():
626626
strategy_config = strategy.load_strategy_config()
627627
order_gen = strategy.create_order_generator(strategy_config)
628628

629-
# 策略参数(由 Strategy Provider 统一管理)
629+
# 策略参数(由 Strategy Provider 统一管理,底层已切到 config_loader
630630
st_params = strategy.get_strategy_params(strategy_config)
631631
top_k = st_params.get('topk', 20)
632632
drop_n = st_params.get('n_drop', 3)
633633
buy_suggestion_factor = st_params.get('buy_suggestion_factor', 2)
634634

635-
# 运行时状态变量(来自 prod_config)
636-
market = config.get('market')
637-
if not market:
638-
market = 'csi300'
639-
print(f"⚠️ Warning: 'market' not found in prod_config.json. Defaulting to '{market}'.")
640-
635+
# 运行时状态变量(来自 config_loader 整合后的 config)
636+
market = config.get('market', 'csi300')
641637
current_cash = float(config.get('current_cash', 0))
642638
current_holding = config.get('current_holding', [])
643639
cash_flow_today = get_cashflow_today(cashflow_config, anchor_date)

0 commit comments

Comments
 (0)