refactor: unify qlib experiment configs, runners, and templates (#1320)

* refactor: unify qlib experiment configs, runners, and templates

* fix: use PropSetting instances instead of class attributes in qlib runners

* docs: add configurable train/valid/test time segments for fintech scenarios
This commit is contained in:
Linlang
2026-01-07 12:49:15 +08:00
committed by GitHub
parent 99a71bf533
commit 19dbeb93e9
13 changed files with 244 additions and 52 deletions
+84
View File
@@ -201,6 +201,90 @@ The execution environment is determined by the ``DS_CODER_COSTEER_ENV_TYPE`` var
DS_CODER_COSTEER_ENV_TYPE=conda
Custom Time Segment Configuration (Train / Valid / Test)
=========================================================
RD-Agent now supports user-defined time segments for training, validation,
and testing (backtesting). Users can customize these segments via environment
variables in the ``.env`` file, depending on the scenario being executed.
This feature allows greater flexibility when running experiments on different
time ranges without modifying code or YAML configurations.
Fin-Factor Scenario
-------------------
When running the **fin_factor** scenario, you can configure the time segments
using the following environment variables. These variables are read by the
Factor-related PropSettings and directly affect the execution process.
Add the following entries to your ``.env`` file as needed:
.. code-block:: properties
QLIB_FACTOR_TRAIN_START=<train start date, default is 2008-01-01>
QLIB_FACTOR_TRAIN_END=<train end date, default is 2014-12-31>
QLIB_FACTOR_VALID_START=<valid start date, default is 2015-01-01>
QLIB_FACTOR_VALID_END=<valid end date, default is 2016-12-31>
QLIB_FACTOR_TEST_START=<test / backtest start date, default is 2017-01-01>
QLIB_FACTOR_TEST_END=<test / backtest end date, default is 2020-12-31>
Fin-Model Scenario
------------------
When running the **fin_model** scenario, the model training, validation, and
testing time segments can be configured independently via the following
environment variables:
.. code-block:: properties
QLIB_MODEL_TRAIN_START=<train start date, default is 2008-01-01>
QLIB_MODEL_TRAIN_END=<train end date, default is 2014-12-31>
QLIB_MODEL_VALID_START=<valid start date, default is 2015-01-01>
QLIB_MODEL_VALID_END=<valid end date, default is 2016-12-31>
QLIB_MODEL_TEST_START=<test / backtest start date, default is 2017-01-01>
QLIB_MODEL_TEST_END=<test / backtest end date, default is 2020-12-31>
These settings are used during model training and evaluation and directly
impact the execution workflow.
Fin-Quant Scenario
------------------
When running the **fin_quant** scenario, RD-Agent supports configuring time
segments for factor, model, and quant stages simultaneously.
**Note:** The ``QLIB_QUANT_*`` variables are only used for front-end UI display
purposes and do **not** affect the actual execution process.
You may configure the following variables in your ``.env`` file:
.. code-block:: properties
QLIB_FACTOR_TRAIN_START=<train start date, default is 2008-01-01>
QLIB_FACTOR_TRAIN_END=<train end date, default is 2014-12-31>
QLIB_FACTOR_VALID_START=<valid start date, default is 2015-01-01>
QLIB_FACTOR_VALID_END=<valid end date, default is 2016-12-31>
QLIB_FACTOR_TEST_START=<test / backtest start date, default is 2017-01-01>
QLIB_FACTOR_TEST_END=<test / backtest end date, default is 2020-12-31>
QLIB_MODEL_TRAIN_START=<train start date, default is 2008-01-01>
QLIB_MODEL_TRAIN_END=<train end date, default is 2014-12-31>
QLIB_MODEL_VALID_START=<valid start date, default is 2015-01-01>
QLIB_MODEL_VALID_END=<valid end date, default is 2016-12-31>
QLIB_MODEL_TEST_START=<test / backtest start date, default is 2017-01-01>
QLIB_MODEL_TEST_END=<test / backtest end date, default is 2020-12-31>
QLIB_QUANT_TRAIN_START=<train start date, default is 2008-01-01>
QLIB_QUANT_TRAIN_END=<train end date, default is 2014-12-31>
QLIB_QUANT_VALID_START=<valid start date, default is 2015-01-01>
QLIB_QUANT_VALID_END=<valid end date, default is 2016-12-31>
QLIB_QUANT_TEST_START=<test / backtest start date, default is 2017-01-01>
QLIB_QUANT_TEST_END=<test / backtest end date, default is 2020-12-31>
This setup allows the front-end to display consistent segment information
across different stages while keeping execution logic unchanged.
Configuration(deprecated)
=========================
+54
View File
@@ -28,6 +28,24 @@ class ModelBasePropSetting(BasePropSetting):
evolving_n: int = 10
"""Number of evolutions"""
train_start: str = "2008-01-01"
"""Start date of the training segment"""
train_end: str = "2014-12-31"
"""End date of the training segment"""
valid_start: str = "2015-01-01"
"""Start date of the validation segment"""
valid_end: str = "2016-12-31"
"""End date of the validation segment"""
test_start: str = "2017-01-01"
"""Start date of the test / backtest segment"""
test_end: str = "2020-12-31"
"""End date of the test / backtest segment"""
class FactorBasePropSetting(BasePropSetting):
model_config = SettingsConfigDict(env_prefix="QLIB_FACTOR_", protected_namespaces=())
@@ -54,6 +72,24 @@ class FactorBasePropSetting(BasePropSetting):
evolving_n: int = 10
"""Number of evolutions"""
train_start: str = "2008-01-01"
"""Start date of the training segment"""
train_end: str = "2014-12-31"
"""End date of the training segment"""
valid_start: str = "2015-01-01"
"""Start date of the validation segment"""
valid_end: str = "2016-12-31"
"""End date of the validation segment"""
test_start: str = "2017-01-01"
"""Start date of the test / backtest segment"""
test_end: str = "2020-12-31"
"""End date of the test / backtest segment"""
class FactorFromReportPropSetting(FactorBasePropSetting):
# 1) override the scen attribute
@@ -113,6 +149,24 @@ class QuantBasePropSetting(BasePropSetting):
action_selection: str = "bandit"
"""Action selection strategy: 'bandit' for bandit-based selection, 'llm' for LLM-based selection, 'random' for random selection"""
train_start: str = "2008-01-01"
"""Start date of the training segment"""
train_end: str = "2014-12-31"
"""End date of the training segment"""
valid_start: str = "2015-01-01"
"""Start date of the validation segment"""
valid_end: str = "2016-12-31"
"""End date of the validation segment"""
test_start: str = "2017-01-01"
"""Start date of the test / backtest segment"""
test_end: str = "2020-12-31"
"""End date of the test / backtest segment"""
FACTOR_PROP_SETTING = FactorBasePropSetting()
FACTOR_FROM_REPORT_PROP_SETTING = FactorFromReportPropSetting()
@@ -8,6 +8,7 @@ from rdagent.core.utils import cache_with_pickle
pandarallel.initialize(verbose=1)
from rdagent.app.qlib_rd_loop.conf import FactorBasePropSetting
from rdagent.components.runner import CachedRunner
from rdagent.core.exception import FactorEmptyError
from rdagent.log import rdagent_logger as logger
@@ -80,6 +81,17 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
logger.info(f"Baseline experiment execution ...")
exp.based_experiments[-1] = self.develop(exp.based_experiments[-1])
fbps = FactorBasePropSetting()
env_to_use = {
"PYTHONPATH": "./",
"train_start": fbps.train_start,
"train_end": fbps.train_end,
"valid_start": fbps.valid_start,
"valid_end": fbps.valid_end,
"test_start": fbps.test_start,
"test_end": fbps.test_end,
}
if exp.based_experiments:
SOTA_factor = None
# Filter and retain only QlibFactorExperiment instances
@@ -136,7 +148,6 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
exp.experiment_workspace.inject_files(
**{"model.py": sota_model_exp.sub_workspace_list[0].file_dict["model.py"]}
)
env_to_use = {"PYTHONPATH": "./"}
sota_training_hyperparameters = sota_model_exp.sub_tasks[0].training_hyperparameters
if sota_training_hyperparameters:
env_to_use.update(
@@ -165,14 +176,16 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
result, stdout = exp.experiment_workspace.execute(
qlib_config_name=(
f"conf_baseline.yaml" if len(exp.based_experiments) == 0 else "conf_combined_factors.yaml"
)
),
run_env=env_to_use,
)
else:
logger.info(f"Experiment execution ...")
result, stdout = exp.experiment_workspace.execute(
qlib_config_name=(
f"conf_baseline.yaml" if len(exp.based_experiments) == 0 else "conf_combined_factors.yaml"
)
),
run_env=env_to_use,
)
if result is None:
@@ -1,5 +1,6 @@
import pandas as pd
from rdagent.app.qlib_rd_loop.conf import ModelBasePropSetting
from rdagent.components.runner import CachedRunner
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.exception import ModelEmptyError
@@ -58,7 +59,16 @@ class QlibModelRunner(CachedRunner[QlibModelExperiment]):
# to replace & inject code
exp.experiment_workspace.inject_files(**{"model.py": exp.sub_workspace_list[0].file_dict["model.py"]})
env_to_use = {"PYTHONPATH": "./"}
mbps = ModelBasePropSetting()
env_to_use = {
"PYTHONPATH": "./",
"train_start": mbps.train_start,
"train_end": mbps.train_end,
"valid_start": mbps.valid_start,
"valid_end": mbps.valid_end,
"test_start": mbps.test_start,
"test_end": mbps.test_end,
}
training_hyperparameters = exp.sub_tasks[0].training_hyperparameters
if training_hyperparameters:
@@ -1,6 +1,7 @@
from copy import deepcopy
from pathlib import Path
from rdagent.app.qlib_rd_loop.conf import FACTOR_PROP_SETTING
from rdagent.components.coder.factor_coder.config import get_factor_env
from rdagent.components.coder.factor_coder.factor import (
FactorExperiment,
@@ -36,7 +37,16 @@ class QlibFactorScenario(Scenario):
self._strategy = deepcopy(T(".prompts:qlib_factor_strategy").r())
self._simulator = deepcopy(T(".prompts:qlib_factor_simulator").r())
self._rich_style_description = deepcopy(T(".prompts:qlib_factor_rich_style_description").r())
self._experiment_setting = deepcopy(T(".prompts:qlib_factor_experiment_setting").r())
self._experiment_setting = deepcopy(
T(".prompts:qlib_factor_experiment_setting").r(
train_start=FACTOR_PROP_SETTING.train_start,
train_end=FACTOR_PROP_SETTING.train_end,
valid_start=FACTOR_PROP_SETTING.valid_start,
valid_end=FACTOR_PROP_SETTING.valid_end,
test_start=FACTOR_PROP_SETTING.test_start,
test_end=FACTOR_PROP_SETTING.test_end,
)
)
@property
def background(self) -> str:
@@ -6,10 +6,10 @@ market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2020-08-01
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
start_time: {{ train_start | default("2008-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
fit_start_time: {{ train_start | default("2008-01-01", true) }}
fit_end_time: {{ train_end | default("2014-12-31", true) }}
instruments: *market
infer_processors:
- class: FilterCol
@@ -42,8 +42,8 @@ port_analysis_config: &port_analysis_config
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
start_time: {{ test_start | default("2017-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
account: 100000000
benchmark: *benchmark
exchange_kwargs:
@@ -75,9 +75,9 @@ task:
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
train: [{{ train_start | default("2008-01-01", true) }}, {{ train_end | default("2014-12-31", true) }}]
valid: [{{ valid_start | default("2015-01-01", true) }}, {{ valid_end | default("2016-12-31", true) }}]
test: [{{ test_start | default("2017-01-01", true) }}, {{ test_end | default("2020-08-01", true) }}]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
@@ -6,8 +6,8 @@ market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2022-08-01
start_time: {{ train_start | default("2008-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
instruments: *market
data_loader:
class: NestedDataLoader
@@ -50,8 +50,8 @@ port_analysis_config: &port_analysis_config
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
start_time: {{ test_start | default("2017-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
account: 100000000
benchmark: *benchmark
exchange_kwargs:
@@ -84,9 +84,9 @@ task:
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
train: [{{ train_start | default("2008-01-01", true) }}, {{ train_end | default("2014-12-31", true) }}]
valid: [{{ valid_start | default("2015-01-01", true) }}, {{ valid_end | default("2016-12-31", true) }}]
test: [{{ test_start | default("2017-01-01", true) }}, {{ test_end | default("2020-08-01", true) }}]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
@@ -6,8 +6,8 @@ market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2022-08-01
start_time: {{ train_start | default("2008-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
instruments: *market
data_loader:
class: NestedDataLoader
@@ -40,8 +40,8 @@ data_handler_config: &data_handler_config
kwargs:
fields_group: feature
clip_outlier: true
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
fit_start_time: {{ train_start | default("2008-01-01", true) }}
fit_end_time: {{ train_end | default("2014-12-31", true) }}
- class: Fillna
kwargs:
fields_group: feature
@@ -60,8 +60,8 @@ port_analysis_config: &port_analysis_config
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
start_time: {{ test_start | default("2017-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
account: 100000000
benchmark: *benchmark
exchange_kwargs:
@@ -98,9 +98,9 @@ task:
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
train: [{{ train_start | default("2008-01-01", true) }}, {{ train_end | default("2014-12-31", true) }}]
valid: [{{ valid_start | default("2015-01-01", true) }}, {{ valid_end | default("2016-12-31", true) }}]
test: [{{ test_start | default("2017-01-01", true) }}, {{ test_end | default("2020-08-01", true) }}]
{% if step_len %}step_len: {{ step_len }}{% endif %}
record:
- class: SignalRecord
@@ -1,6 +1,7 @@
from copy import deepcopy
from pathlib import Path
from rdagent.app.qlib_rd_loop.conf import MODEL_PROP_SETTING
from rdagent.components.coder.model_coder.conf import get_model_env
from rdagent.components.coder.model_coder.model import (
ModelExperiment,
@@ -33,7 +34,16 @@ class QlibModelScenario(Scenario):
self._interface = deepcopy(T(".prompts:qlib_model_interface").r())
self._simulator = deepcopy(T(".prompts:qlib_model_simulator").r())
self._rich_style_description = deepcopy(T(".prompts:qlib_model_rich_style_description").r())
self._experiment_setting = deepcopy(T(".prompts:qlib_model_experiment_setting").r())
self._experiment_setting = deepcopy(
T(".prompts:qlib_model_experiment_setting").r(
train_start=MODEL_PROP_SETTING.train_start,
train_end=MODEL_PROP_SETTING.train_end,
valid_start=MODEL_PROP_SETTING.valid_start,
valid_end=MODEL_PROP_SETTING.valid_end,
test_start=MODEL_PROP_SETTING.test_start,
test_end=MODEL_PROP_SETTING.test_end,
)
)
@property
def background(self) -> str:
@@ -4,10 +4,10 @@ qlib_init:
market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2020-08-01
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
start_time: {{ train_start | default("2008-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
fit_start_time: {{ train_start | default("2008-01-01", true) }}
fit_end_time: {{ train_end | default("2014-12-31", true) }}
instruments: *market
infer_processors:
- class: FilterCol
@@ -40,8 +40,8 @@ port_analysis_config: &port_analysis_config
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
start_time: {{ test_start | default("2017-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
account: 100000000
benchmark: *benchmark
exchange_kwargs:
@@ -78,9 +78,9 @@ task:
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
train: [{{ train_start | default("2008-01-01", true) }}, {{ train_end | default("2014-12-31", true) }}]
valid: [{{ valid_start | default("2015-01-01", true) }}, {{ valid_end | default("2016-12-31", true) }}]
test: [{{ test_start | default("2017-01-01", true) }}, {{ test_end | default("2020-08-01", true) }}]
{% if step_len %}step_len: {{ step_len }}{% endif %}
record:
- class: SignalRecord
@@ -6,8 +6,8 @@ market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2022-08-01
start_time: {{ train_start | default("2008-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
instruments: *market
data_loader:
class: NestedDataLoader
@@ -40,8 +40,8 @@ data_handler_config: &data_handler_config
kwargs:
fields_group: feature
clip_outlier: true
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
fit_start_time: {{ train_start | default("2008-01-01", true) }}
fit_end_time: {{ train_end | default("2014-12-31", true) }}
- class: Fillna
kwargs:
fields_group: feature
@@ -60,8 +60,8 @@ port_analysis_config: &port_analysis_config
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
start_time: {{ test_start | default("2017-01-01", true) }}
end_time: {{ test_end | default("2020-08-01", true) }}
account: 100000000
benchmark: *benchmark
exchange_kwargs:
@@ -98,9 +98,9 @@ task:
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
train: [{{ train_start | default("2008-01-01", true) }}, {{ train_end | default("2014-12-31", true) }}]
valid: [{{ valid_start | default("2015-01-01", true) }}, {{ valid_end | default("2016-12-31", true) }}]
test: [{{ test_start | default("2017-01-01", true) }}, {{ test_end | default("2020-08-01", true) }}]
{% if step_len %}step_len: {{ step_len }}{% endif %}
record:
- class: SignalRecord
@@ -161,7 +161,7 @@ qlib_factor_from_report_rich_style_description : |-
qlib_factor_experiment_setting: |-
| Dataset 📊 | Model 🤖 | Factors 🌟 | Data Split 🧮 |
|---------|----------|---------------|-------------------------------------------------|
| CSI300 | LGBModel | Alpha158 Plus | Train: 2008-01-01 to 2014-12-31 <br> Valid: 2015-01-01 to 2016-12-31 <br> Test &nbsp;: 2017-01-01 to 2020-08-01 |
| CSI300 | LGBModel | Alpha158 Plus | Train: {{ train_start }} to {{ train_end }} <br> Valid: {{ valid_start }} to {{ valid_end }} <br> Test &nbsp;: {{ test_start }} to {{ test_end }} |
qlib_model_background: |-
@@ -256,4 +256,4 @@ qlib_model_rich_style_description: |-
qlib_model_experiment_setting: |-
| Dataset 📊 | Model 🤖 | Factors 🌟 | Data Split 🧮 |
|---------|----------|---------------|-------------------------------------------------|
| CSI300 | RDAgent-dev | 20 factors (Alpha158) | Train: 2008-01-01 to 2014-12-31 <br> Valid: 2015-01-01 to 2016-12-31 <br> Test &nbsp;: 2017-01-01 to 2020-08-01 |
| CSI300 | RDAgent-dev | 20 factors (Alpha158) | Train: {{ train_start }} to {{ train_end }} <br> Valid: {{ valid_start }} to {{ valid_end }} <br> Test &nbsp;: {{ test_start }} to {{ test_end }} |
@@ -1,6 +1,8 @@
from copy import deepcopy
from pathlib import Path
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING
# Factor
from rdagent.components.coder.factor_coder.config import get_factor_env
from rdagent.components.coder.factor_coder.factor import (
@@ -42,7 +44,16 @@ class QlibQuantScenario(Scenario):
self._source_data = deepcopy(get_data_folder_intro())
self._rich_style_description = deepcopy(T(".prompts:qlib_factor_rich_style_description").r())
self._experiment_setting = deepcopy(T(".prompts:qlib_factor_experiment_setting").r())
self._experiment_setting = deepcopy(
T(".prompts:qlib_factor_experiment_setting").r(
train_start=QUANT_PROP_SETTING.train_start,
train_end=QUANT_PROP_SETTING.train_end,
valid_start=QUANT_PROP_SETTING.valid_start,
valid_end=QUANT_PROP_SETTING.valid_end,
test_start=QUANT_PROP_SETTING.test_start,
test_end=QUANT_PROP_SETTING.test_end,
)
)
def background(self, tag=None) -> str:
assert tag in [None, "factor", "model"]