mirror of
https://github.com/microsoft/qlib.git
synced 2026-11-13 14:31:16 +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>
174 lines
8.1 KiB
ReStructuredText
174 lines
8.1 KiB
ReStructuredText
.. _alpha:
|
|
|
|
=========================
|
|
Building Formulaic Alphas
|
|
=========================
|
|
.. currentmodule:: qlib
|
|
|
|
Introduction
|
|
============
|
|
|
|
In quantitative trading practice, designing novel factors that can explain and predict future asset returns are of vital importance to the profitability of a strategy. Such factors are usually called alpha factors, or alphas in short.
|
|
|
|
|
|
A formulaic alpha, as the name suggests, is a kind of alpha that can be presented as a formula or a mathematical expression.
|
|
|
|
|
|
Building Formulaic Alphas in ``Qlib``
|
|
=====================================
|
|
|
|
In ``Qlib``, users can easily build formulaic alphas.
|
|
|
|
Example
|
|
-------
|
|
|
|
`MACD`, short for moving average convergence/divergence, is a formulaic alpha used in technical analysis of stock prices. It is designed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock's price.
|
|
|
|
`MACD` can be presented as the following formula:
|
|
|
|
.. math::
|
|
|
|
MACD = 2\times (DIF-DEA)
|
|
|
|
.. note::
|
|
|
|
`DIF` means Differential value, which is 12-period EMA minus 26-period EMA.
|
|
|
|
.. math::
|
|
|
|
DIF = \frac{EMA(CLOSE, 12) - EMA(CLOSE, 26)}{CLOSE}
|
|
|
|
`DEA` means a 9-period EMA of the DIF.
|
|
|
|
.. math::
|
|
|
|
DEA = EMA(DIF, 9)
|
|
|
|
Users can use ``Data Handler`` to build formulaic alphas `MACD` in qlib:
|
|
|
|
.. note:: Users need to initialize ``Qlib`` with `qlib.init` first. Please refer to `initialization <../start/initialization.html>`_.
|
|
|
|
.. code-block:: python
|
|
|
|
>> from qlib.data.dataset.loader import QlibDataLoader
|
|
>> MACD_EXP = '2 * ((EMA($close, 12) - EMA($close, 26))/$close - EMA((EMA($close, 12) - EMA($close, 26))/$close, 9))'
|
|
>> fields = [MACD_EXP] # MACD
|
|
>> names = ['MACD']
|
|
>> labels = ['Ref($close, -2)/Ref($close, -1) - 1'] # label
|
|
>> label_names = ['LABEL']
|
|
>> data_loader_config = {
|
|
.. "feature": (fields, names),
|
|
.. "label": (labels, label_names)
|
|
.. }
|
|
>> data_loader = QlibDataLoader(config=data_loader_config)
|
|
>> df = data_loader.load(instruments='csi300', start_time='2010-01-01', end_time='2017-12-31')
|
|
>> print(df)
|
|
feature label
|
|
MACD LABEL
|
|
datetime instrument
|
|
2010-01-04 SH600000 0.008781 -0.019672
|
|
SH600004 0.006699 -0.014721
|
|
SH600006 0.005714 0.002911
|
|
SH600008 0.000798 0.009818
|
|
SH600009 0.017015 -0.017758
|
|
... ... ...
|
|
2017-12-29 SZ300124 0.015071 -0.005074
|
|
SZ300136 -0.015466 0.056352
|
|
SZ300144 0.013082 0.011853
|
|
SZ300251 -0.001026 0.021739
|
|
SZ300315 -0.007559 0.012455
|
|
|
|
.. _expression_syntax:
|
|
|
|
Expression syntax and migration
|
|
===============================
|
|
|
|
Feature expressions are a restricted language, not general Python.
|
|
``Qlib`` interprets their syntax and calls registered operators rather than evaluating arbitrary Python code.
|
|
Standard Alpha158/Alpha360 feature definitions and registered custom operators remain supported.
|
|
For an upgrade checklist covering expressions, file imports, and extension
|
|
registries, see :ref:`config_migration`.
|
|
|
|
Supported expressions include:
|
|
|
|
.. code-block:: text
|
|
|
|
$close
|
|
Ref($close, 1) / $close - 1
|
|
Mean($close, 2 + 3)
|
|
$close / (1 + 0.01)
|
|
If(Gt($close, $open), $close, $open)
|
|
($close > $open) & ($volume > 0)
|
|
Ref(*[$close, 1])
|
|
Mean($close, **{"N": 2 + 3})
|
|
Ref($close, [1, 5, 10][0])
|
|
Ref(*[$close, 1, 99][:2])
|
|
Ref($close, 5 if (1 < 2 and not False) else 10)
|
|
|
|
You can use feature references (``$field`` and point-in-time ``$$field``), registered operator calls, arithmetic on expressions, and single comparisons.
|
|
Operator arguments can include literals, lists, tuples, dictionaries, and named arguments where the operator accepts them.
|
|
Numeric parameter arithmetic is supported, including ``+``, ``-``, ``*``, ``/``, ``//``, ``%``, and ``**``.
|
|
Constant arithmetic requires real numbers; integers are limited to 4096 bits and the absolute value of a constant exponent is limited to 4096.
|
|
String/list repetition (such as ``'x' * n`` or ``[1] * n``) and complex-valued constant arithmetic are rejected.
|
|
The final result must be a Qlib ``Expression`` object, not a standalone constant.
|
|
|
|
Safe parameter syntax
|
|
---------------------
|
|
|
|
* ``*`` expands literal lists or tuples into positional arguments or another list/tuple.
|
|
``**`` expands literal dictionaries into keyword arguments or another dictionary.
|
|
Arbitrary iterables and mapping objects are not accepted.
|
|
* Dictionary keys must be literal scalars (numbers, booleans, strings, bytes, or ``None``), not expression objects or tuples.
|
|
Keyword argument names must be strings, and duplicate keyword arguments are rejected.
|
|
Dictionary literals retain Python's last-value-wins behavior when keys are repeated.
|
|
* Literal lists, tuples, dictionaries, strings, and bytes support indexing.
|
|
Sequences also support slices with integer bounds and steps.
|
|
These containers can hold expressions, but expression objects themselves cannot be indexed: ``[$close][0]`` is supported, while ``$close[0]`` is not.
|
|
* Each list, tuple, or dictionary is limited to 4096 items, including items introduced by expansion.
|
|
A call is limited to 4096 positional and keyword arguments in total.
|
|
* Literal scalar ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` comparisons, including chained comparisons, are supported.
|
|
Scalar ``and``, ``or``, ``not``, and conditional expressions preserve Python's short-circuit behavior.
|
|
Their truth-value tests must be built from literal scalars, optionally using constant arithmetic or literal-container lookups; they cannot contain Qlib operator calls.
|
|
For example, ``$close if True else $open`` is supported, but ``$close if $volume > 0 else $open`` is not.
|
|
|
|
All branches are checked for supported syntax and registered operator names before any operators are constructed, even if a branch will not be selected.
|
|
String arguments are preserved literally; text such as ``"$close"`` inside a string is not rewritten into a feature reference.
|
|
Dataset and disk-cache field normalization also preserve spaces inside literals and the token boundaries needed by scalar conditions.
|
|
|
|
Expression logic and unsupported Python syntax
|
|
----------------------------------------------
|
|
|
|
For elementwise logic involving Qlib expressions, use ``&`` and ``|`` with parenthesized comparisons, or ``If``.
|
|
Write ``($close > 0) & ($close < 10)`` instead of ``0 < $close < 10``.
|
|
Use ``If($volume > 0, $close, $open)`` instead of a Python conditional whose test is an expression.
|
|
Python ``and``, ``or``, ``not``, and chained comparisons involving expression objects are rejected rather than silently changing their meaning.
|
|
|
|
Attribute access, lambdas, comprehensions, and arbitrary function calls inside expression strings remain unsupported.
|
|
Move such logic into trusted Python code or a registered custom operator.
|
|
These restrictions do not affect normal Python code used to generate expression strings:
|
|
|
|
.. code-block:: Python
|
|
|
|
fields = [f"Mean($close, {window})" for window in [5, 10, 20]]
|
|
|
|
Unsupported syntax and unknown operator names raise ``qlib.data.expression_parser.ExpressionSyntaxError``, a subclass of ``ValueError``.
|
|
Operator-specific argument validation still applies.
|
|
|
|
Register custom operators before use, for example through ``qlib.init(custom_ops=[...])``; merely making a Python function importable does not make it an expression operator.
|
|
See ``tests/test_register_ops.py`` for an example.
|
|
For file-based custom operators, put ``trusted: true`` in each operator's
|
|
configuration alongside ``class`` and ``module_path``; see the working example
|
|
in :ref:`config_migration`. This permits that file import only and never
|
|
relaxes the expression grammar.
|
|
|
|
.. note::
|
|
|
|
Custom operator code must be trusted. The expression language restricts syntax, but does not sandbox registered operators or impose general resource limits on expression evaluation.
|
|
|
|
Reference
|
|
=========
|
|
|
|
To learn more about ``Data Loader``, please refer to `Data Loader <../component/data.html#data-loader>`_
|
|
|
|
To learn more about ``Data API``, please refer to `Data API <../component/data.html>`_
|