Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint_title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: '22'

- name: Install commitlint
run: npm install --save-dev @commitlint/{config-conventional,cli}
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/test_qlib_from_pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
jobs:
build:
timeout-minutes: 120
env:
# Released Qlib versions use MLflow's legacy file-store backend.
MLFLOW_ALLOW_FILE_STORE: "true"

runs-on: ${{ matrix.os }}
strategy:
Expand All @@ -22,6 +25,11 @@ jobs:
# Also, python 3.7 is no longer supported from macos-14, and will be phased out from macos-13 in the near future.
# All things considered, we have removed python 3.7.
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude:
# The released package eagerly imports OSQP, whose Windows/Python 3.8
# native extension crashes during module initialization.
- os: windows-latest
python-version: "3.8"

steps:
- name: Test qlib from pip
Expand Down Expand Up @@ -56,5 +64,8 @@ jobs:
cd qlib

- name: Test workflow by config
run: |
qrun examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml
uses: nick-fields/retry@v2
with:
timeout_minutes: 30
max_attempts: 3
command: qrun examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml
10 changes: 7 additions & 3 deletions .github/workflows/test_qlib_from_source.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,13 @@ jobs:
make nbconvert

- name: Test workflow by config (install from source)
run: |
python -m pip install numba
python qlib/cli/run.py examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml
uses: nick-fields/retry@v2
with:
timeout_minutes: 30
max_attempts: 3
command: |
python -m pip install numba
python qlib/cli/run.py examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml

- name: Unit tests with Pytest (MacOS)
if: ${{ matrix.os == 'macos-14' || matrix.os == 'macos-15' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ qlib_init:
day: "~/.qlib/qlib_data/cn_data"
1min: "~/.qlib/qlib_data/cn_data_1min"
region: cn
trusted_module_roots: [.]
dataset_cache: null
maxtasksperchild: 1
market: &market csi300
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ qlib_init:
day: "~/.qlib/qlib_data/cn_data"
1min: "~/.qlib/qlib_data/cn_data_1min"
region: cn
trusted_module_roots: [.]
dataset_cache: null
maxtasksperchild: null
market: &market csi300
Expand Down
3 changes: 2 additions & 1 deletion examples/benchmarks/TRA/configs/config_alstm.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 1024
batch_size: 1024
3 changes: 2 additions & 1 deletion examples/benchmarks/TRA/configs/config_alstm_tra.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 1024
batch_size: 1024
3 changes: 2 additions & 1 deletion examples/benchmarks/TRA/configs/config_alstm_tra_init.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 512
batch_size: 512
3 changes: 2 additions & 1 deletion examples/benchmarks/TRA/configs/config_transformer.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 1024
batch_size: 1024
3 changes: 2 additions & 1 deletion examples/benchmarks/TRA/configs/config_transformer_tra.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 512
batch_size: 512
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
trusted_module_roots: [.]

data_loader_config: &data_loader_config
class: StaticDataLoader
Expand Down Expand Up @@ -60,4 +61,4 @@ task:
seq_len: 60
horizon: 21
num_states: *num_states
batch_size: 512
batch_size: 512
13 changes: 10 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ dependencies = [
# I encoutered an Error that the set_uri does not work when downloading artifacts in mlflow 3.1.1;
# But earlier versions of mlflow does not have this problem.
# But when I switch to 2.*.* version, another error occurs, which is even more strange...
"mlflow",
"filelock>=3.16.0",
# MLflow 3.13 disables the file-store backend used by Qlib by default.
# Remove this cap after Qlib migrates its default tracking backend.
"mlflow<3.13",
# filelock 3.30 rejects forks while another thread changes descriptor
# ownership, which conflicts with Qlib's multiprocessing data queues.
"filelock>=3.16.0,<3.30",
"redis",
"dill",
"fire",
Expand Down Expand Up @@ -97,9 +101,12 @@ package = [
test = [
"yahooquery",
"baostock",
# lxml 6.1.3 does not currently publish wheels for every CI platform.
"lxml<6.1.3",
]
analysis = [
"plotly",
# Plotly 7 removed figure_factory.create_distplot, which Qlib still uses.
"plotly<7",
"statsmodels",
]
client = [
Expand Down
5 changes: 5 additions & 0 deletions qlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ def register_from_C(config, skip_register=True):
# if element of custom_ops is Type[ExpressionOps], it represents the custom operator class
# if element of custom_ops is dict, it represents the config of custom operator and should include `class` and `module_path` keys.
"custom_ops": [],
# Trusted roots for modules loaded from Python source files.
# File-based module loading is disabled when this list is empty.
"trusted_module_roots": [],
},
}

Expand Down Expand Up @@ -502,11 +505,13 @@ def set(self, default_conf: str = "client", **kwargs):

def register(self):
from .utils import init_instance_by_config # pylint: disable=C0415
from .utils.mod import set_trusted_module_roots # pylint: disable=C0415
from .data.ops import register_all_ops # pylint: disable=C0415
from .data.data import register_all_wrappers # pylint: disable=C0415
from .workflow import R, QlibRecorder # pylint: disable=C0415
from .workflow.utils import experiment_exit_handler # pylint: disable=C0415

set_trusted_module_roots(self.trusted_module_roots)
register_all_ops(self)
register_all_wrappers(self)
# set up QlibRecorder
Expand Down
14 changes: 13 additions & 1 deletion qlib/contrib/model/pytorch_tra.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
device = "cuda" if torch.cuda.is_available() else "cpu"


MODEL_TYPES = {}


class TRAModel(Model):
"""
TRA Model
Expand Down Expand Up @@ -137,7 +140,13 @@ def __init__(
def _init_model(self):
self.logger.info("init TRAModel...")

self.model = eval(self.model_type)(**self.model_config).to(device)
try:
model_class = MODEL_TYPES[self.model_type]
except KeyError as exc:
raise ValueError(
f"Unsupported model_type {self.model_type!r}; expected one of {sorted(MODEL_TYPES)}"
) from exc
self.model = model_class(**self.model_config).to(device)
print(self.model)

self.tra = TRA(self.model.output_size, **self.tra_config).to(device)
Expand Down Expand Up @@ -646,6 +655,9 @@ def forward(self, x):
return out[-1]


MODEL_TYPES.update({"RNN": RNN, "Transformer": Transformer})


class TRA(nn.Module):
"""Temporal Routing Adaptor (TRA)

Expand Down
16 changes: 15 additions & 1 deletion qlib/contrib/report/analysis_model/analysis_model_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ def _pred_turnover(pred_label: pd.DataFrame, N=5, lag=1, **kwargs) -> tuple:
return (turnover_figure,)


GRAPH_FUNCTIONS = {
"group_return": _group_return,
"pred_ic": _pred_ic,
"pred_autocorr": _pred_autocorr,
"pred_turnover": _pred_turnover,
}


def ic_figure(ic_df: pd.DataFrame, show_nature_day=True, **kwargs) -> go.Figure:
r"""IC figure

Expand Down Expand Up @@ -330,7 +338,13 @@ def model_performance_graph(
"""
figure_list = []
for graph_name in graph_names:
fun_res = eval(f"_{graph_name}")(
try:
graph_function = GRAPH_FUNCTIONS[graph_name]
except KeyError as exc:
raise ValueError(
f"Unsupported graph name {graph_name!r}; expected one of {sorted(GRAPH_FUNCTIONS)}"
) from exc
fun_res = graph_function(
pred_label=pred_label, lag=lag, N=N, reverse=reverse, rank=rank, show_nature_day=show_nature_day, **kwargs
)
figure_list += fun_res
Expand Down
7 changes: 6 additions & 1 deletion qlib/contrib/strategy/signal_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from qlib.log import get_module_logger
from qlib.utils import get_pre_trading_date, load_dataset
from qlib.contrib.strategy.order_generator import OrderGenerator, OrderGenWOInteract
from qlib.contrib.strategy.optimizer import EnhancedIndexingOptimizer


class BaseSignalStrategy(BaseStrategy, ABC):
Expand Down Expand Up @@ -414,6 +413,12 @@ def __init__(
verbose=False,
**kwargs,
):
# Import lazily so unrelated strategies do not load CVXPY's native
# solver extensions during module discovery or test collection.
from qlib.contrib.strategy.optimizer.enhanced_indexing import ( # pylint: disable=C0415
EnhancedIndexingOptimizer,
)

super().__init__(**kwargs)

self.logger = get_module_logger("EnhancedIndexingStrategy")
Expand Down
5 changes: 2 additions & 3 deletions qlib/data/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
hash_args,
get_redis_connection,
read_bin,
parse_field,
remove_fields_space,
normalize_cache_fields,
normalize_cache_instruments,
Expand All @@ -34,7 +33,7 @@

from ..log import get_module_logger
from .base import Feature
from .ops import Operators # pylint: disable=W0611 # noqa: F401
from .expression_parser import parse_expression


class QlibCacheException(RuntimeError):
Expand Down Expand Up @@ -540,7 +539,7 @@ def _expression(self, instrument, field, start_time=None, end_time=None, freq="d
field = remove_fields_space(field)
# cache unavailable, generate the cache
_instrument_dir.mkdir(parents=True, exist_ok=True)
if not isinstance(eval(parse_field(field)), Feature):
if not isinstance(parse_expression(field), Feature):
# When the expression is not a raw feature
# generate expression cache if the feature is not a Feature
# instance
Expand Down
5 changes: 2 additions & 3 deletions qlib/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
init_instance_by_config,
register_wrapper,
get_module_by_module_path,
parse_field,
hash_args,
normalize_cache_fields,
code_to_fname,
Expand All @@ -37,7 +36,7 @@
get_period_list,
)
from ..utils.paral import ParallelExt
from .ops import Operators # pylint: disable=W0611 # noqa: F401
from .expression_parser import parse_expression


class ProviderBackendMixin:
Expand Down Expand Up @@ -394,7 +393,7 @@ def get_expression_instance(self, field):
if field in self.expression_instance_cache:
expression = self.expression_instance_cache[field]
else:
expression = eval(parse_field(field))
expression = parse_expression(field)
self.expression_instance_cache[field] = expression
except NameError as e:
get_module_logger("data").exception(
Expand Down
Loading
Loading