mirror of
https://github.com/microsoft/qlib.git
synced 2026-11-12 22:11:07 +08:00
* security: constrain config-driven code execution * fix: address security test and lint failures * ci: restore dependency and title lint compatibility * ci: handle current MLflow and Windows OSQP changes * ci: use lxml release with cross-platform wheels * fix: defer optional CVXPY solver imports * style: document intentional lazy import * ci: skip broken released OSQP on Windows Python 3.8 * test: make MLflow client timing check robust * ci: retry nondeterministic benchmark smoke test * fix: cap filelock before fork safety regression * fix: use filelock release compatible with multiprocessing * fix(security): restrict operator lookup and validate trusted roots * fix(security): preserve numeric expressions and legacy model imports * fix: preserve TRA initialization and document config migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve safe expression syntax and cache semantics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use explicit scoped trust for file modules Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: satisfy CI lint without weakening trust checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: handle escaped file paths in module refusal errors Match the repr-formatted module path in refusal messages and cover Windows, UNC, quoted, and newline-containing paths on every platform. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve expression parser error logging Log ExpressionSyntaxError with its field and traceback before re-raising, while preserving custom operator NameError and SyntaxError handling. Add five regression cases covering logging, exception identity, and failed cache entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: isolate expression logger capture Capture parser logs directly on qlib.data without propagating to parent and root capture handlers. Restore logger state with monkeypatch and retain exact single-record, traceback, and exception-identity assertions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(security): preserve malformed feature boundaries and reject unsupported literals * test(security): cover malformed dollar-prefixed expressions --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Yang Liu <DG-git-dev@users.noreply.github.com>
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
|
|
import pandas as pd
|
|
|
|
from qlib.data import D
|
|
from qlib.data.ops import Ref
|
|
from qlib.tests import TestAutoData
|
|
|
|
|
|
class MetadataRef(Ref):
|
|
def __init__(self, feature, N=1, metadata=None):
|
|
super().__init__(feature, N)
|
|
assert metadata == {"label": "close price", "literal": "$close", "window": (1, None)}
|
|
|
|
|
|
class TestExpressionCompatibility(TestAutoData):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls._setup_kwargs = dict(cls._setup_kwargs, kernels=1, custom_ops=[MetadataRef])
|
|
super().setUpClass()
|
|
|
|
def test_real_data_queries_preserve_values(self):
|
|
cases = [
|
|
("Ref(*[$close, 1])", "Ref($close, 1)"),
|
|
("Ref(*($close, 1))", "Ref($close, 1)"),
|
|
("Ref(*[$close, *[1]])", "Ref($close, 1)"),
|
|
("Ref($close, **{'N': 1})", "Ref($close, 1)"),
|
|
("Ref(**{'feature': $close, 'N': 1})", "Ref($close, 1)"),
|
|
("Ref($close, **{'N': 2, **{'N': 1}})", "Ref($close, 1)"),
|
|
("Ref($close, [1, 5][0])", "Ref($close, 1)"),
|
|
("Ref($close, {'window': 1}['window'])", "Ref($close, 1)"),
|
|
("Ref(*[$close, 1, 99][:2])", "Ref($close, 1)"),
|
|
("Ref(*[1, $close][::-1])", "Ref($close, 1)"),
|
|
("Ref($close, 1 if (2 > 1 and not False) else 2)", "Ref($close, 1)"),
|
|
("Ref($close, 0 or (1))", "Ref($close, 1)"),
|
|
("Ref($close, 1 if 0 < 1 < 2 else 2)", "Ref($close, 1)"),
|
|
("Ref($close, 1 if True else 1 / 0)", "Ref($close, 1)"),
|
|
("Mean($close, **{'N': 2 + 3})", "Mean($close, 5)"),
|
|
("$close if True else $open", "$close"),
|
|
("If($close > $open, Ref($close, 1 if True else 2), $open)", "If($close > $open, Ref($close, 1), $open)"),
|
|
("Feature('close'[::1])", "$close"),
|
|
(
|
|
"MetadataRef(*[$close], **{'N': 1, 'metadata': "
|
|
"{'label': 'close price', 'literal': '$close', 'window': (1, None)}})",
|
|
"Ref($close, 1)",
|
|
),
|
|
]
|
|
instruments = ["SH600000", "SH600519", "SZ000001"]
|
|
actual = D.features(instruments, [source for source, _ in cases], "2018-01-02", "2018-01-31")
|
|
expected = D.features(instruments, [plain for _, plain in cases], "2018-01-02", "2018-01-31")
|
|
self.assertGreater(len(actual), 0)
|
|
self.assertTrue(actual.notna().any().all())
|
|
actual.columns = expected.columns = range(len(cases))
|
|
pd.testing.assert_frame_equal(actual, expected, check_exact=True)
|