Repository: wukan1986/polars_ta Branch: main Commit: f3097d5ac93d Files: 150 Total size: 392.1 KB Directory structure: gitextract_uz7e5vob/ ├── .github/ │ └── workflows/ │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── compare.md ├── deprecated/ │ ├── demo_ta1.py │ ├── demo_ta2.py │ ├── helper.py │ ├── pandas_.py │ └── wrapper.py ├── docs/ │ ├── index.md │ ├── ta/ │ │ ├── index.md │ │ ├── momentum.md │ │ ├── operators.md │ │ ├── overlap.md │ │ ├── price.md │ │ ├── statistic.md │ │ ├── transform.md │ │ ├── volatility.md │ │ └── volume.md │ ├── talib/ │ │ └── index.md │ ├── tdx/ │ │ ├── arithmetic.md │ │ ├── choice.md │ │ ├── energy.md │ │ ├── logical.md │ │ ├── moving_average.md │ │ ├── over_bought_over_sold.md │ │ ├── pattern.md │ │ ├── pattern_feature.md │ │ ├── pressure_support.md │ │ ├── reference.md │ │ ├── statistic.md │ │ ├── times.md │ │ ├── trend.md │ │ ├── trend_feature.md │ │ └── volume.md │ └── wq/ │ ├── arithmetic.md │ ├── cross_sectional.md │ ├── half_life.md │ ├── logical.md │ ├── preprocess.md │ ├── time_series.md │ ├── transformational.md │ └── vector.md ├── examples/ │ ├── alpha101.py │ └── demo_ta3.py ├── mkdocs.yml ├── nan_to_null.md ├── point_in_time.md ├── polars_ta/ │ ├── __init__.py │ ├── _version.py │ ├── candles/ │ │ ├── __init__.py │ │ ├── cdl1.py │ │ ├── cdl1_limit.py │ │ └── cdl2.py │ ├── labels/ │ │ ├── __init__.py │ │ ├── _nb.py │ │ └── future.py │ ├── noise.py │ ├── performance/ │ │ ├── __init__.py │ │ ├── drawdown.py │ │ └── returns.py │ ├── prefix/ │ │ ├── __init__.py │ │ ├── cdl.py │ │ ├── labels.py │ │ ├── reports.py │ │ ├── ta.py │ │ ├── talib.py │ │ ├── tdx.py │ │ ├── vec.py │ │ └── wq.py │ ├── reports/ │ │ ├── __init__.py │ │ └── cicc.py │ ├── ta/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── momentum.py │ │ ├── operators.py │ │ ├── overlap.py │ │ ├── price.py │ │ ├── statistic.py │ │ ├── transform.py │ │ ├── volatility.py │ │ └── volume.py │ ├── talib/ │ │ ├── README.md │ │ └── __init__.py │ ├── tdx/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── _chip.py │ │ ├── _nb.py │ │ ├── _slow.py │ │ ├── arithmetic.py │ │ ├── choice.py │ │ ├── energy.py │ │ ├── logical.py │ │ ├── moving_average.py │ │ ├── over_bought_over_sold.py │ │ ├── pattern.py │ │ ├── pattern_feature.py │ │ ├── pressure_support.py │ │ ├── reference.py │ │ ├── statistic.py │ │ ├── times.py │ │ ├── trend.py │ │ ├── trend_feature.py │ │ └── volume.py │ ├── utils/ │ │ ├── __init__.py │ │ ├── factor.py │ │ ├── functions.py │ │ ├── numba_.py │ │ ├── pit.py │ │ └── withs.py │ └── wq/ │ ├── __init__.py │ ├── _nb.py │ ├── _slow.py │ ├── arithmetic.py │ ├── cross_sectional.py │ ├── half_life.py │ ├── logical.py │ ├── preprocess.py │ ├── time_series.py │ ├── transformational.py │ └── vector.py ├── prompt.txt ├── pyproject.toml ├── requirements-docs.txt ├── requirements.txt ├── setup.py ├── tests/ │ ├── numba_test.py │ ├── pit_test.py │ ├── ta/ │ │ ├── test_momentum.py │ │ ├── test_operators.py │ │ ├── test_overlap.py │ │ ├── test_statistic.py │ │ ├── test_volatility.py │ │ └── test_volume.py │ ├── tdx/ │ │ ├── chip_test.py │ │ ├── test_reference.py │ │ └── test_statistic.py │ └── wq/ │ ├── test_arithmetic.py │ └── test_time_series.py ├── thinking_about_TA.md └── tools/ ├── README.md ├── codegen_talib.py ├── prefix.py ├── prefix_ta.py ├── prefix_talib.py ├── prefix_tdx.py ├── prefix_vec.py └── prompt.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/python-publish.yml ================================================ # This workflow will upload a Python Package using Twine when a release is created # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Upload Python Package on: release: types: [published] permissions: contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install build - name: Build package run: python -m build - name: Publish package uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ================================================ FILE: .readthedocs.yaml ================================================ # Read the Docs configuration file for MkDocs projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.11" mkdocs: configuration: mkdocs.yml # Optionally declare the Python requirements required to build your docs python: install: - requirements: requirements-docs.txt ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 wukan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # polars_ta Technical Indicator Operators Rewritten in `polars`. We provide wrappers for some functions (like `TA-Lib`) that are not `pl.Expr` alike. ## How to Install ### Using `pip` ```commandline pip install -i https://pypi.org/simple --upgrade polars_ta pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade polars_ta # Mirror in China ``` ### Build from Source ```commandline git clone --depth=1 https://github.com/wukan1986/polars_ta.git cd polars_ta python -m build cd dist pip install polars_ta-0.1.2-py3-none-any.whl ``` ### How to Install TA-Lib Non-official `TA-Lib` wheels can be downloaded from `https://github.com/cgohlke/talib-build/releases` ## Usage See `examples` folder. ```python # We need to modify the function name by prefixing `ts_` before using them in `expr_coodegen` from polars_ta.prefix.tdx import * # Import functions from `wq` from polars_ta.prefix.wq import * # Example df = df.with_columns([ # Load from `wq` *[ts_returns(CLOSE, i).alias(f'ROCP_{i:03d}') for i in (1, 3, 5, 10, 20, 60, 120)], *[ts_mean(CLOSE, i).alias(f'SMA_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_std_dev(CLOSE, i).alias(f'STD_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_max(HIGH, i).alias(f'HHV_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_min(LOW, i).alias(f'LLV_{i:03d}') for i in (5, 10, 20, 60, 120)], # Load from `tdx` *[ts_RSI(CLOSE, i).alias(f'RSI_{i:03d}') for i in (6, 12, 24)], ]) ``` When both `min_samples` and `MIN_SAMPLES` are set, `min_samples` takes precedence. default value is `None`. ```python import polars_ta # Global settings. Priority Low polars_ta.MIN_SAMPLES = 1 # High priority ts_mean(CLOSE, 10, min_samples=1) ``` ## How We Designed This 1. We use `Expr` instead of `Series` to avoid using `Series` in the calculation. Functions are no longer methods of class. 2. Use `wq` first. It mimics `WorldQuant Alpha` and strives to be consistent with them. 3. Use `ta` otherwise. It is a `polars`-style version of `TA-Lib`. It tries to reuse functions from `wq`. 4. Use `tdx` last. It also tries to import functions from `wq` and `ta`. 5. We keep the same signature and parameters as the original `TA-Lib` in `talib`. 6. If there is a naming conflict, we suggest calling `wq`, `ta`, `tdx`, `talib` in order. The higher the priority, the closer the implementation is to `Expr`. ## Comparison of Our Indicators and Others See [compare](compare.md) ## Handling Null/NaN Values See [nan_to_null](nan_to_null.md) ## Debugging ```commandline git clone --depth=1 https://github.com/wukan1986/polars_ta.git cd polars_ta pip install -e . ``` Notice: If you have added some functions in `ta` or `tdx`, please run `prefix_ta.py` or `prefix_tdx.py` inside the `tools` folder to generate the corrected Python script (with the prefix added). This is required to use in `expr_codegen`. ## Reference - https://github.com/pola-rs/polars - https://github.com/TA-Lib/ta-lib - https://github.com/twopirllc/pandas-ta - https://github.com/bukosabino/ta - https://github.com/peerchemist/finta - https://github.com/wukan1986/ta_cn - https://support.worldquantbrain.com/hc/en-us/community/posts/20278408956439-从价量看技术指标总结-Technical-Indicator- - https://platform.worldquantbrain.com/learn/operators/operators # polars_ta 基于`polars`的算子库。实现量化投研中常用的技术指标、数据处理等函数。对于不易翻译成`Expr`的库(如:`TA-Lib`)也提供了函数式调用的封装 ## 安装 ### 在线安装 ```commandline pip install -i https://pypi.org/simple --upgrade polars_ta # 官方源 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade polars_ta # 国内镜像源 ``` ### 源码安装 ```commandline git clone --depth=1 https://github.com/wukan1986/polars_ta.git cd polars_ta python -m build cd dist pip install polars_ta-0.1.2-py3-none-any.whl ``` ### TA-Lib安装 Windows用户不会安装可从`https://github.com/cgohlke/talib-build/releases` 下载对应版本whl文件 ## 使用方法 参考`examples`目录即可,例如: ```python # 如果需要在`expr_codegen`中使用,需要有`ts_`等前权,这里导入提供了前缀 from polars_ta.prefix.tdx import * # 导入wq公式 from polars_ta.prefix.wq import * # 演示生成大量指标 df = df.with_columns([ # 从wq中导入指标 *[ts_returns(CLOSE, i).alias(f'ROCP_{i:03d}') for i in (1, 3, 5, 10, 20, 60, 120)], *[ts_mean(CLOSE, i).alias(f'SMA_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_std_dev(CLOSE, i).alias(f'STD_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_max(HIGH, i).alias(f'HHV_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_min(LOW, i).alias(f'LLV_{i:03d}') for i in (5, 10, 20, 60, 120)], # 从tdx中导入指标 *[ts_RSI(CLOSE, i).alias(f'RSI_{i:03d}') for i in (6, 12, 24)], ]) ``` 当`min_samples`和`MIN_SAMPLES`都设置时,以`min_samples`为准,默认值为`None` ```python import polars_ta # 全局设置。优先级低 polars_ta.MIN_SAMPLES = 1 # 指定函数。优先级高 ts_mean(CLOSE, 10, min_samples=1) ``` ## 设计原则 1. 调用方法由`成员函数`换成`独立函数`。输入输出使用`Expr`,避免使用`Series` 2. 优先实现`wq`公式,它仿`WorldQuant Alpha`公式,与官网尽量保持一致。如果部分功能实现在此更合适将放在此处 3. 其次实现`ta`公式,它相当于`TA-Lib`的`polars`风格的版本。优先从`wq`中导入更名 4. 最后实现`tdx`公式,它也是优先从`wq`和`ta`中导入 5. `talib`的函数名与参数与原版`TA-Lib`完全一致 6. 如果出现了命名冲突,建议调用优先级为`wq`、`ta`、`tdx`、`talib`。因为优先级越高,实现方案越接近于`Expr` ## 指标区别 请参考[compare](compare.md) ## 空值处理 请参考[nan_to_null](nan_to_null.md) ## 开发调试 ```commandline git clone --depth=1 https://github.com/wukan1986/polars_ta.git cd polars_ta pip install -e . ``` 注意:如果你在`ta`或`tdx`中添加了新的函数,请再运行`tools`下的`prefix_ta.py`或`prefix_tdx.py`,用于生成对应的前缀文件。前缀文件方便在`expr_codegen`中使用 ## 文档生成 ```commandline pip install -r requirements-docs.txt mkdocs build ``` 文档生成在`site`目录下,其中的`llms-full.txt`可以作为大语言模型的知识库导入。 也可以通过以下链接导入: https://polars-ta.readthedocs.io/en/latest/llms-full.txt ## 提示词 由于`llms-full.txt`信息不适合做提示词,所以`tools/prompt.py`提供了生成更简洁算子清单的功能。 用户也可以直接使用`prompt.txt`(欢迎提示词工程专家帮忙改进,做的更准确) ## 参考 - https://github.com/pola-rs/polars - https://github.com/TA-Lib/ta-lib - https://github.com/twopirllc/pandas-ta - https://github.com/bukosabino/ta - https://github.com/peerchemist/finta - https://github.com/wukan1986/ta_cn - https://support.worldquantbrain.com/hc/en-us/community/posts/20278408956439-从价量看技术指标总结-Technical-Indicator- ================================================ FILE: compare.md ================================================ # Differences in Indicators ## 1. EMA alike indicators ### 1.1 EMA 1. EMA(CLOSE, 10), `talib.set_compatibility(0)` is the default, and is equivalent to`EXPMEMA` - The first not nan value starts as `talib.SMA(CLOSE, 10)` 2. EMA(CLOSE, 10), `talib.set_compatibility(1)` - The first not nan value starts as `CLOSE` Since the logic of `EMA` calculation has changed in `TA-Lib`'s `compatibility mode 0`, it is complex and inefficient to implement it using expressions. We only implement `compatibility mode 1`. It just so happens that the Chinese stock software only implements `compatibility mode 1`. You can compare the full data with `TA-Lib` for unit testing. Indicators affected by `EMA` include `MACD, DEMA, TEMA, TRIX, T3`, etc. ### 1.2 Chinese Version of SMA(X, N, M) In essence, it is `RMA compatibility mode 0`, that is, the first valid value is the moving average. And then the difference is `alpha` 1. `SMA(X, N, M) = X.ema_mean(alpha=M/N)` 2. `RMA(X, N) = X.ema_mean(alpha=1/N) = SMA(X, N, 1)` 3. `EMA(x, N) = X.ema_mean(alpha=2/(N+1)) = X.ema_mean(span=N)` Refer to: [ewm_mean](https://pola-rs.github.io/polars/py-polars/html/reference/expressions/api/polars.Expr.ewm_mean.html#polars.Expr.ewm_mean) In this case, we use `RMA compatibility mode 1`. There exists error in switching between the two modes. So please provide enough length of data. The data in the later part can be used for unit testing. Indicators affected by `SMA` include `ATR, RSI`, etc. ### 1.3 Moving Sum Some indicators including `ADX` require the first value as a `SUM` rather than `SMA`. We will implement them later by `ema_mean(alpha=1/N)` ## 2. MAX/MIN 1. In package `wq`, `max_/min_` are cross-sectional operators, `ts_max/ts_min` are time series indicators 2. In `talib`, `MAX/MIN` are time series indicators, without cross-sectional operators. 3. In `ta`, to mimic `talib`, `MAX/MIN` are time series indicators, without cross-sectional operators. 4. In `tdx`, `MAX/MIN` are cross-sectional operators, `HHV/LLV` are time series indicators. # 指标区别 ## 1. EMA系列 ### 1.1 EMA指标 1. EMA(CLOSE, 10),`talib.set_compatibility(0)`,此为默认设置,等价于`EXPMEMA` - 第一个有效值为`talib.SMA(CLOSE, 10)` 2. EMA(CLOSE, 10),`talib.set_compatibility(1)` - 第一个有效值为`CLOSE` 由于`TA-Lib`的`兼容模式0`在`EMA`计算时逻辑发生了变动,用表达式实现起来复杂、计算效率低。 所以本库只实现`兼容模式1`。正好国内股票软件其实也只实现了`兼容模式1`。可以全量数据与`TA-Lib`进行单元测试比较 因`EMA`受影响的指标有`MACD, DEMA, TEMA, TRIX, T3`等。 ### 1.2 中国版SMA(X, N, M) 本质上是国外的`RMA 兼容模式0`,即第一个有效值为移动平均,然后就是`alpha`区别 1. `SMA(X, N, M) = X.ema_mean(alpha=M/N)` 2. `RMA(X, N) = X.ema_mean(alpha=1/N) = SMA(X, N, 1)` 3. `EMA(x, N) = X.ema_mean(alpha=2/(N+1)) = X.ema_mean(span=N)` 换算关系可参考: [ewm_mean](https://pola-rs.github.io/polars/py-polars/html/reference/expressions/api/polars.Expr.ewm_mean.html#polars.Expr.ewm_mean) 遇到这种情况,本项目还是用`RMA 兼容模式1`来代替,数据误差由大到小,所以请预先提供一定长度的数据。后面一段的数据可以单元测试 受影响的的指标有`ATR, RSI`等 ### 1.3 移动求和 `ADX`等一类的指标第一个有效值算法为`SUM`,而不是`SMA`,之后使用`ema_mean(alpha=1/N)`。此类暂不实现 ## 2. MAX/MIN等指标 1. 在`wq`中,`max_/min_`横向算子,`ts_max/ts_min`时序指标 2. 在`talib`中, `MAX/MIN`时序指标,没有横向算子 3. 在`ta`中,由于要模仿`talib`,所以有`MAX/MIN`时序指标,也没有横向算子 4. 在`tdx`中,`MAX/MIN`横向算子,`HHV/LLV`时序指标 ================================================ FILE: deprecated/demo_ta1.py ================================================ """ Thisis how polars implements calling third-party packages 以下是polars提供的实现调用第三方库的方案 expr.ta.func """ import polars as pl from polars_ta.utils.helper import TaLibHelper # noqa df = pl.DataFrame( { "A": [5, None, 3, 2, 1], "B": [5, 4, None, 2, 1], "C": [5, 4, 3, 2, 1], } ) df = df.with_columns([ # single input single ouput, no need to handle null/nan values # 一输入一输出,不需处理空值 pl.col('A').ta.COS().alias('COS'), # single input, multi output # 一输入多输出 pl.col('A').ta.BBANDS(timeperiod=2, skip_nan=True, schema=['upperband', 'middleband', 'lowerband']).alias('BBANDS'), # multi input, single output # 多输入一输出 pl.struct(['A', 'B', 'C']).ta.ATR(timeperiod=2, skip_nan=True).alias('ATR'), # multi input, multi output # 多输入多输出 pl.struct(['A', 'B']).ta.AROON(timeperiod=2, skip_nan=True, schema=('aroondown', 'aroonup'), schema_format='XX_{}_YY').alias('AROON'), # multi input, single output # 多输入一输出 pl.struct(['A', 'B']).ta.AROON(timeperiod=2, skip_nan=True, output_idx=1).alias('aroonup1'), # call third-party packages # 调用另一库 pl.col('A').bn.move_rank(window=2, skip_nan=False).alias('move_rank'), ]) print(df) df = df.unnest('BBANDS', 'AROON') print(df) ================================================ FILE: deprecated/demo_ta2.py ================================================ """ This is how we wrap upon polars's code 以下是polars提供的方案基础上封装的方案 func(expr) """ import polars as pl from polars_ta.utils.wrapper import init df = pl.DataFrame( { "A": [5, None, 3, 2, 1], "B": [5, 4, None, 2, 1], "C": [5, 4, 3, 2, 1], } ) # register to global variables. You may directly use them. # IDE might give warnings about not defined. # 已经注册到全局,可直接使用。但IDE中没有智能提示 init(to_globals=True, name_format='{}') df = df.with_columns([ # single input single ouput, no need to handle null/nan values # 一输入一输出,不需处理空值 COS(pl.col('A')).alias('COS'), # multi input, single output # 多输入一输出 ATR(pl.struct(['A', 'B', 'C']), timeperiod=2).alias('ATR1'), ATR(pl.col('A'), pl.col('B'), pl.col('C'), 2, skip_nan=True).alias('ATR2'), # single input, multi output, add prefix to the column names passing to `BBANDS` # 一输入多输出,可通过prefix为多输出添加前缀 BBANDS(pl.col('A'), timeperiod=2, skip_nan=True, schema_format='bbands_{}').alias('BBANDS'), # multi input multi output, set column name directly by `schema` # 多输入多输出。可通过schema直接添加 AROON(pl.struct(['A', 'B']), timeperiod=2, skip_nan=True, schema=('aroondown', 'aroonup')).alias('AROON'), ]) print(df) df = df.unnest('BBANDS', 'AROON') print(df) # another way of calling init # 另一种调用方法 t = init(to_globals=False, name_format='ts_{}') df = df.with_columns([ # single input single ouput, no need to handle null/nan values # 一输入一输出,不需处理空值 t.ts_COS(pl.col('A')).alias('ts_COS'), ]) print(df) ================================================ FILE: deprecated/helper.py ================================================ """ We follow the spirit of https://github.com/pola-rs/polars/issues/9261 and adjusted it to fit our needs. The usage is as follows expr.ta.func(..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) skip_nan: bool reduces speed output_idx: int select a single column when outputing multiple columns schema: list or tuple assign column names for multiple output columns schema_format: str assign data types for multiple output columns nan_to_null: bool replace all nan to null when outputing 以下是在github issues中 @cmdlineluser 提供的 注册命名空间 方案 https://github.com/pola-rs/polars/issues/9261 本人做了一定的调整。使用方法如下 expr.ta.func(..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) skip_nan: bool 是否跳过空值。可以处理停牌无数据的问题,但会降低运行速度 output_idx: int 多列输出时,选择只输出其中一列 schema: list or tuple 返回为多列时,会组装成struct,可以提前设置子列的名字 schema_format: str 为子列名指定格式 nan_to_null: bool 返回值是否改成null 其它参数按**位置参数**和**命名参数**输入皆可 """ import numpy as np from polars import Expr, Float64, DataFrame, api from polars import Series, Struct def func_wrap_mn(func, cols, *args, skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False, **kwargs): """multiple input multiple output, compatible with single input single output 多输入多输出,兼容一输入一输出 Parameters ---------- func cols args skip_nan output_idx schema schema_format nan_to_null kwargs Returns ------- Series """ if cols.dtype.base_type() == Struct: # struct(['A', 'B']).ta.AROON _cols = [cols.struct[field] for field in cols.dtype.to_schema()] else: # col('A').ta.BBANDS _cols = [cols] if skip_nan: _cols = [c.cast(Float64).to_numpy() for c in _cols] _cols = np.vstack(_cols) # move nan to head idx1 = (~np.isnan(_cols).any(axis=0)).argsort(kind='stable') # restore nan idx2 = idx1.argsort(kind='stable') _cols = [_cols[i, idx1] for i in range(len(_cols))] result = func(*_cols, *args, **kwargs) if isinstance(result, tuple): result = tuple([_[idx2] for _ in result]) else: result = result[idx2] else: result = func(*_cols, *args, **kwargs) if isinstance(result, tuple): if output_idx is None: # struct(['A').ta.BBANDS # you need alias outside, finally unnest if schema is None: schema = [f'column_{i}' for i in range(len(result))] schema = [schema_format.format(name) for name in schema] # nan_to_null is not effective for struct # nan_to_null 对struct中的nan无效 return DataFrame(result, schema=schema, nan_to_null=nan_to_null).to_struct('') # output only one column if 0 <= output_idx < len(result): return Series(result[output_idx], nan_to_null=nan_to_null) elif not isinstance(result, Series): # col('A').bn.move_rank return Series(result, nan_to_null=nan_to_null) else: # col('A').ta.COS if nan_to_null: return result.fill_nan(None) else: return result def func_wrap_11(func, cols, *args, skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False, **kwargs): """single input single output is faster than multiple input multiple output 一输入,一输出。处理速度能更快一些""" _cols = cols if skip_nan: _cols = _cols.cast(Float64).to_numpy() # move nan to head idx1 = (~np.isnan(_cols)).argsort(kind='stable') # restore nan idx2 = idx1.argsort(kind='stable') _cols = _cols[idx1] result = func(_cols, *args, **kwargs) result = result[idx2] else: result = func(_cols, *args, **kwargs) if isinstance(result, Series): if nan_to_null: return result.fill_nan(None) else: return result else: return Series(result, nan_to_null=nan_to_null) class FuncHelper: def __init__(self, expr: Expr, lib=None, wrap=None) -> None: """initialization Parameters ---------- expr lib: third-party packages required wrap: wrapper for third-party packages 初始化 Parameters ---------- expr lib: 需要调用的第三方库 wrap: 对第三方库的封装方法 """ object.__setattr__(self, '_expr', expr) object.__setattr__(self, '_lib', lib) object.__setattr__(self, '_wrap', wrap) def __getattribute__(self, name: str): _expr: Expr = object.__getattribute__(self, '_expr') _lib = object.__getattribute__(self, '_lib') _wrap = object.__getattribute__(self, '_wrap') _func = getattr(_lib, name) return ( lambda *args, skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False, **kwargs: _expr.map_batches( lambda cols: _wrap(_func, cols, *args, skip_nan=skip_nan, output_idx=output_idx, schema=schema, schema_format=schema_format, nan_to_null=nan_to_null, **kwargs) ) ) @api.register_expr_namespace('ta') class TaLibHelper(FuncHelper): def __init__(self, expr: Expr) -> None: import talib as ta super().__init__(expr, ta, func_wrap_mn) @api.register_expr_namespace('bn') class BottleneckHelper(FuncHelper): def __init__(self, expr: Expr) -> None: import bottleneck as bn super().__init__(expr, bn, func_wrap_11) ================================================ FILE: deprecated/pandas_.py ================================================ from functools import lru_cache from typing import Tuple import numpy as np from numba import jit from pandas._libs.window.aggregations import roll_kurt as _roll_kurt from pandas._libs.window.aggregations import roll_rank as _roll_rank from polars import Series """ When converting float32 to float64 before computing. Either use x.cast(Float64).to_numpy() or x.to_numpy().astype(float) The second one is faster 在计算前需要将float32转成float64,有以下两种方法 x.cast(Float64).to_numpy() x.to_numpy().astype(float) 第二种方法更快 """ @lru_cache @jit(nopython=True, nogil=True, fastmath=True, cache=True) def get_window_bounds( num_values: int = 0, window_size: int = 10, ) -> Tuple[np.ndarray, np.ndarray]: end = np.arange(1, num_values + 1, dtype=np.int64) start = end - window_size start = np.clip(start, 0, num_values) return start, end def roll_rank(x: Series, d: int, minp: int, pct: bool = True, method: str = 'average', ascending: bool = True): start, end = get_window_bounds(len(x), d) """ https://github.com/pandas-dev/pandas/blob/main/pandas/_libs/window/aggregations.pyx#L1281 def roll_rank(const float64_t[:] values, ndarray[int64_t] start, ndarray[int64_t] end, int64_t minp, bint percentile, str method, bint ascending) -> np.ndarray: O(N log(window)) implementation using skip list """ ret = _roll_rank(x.to_numpy().astype(float), start, end, minp, pct, method, ascending) return Series(ret, nan_to_null=True) def roll_kurt(x, d: int, minp: int): start, end = get_window_bounds(len(x), d) """ https://github.com/pandas-dev/pandas/blob/main/pandas/_libs/window/aggregations.pyx#L803 def roll_kurt(ndarray[float64_t] values, ndarray[int64_t] start, ndarray[int64_t] end, int64_t minp) -> np.ndarray: """ ret = _roll_kurt(x.to_numpy().astype(float), start, end, minp) return Series(ret, nan_to_null=True) ================================================ FILE: deprecated/wrapper.py ================================================ """ Another wrapper for raised github issue before: expr.ta.func(..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) now: func(expr, ..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) This wrapper allows the prefix expression to be easily used in genetic algorithm tools 此处是对github issues上提出的 注册命名空间 方案的再封装 之前的使用方法为 expr.ta.func(..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) 封装后方法为 func(expr, ..., skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False) 此种封装方法的优点是前缀表达式方便输入到遗传算法工具包中使用 """ from functools import wraps import talib as _talib from polars import Expr from polars import struct from talib import abstract as _abstract from polars_ta.utils.helper import TaLibHelper _ = TaLibHelper def ta_func(func, func_name, input_names, output_names, *args, skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False, **kwargs): """ Parameters ---------- func func_name input_names: list of str 函数输入名 output_names: list of str 函数输出名 args 位置参数 skip_nan output_idx schema schema_format nan_to_null kwargs 命名参数 Returns ------- """ exprs = [arg for arg in args if isinstance(arg, Expr)] param = [arg for arg in args if not isinstance(arg, Expr)] if len(exprs) == 1: ef = getattr(exprs[0].ta, func_name) else: ef = getattr(struct(*exprs).ta, func_name) return ef(*param, skip_nan=skip_nan, output_idx=output_idx, schema=schema, schema_format=schema_format, nan_to_null=nan_to_null, **kwargs) def ta_decorator(func, func_name, input_names, output_names): @wraps(func) def decorated(*args, skip_nan=False, output_idx=None, schema=None, schema_format='{}', nan_to_null=False, **kwargs): return ta_func(func, func_name, input_names, output_names, *args, skip_nan=skip_nan, output_idx=output_idx, schema=schema, schema_format=schema_format, nan_to_null=nan_to_null, **kwargs) return decorated def init(to_globals=False, name_format='{}'): """初始化环境 Parameters ---------- to_globals: bool 是否注册到全局变量中 name_format: str 函数名格式 Returns ------- class 对象实例,可调用其内的方法 """ class TA_LIB: pass lib = TA_LIB() for i, func_name in enumerate(_talib.get_functions()): """talib遍历""" _ta_func = getattr(_talib, func_name) info = _abstract.Function(func_name).info output_names = info['output_names'] input_names = info['input_names'] # 创建函数 f = ta_decorator(_ta_func, func_name, input_names, output_names) name = name_format.format(func_name) setattr(lib, name, f) if to_globals: from inspect import currentframe frame = currentframe().f_back frame.f_globals[name] = f return lib ================================================ FILE: docs/index.md ================================================ # polars_ta 尽量接近`WorldQuant Alpha101`风格函数,但部分又做了一定的调整 例如: 1. x.abs().log() - 可利用`IDE`的自动补全,输入方便 - 默认是一个输入,多输入要通过参数列表。输入不统一 2. log(abs_(x)) - 都是通过参数列表,输入统一 - 一层套一层,正好对应表达式树,直接可用于遗传规划 - `abs`与`python`内置函数冲突,使用`abs_`代替,同样还有`and_`、`int_`、`max_`等 ## References - https://platform.worldquantbrain.com/learn/operators/operators - https://github.com/TA-Lib/ta-lib ================================================ FILE: docs/ta/index.md ================================================ 用`polars`函数模仿`TA-Lib`的函数 ================================================ FILE: docs/ta/momentum.md ================================================ ::: polars_ta.ta.momentum ================================================ FILE: docs/ta/operators.md ================================================ ::: polars_ta.ta.operators ================================================ FILE: docs/ta/overlap.md ================================================ ::: polars_ta.ta.overlap ================================================ FILE: docs/ta/price.md ================================================ ::: polars_ta.ta.price ================================================ FILE: docs/ta/statistic.md ================================================ ::: polars_ta.ta.statistic ================================================ FILE: docs/ta/transform.md ================================================ ::: polars_ta.ta.transform ================================================ FILE: docs/ta/volatility.md ================================================ ::: polars_ta.ta.volatility ================================================ FILE: docs/ta/volume.md ================================================ ::: polars_ta.ta.volume ================================================ FILE: docs/talib/index.md ================================================ 直接调用`TA-Lib`, 多输出时返回`struct`, 可以使用`.struct[0]`取到对应字段的`Series`. ::: polars_ta.talib ================================================ FILE: docs/tdx/arithmetic.md ================================================ ::: polars_ta.tdx.arithmetic ================================================ FILE: docs/tdx/choice.md ================================================ ::: polars_ta.tdx.choice ================================================ FILE: docs/tdx/energy.md ================================================ ::: polars_ta.tdx.energy ================================================ FILE: docs/tdx/logical.md ================================================ ::: polars_ta.tdx.logical ================================================ FILE: docs/tdx/moving_average.md ================================================ ::: polars_ta.tdx.moving_average ================================================ FILE: docs/tdx/over_bought_over_sold.md ================================================ ::: polars_ta.tdx.over_bought_over_sold ================================================ FILE: docs/tdx/pattern.md ================================================ ::: polars_ta.tdx.pattern ================================================ FILE: docs/tdx/pattern_feature.md ================================================ ::: polars_ta.tdx.pattern_feature ================================================ FILE: docs/tdx/pressure_support.md ================================================ ::: polars_ta.tdx.pressure_support ================================================ FILE: docs/tdx/reference.md ================================================ ::: polars_ta.tdx.reference ================================================ FILE: docs/tdx/statistic.md ================================================ ::: polars_ta.tdx.statistic ================================================ FILE: docs/tdx/times.md ================================================ ::: polars_ta.tdx.times ================================================ FILE: docs/tdx/trend.md ================================================ ::: polars_ta.tdx.trend ================================================ FILE: docs/tdx/trend_feature.md ================================================ ::: polars_ta.tdx.trend_feature ================================================ FILE: docs/tdx/volume.md ================================================ ::: polars_ta.tdx.volume ================================================ FILE: docs/wq/arithmetic.md ================================================ ::: polars_ta.wq.arithmetic ================================================ FILE: docs/wq/cross_sectional.md ================================================ ::: polars_ta.wq.cross_sectional ================================================ FILE: docs/wq/half_life.md ================================================ ::: polars_ta.wq.half_life ================================================ FILE: docs/wq/logical.md ================================================ ::: polars_ta.wq.logical ================================================ FILE: docs/wq/preprocess.md ================================================ ::: polars_ta.wq.preprocess ================================================ FILE: docs/wq/time_series.md ================================================ ::: polars_ta.wq.time_series ================================================ FILE: docs/wq/transformational.md ================================================ ::: polars_ta.wq.transformational ================================================ FILE: docs/wq/vector.md ================================================ ::: polars_ta.wq.vector ================================================ FILE: examples/alpha101.py ================================================ from datetime import datetime import pandas as pd import polars as pl from polars_ta.prefix.tdx import * from polars_ta.prefix.wq import * # definition of your features # 因子定义 OPEN, HIGH, LOW, CLOSE, VOLUME, AMOUNT, = pl.col('OPEN'), pl.col('HIGH'), pl.col('LOW'), pl.col('CLOSE'), pl.col('VOLUME'), pl.col('AMOUNT'), RETURNS, VWAP, CAP, = pl.col('RETURNS'), pl.col('VWAP'), pl.col('CAP'), ADV5, ADV10, ADV15, ADV20, ADV30, ADV40, ADV50, ADV60, ADV81, ADV120, ADV150, ADV180, = ( pl.col('ADV5'), pl.col('ADV10'), pl.col('ADV15'), pl.col('ADV20'), pl.col('ADV30'), pl.col('ADV40'), pl.col('ADV50'), pl.col('ADV60'), pl.col('ADV81'), pl.col('ADV120'), pl.col('ADV150'), pl.col('ADV180'),) SECTOR, INDUSTRY, SUBINDUSTRY, = pl.col('SECTOR'), pl.col('INDUSTRY'), pl.col('SUBINDUSTRY'), # you can only call column-alike factor directly. Since ts_, cs_ and gp_ etc. need to be used with group_by, please use expr_codegen project for more complex formula # 列因子才可以直接调用,而ts_、cs_和gp_等公式需要套用group_by,复杂公式请使用expr_codegen项目 alpha_041 = (((HIGH * LOW) ** 0.5) - VWAP) alpha_054 = ((-1 * ((LOW - CLOSE) * (OPEN ** 5))) / ((LOW - HIGH) * (CLOSE ** 5))) alpha_101 = ((CLOSE - OPEN) / ((HIGH - LOW) + 0.001)) # simulate 5000 stocks for 10 years # 模拟5000支股票10年 ASSET_COUNT = 5000 DATE_COUNT = 250 * 10 DATE = pd.date_range(datetime(2020, 1, 1), periods=DATE_COUNT).repeat(ASSET_COUNT) ASSET = [f'A{i:04d}' for i in range(ASSET_COUNT)] * DATE_COUNT # your test data. multi-asset multi-feature # 测试数据。多资产多特征 df = pl.DataFrame( { 'date': DATE, 'asset': ASSET, "OPEN": np.random.rand(DATE_COUNT * ASSET_COUNT), "HIGH": np.random.rand(DATE_COUNT * ASSET_COUNT), "LOW": np.random.rand(DATE_COUNT * ASSET_COUNT), "CLOSE": np.random.rand(DATE_COUNT * ASSET_COUNT), "VWAP": np.random.rand(DATE_COUNT * ASSET_COUNT), "FILTER": np.tri(DATE_COUNT, ASSET_COUNT, k=-2).reshape(-1), } ) # filter out some data. test if it will raise error when the length is not enough # 每行数据量不同,测试是否会因为长度不足报错 df = df.filter(pl.col("FILTER") == 1) # some simple features defined in Alpha101. They don't contain groupby in time-series or cross-section # 部分Alpha101计算。不涉及时序和横截面,可直接计算 df = df.with_columns( alpha_041=alpha_041, alpha_054=alpha_054, alpha_101=alpha_101, ) def func_ts_date(df: pl.DataFrame) -> pl.DataFrame: # ensure sorted before computing time-series features # 时序指标计算前一定要保证有序 df = df.sort(by=['date']) # Example of generating features # 演示生成大量指标 df = df.with_columns([ # load from sub-package `wq` # 从wq中导入指标 *[ts_returns(CLOSE, i).alias(f'ROCP_{i:03d}') for i in (1, 3, 5, 10, 20, 60, 120)], *[ts_mean(CLOSE, i).alias(f'SMA_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_std_dev(CLOSE, i).alias(f'STD_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_max(HIGH, i).alias(f'HHV_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_min(LOW, i).alias(f'LLV_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_rank(CLOSE, i).alias(f'RANK_{i:03d}') for i in (5, 10, 20, 60, 120)], *[ts_arg_max(HIGH, i).alias(f'HHVBAR_{i:03d}') for i in (5, 10, 20, 60, 120)], # load from sub-package `tdx` # 从tdx中导入指标 *[ts_RSI(CLOSE, i).alias(f'RSI_{i:03d}') for i in (6, 12, 24)], ]) return df # ensure grouped before applying to multi-asset data # 多资产需要先按资产分组 df = df.group_by('asset').map_groups(func_ts_date) print(df) ================================================ FILE: examples/demo_ta3.py ================================================ """ This is how we wrap upon polars's code 以下是polars提供的方案基础上封装的方案 func(expr) """ import polars as pl from polars_ta.talib import * df = pl.DataFrame( { "A": [5, None, 3, 2, 1], "B": [5, 4, None, 2, 1], "C": [5, 4, 3, 2, 1], } ) df = df.with_columns([ # single input single ouput, no need to handle null/nan values # # 一输入一输出,不需处理空值 COS(pl.col('A')).alias('COS'), # 多输入一输出 ATR(pl.col('A'), pl.col('B'), pl.col('C'), 2).alias('ATR2'), # 原为一输入多输出,通过ret_idx指定一输出 BBANDS(pl.col('A'), timeperiod=2).alias('BBANDS'), # 原为多输入多输出,通过ret_idx指定一输出 AROON('A', 'B', timeperiod=2).alias('AROON'), ]) print(df) ================================================ FILE: mkdocs.yml ================================================ site_name: polars_ta API References site_url: https://polars-ta.readthedocs.io/en/latest/ nav: - Home: index.md - polars_ta.wq: - wq/arithmetic.md - wq/cross_sectional.md - wq/logical.md - wq/preprocess.md - wq/time_series.md - wq/transformational.md - wq/vector.md - polars_ta.ta: - ta/index.md - ta/momentum.md - ta/operators.md - ta/overlap.md - ta/price.md - ta/statistic.md - ta/transform.md - ta/volatility.md - ta/volume.md - polars_ta.tdx: - tdx/arithmetic.md - tdx/choice.md - tdx/energy.md - tdx/logical.md - tdx/moving_average.md - tdx/over_bought_over_sold.md - tdx/pattern.md - tdx/pattern_feature.md - tdx/pressure_support.md - tdx/reference.md - tdx/statistic.md - tdx/trend.md - tdx/trend_feature.md - tdx/volume.md - polars_ta.talib: talib/index.md theme: name: material features: - navigation.tracking - navigation.instant - navigation.tabs - navigation.tabs.sticky - navigation.footer - navigation.indexes - content.tabs.link - content.code.copy strict: true plugins: - search - mkdocstrings: default_handler: python handlers: python: paths: [ . ] options: summary: true show_root_heading: true show_if_no_docstring: true show_source: false show_signature_annotations: true docstring_style: numpy - llmstxt: full_output: llms-full.txt sections: Usage documentation: - index.md - wq/*.md - ta/*.md - tdx/*.md - talib/*.md ================================================ FILE: nan_to_null.md ================================================ # nan_to_null It is important to handle nan and null values correctly. In many cases, null values may occur. Such as: 1. Stock suspension, data missing, etc. 2. Calculation exceptions, such as division by 0, log non-positive, etc. ## Null (None in Python) and NaN are different! ```python None == None # True # (It should be `None is None`) np.nan == np.nan # False ``` But in `pandas/numpy` many do not distinguish between `None` and `np.nan`, so you should always use `is_null/is_nan` functions to be safe. 1. In `pandas` 1.x, the representation of null values is: - floating point uses `nan`, - string uses `None`, - time uses `pd.NaT`, - integer has no null value representation, it can be converted to a floating-point number. 2. In `pandas` 2.x, the backend can use `Arrow`. It has two storage areas, one is the original data, and the other is the validity bitmap array, which marks whether it is `null`, so there is no data type restriction. 3. In `polars`, the backend is `Arrow`. With a bitmap array, counting `null` values is faster than traversing the original array. ## `polars` 1. Most functions adapt to `null` but not `nan`, the best way should be using `fill_nan(None)` first. 2. To adapt to third-party packages, such as `TA-Lib`, when calling `to_numpy`, `null` will be automatically converted to `nan`, and the return value should be `pl.Series(, nan_to_null=True)`. 3. For the case of `null/nan` appearing in the middle, there is currently no good way to handle it. ## Reference 1. https://docs.pola.rs/user-guide/expressions/missing-data/#null-and-nan-values 2. https://pandas.pydata.org/docs/user_guide/missing_data.html # nan_to_null 空值的处理是一件非常头疼的事性。多种情况下可能会出现空值。如: 1. 股票停牌、数据缺失 等 2. 计算异常,如:除0,log非正数 等 ## None与NaN区别大不同 比较结果不同 ```python None == None # True # 注意None is None才是规范的写法 np.nan == np.nan # False ``` 但在`pandas/numpy`中将`None`当成`np.nan`使用,这类容易混淆的地方,一定要使用`is_null/is_nan`函数才稳妥 1. 在pandas 1.x版本中,空值表示方法有:浮点用`nan`,字符串用`None`,时间用`pd.NaT`,整型没有空值表示方法,只能转成浮点 2. 在pandas 2.x版本中,后端可用`Arrow`。它有两块存储区域,一块还是原数据,另一块则是有效性位图数组,由它标记是否为`null`, 所以没有数据类型限制 3. 在polars中后端是`Arrow` 有位图数组,所以统计`null`数量肯定比遍历原数组要快 ## polars 1. 大部分函数只适配了`null`,所以对于`nan`,最合适的处理方法是`fill_nan(None)`后再处理 2. 需要调用第三方函数时,以`TA-Lib`为例,`to_numpy`时会自动`null`转`nan`,返回值需`pl.Series(, nan_to_null=True)` 3. 对于中段出现`null/nan`的情况,目前还没有很好的处理方法 # 参考 1. https://docs.pola.rs/user-guide/expressions/missing-data/#null-and-nan-values 2. https://pandas.pydata.org/docs/user_guide/missing_data.html ================================================ FILE: point_in_time.md ================================================ # Point In Time Essentially it is due to the fix of historical erroneous data. Sometimes we even need to fix the historical market data. 1. Market data:date\asset\features\[update_time] 2. Financial statement data:date\asset\features\update_time `date`: time of the report, `update_time`: time of the release ## Update of Financial Statement Data There are two ways to download the data. 1. Download by `date`, just specify the 4 report periods of each year. But if the historical report period is changed in the new record, and the user cannot know which report period it is, the entire data needs to be downloaded. 2. Download by `update_time`, which can perfectly solve the above problem. But the release date is not fixed, and holidays may also occur. Downloading by day is inefficient, so it needs to be downloaded period by period. ## Storage of Financial Statement Data Since the data is downloaded by `update_time`, it should be also stored by `update_time`, which is convenient for reading and updating. ## Storage of Historical Market Data In market data, we do not need to distinguish the `date` and `update_time`. Two ways to modify market data: 1. Similar to financial statement data, only add without modifying. - Stored by day, is the record appended to an extra file or added to the file of that day? - Because the storage is ordered by `update_time`, it should be added to the extra last file. 2. Directly modify the original data. In market data, the `update_time` is generally omitted, so the update method is more often to replace the old data. ## Dealing with data from PIT 1. The data update time may be on weekends. But the ideal way is to handle it the same as market data, so the `date` needs to be moved to Friday, and the `update_time` remains the same as the real time. 2. The PIT processing is divided into three steps. - filter out the corresponding time of the DF, - calculate the various time series indicators on each DF separately (or not), - take the latest part of each DF for merging. After this, the time series calculation cannot be executed again, because it will introduce future data. # Point In Time 本质上是因发现历史数据有问题,需要进行修改而产生的。我们使用的行情数据其实也会有修改需求。所以是否能将其一起讨论呢? 1. 行情数据:date\asset\features\[update_time] 2. 财务数据:date\asset\features\update_time date表示报告期、update_time表示公布日期 ## 财务数据的更新 1. 按报告期date下载,只要指定每年的4个报告期即可下载。但新记录中如果改了历史报告期。而用户又无法知道是哪个报告期,得全下 2. 按公布日update_time下载,能完美的解决上面的问题。但公布日不固定、节假日也会发生,按日下载又效率低,所以得按时间段下载 ## 财务数据的存储 由于已经选定用update_time来下载数据,所以存储时也按update_time来分文件名,这样也方便读取和更新 ## 行情数据的更新与存储 行情数据中,date其本质就是报告期,又因更新时间等于报告期,所以被省略了,同股票date一般也不会重复,如有重复,必有update_time字段做区分,否则数据有误。 行情数据有两种修改方法: 1. 仿财务数据,只添加不修改。(其实财务数据发布后在第二天交易开始前发生了更新可以直接修改) - 按日分文件存储,记录是添加到最后一个文件中,还是添加到那天的文件呢? - 因为存储是按update_time有序,所以应当添加到最后 2. 直接修改原数据。在行情数据由于一般省略了update_time,所以更新方式也更多是覆盖 ## PIT数据的处理 1. 数据更新时间可能是周末。但理想方式是与行情一样处理,所以date需要移动到周五,update_time保持真实时间 2. PIT处理分三步,第一步过滤出对应时间的DF、第二步在每个DF上分别计算各时序指标(也可不算),第三步取每个DF中最新部分进行合并 不能在第三步后再执行时序计算了,因为这会引入未来数据 ================================================ FILE: polars_ta/__init__.py ================================================ from typing import Optional from ._version import __version__ # 默认最小误差值 TA_EPSILON: float = 1e-8 # 默认最小样本数量 MIN_SAMPLES: Optional[int] = None ================================================ FILE: polars_ta/_version.py ================================================ __version__ = "0.5.17" ================================================ FILE: polars_ta/candles/__init__.py ================================================ from polars_ta.candles.cdl1 import * # noqa from polars_ta.candles.cdl1_limit import * # noqa from polars_ta.candles.cdl2 import * # noqa ================================================ FILE: polars_ta/candles/cdl1.py ================================================ """ In this file we use open_: Expr, high: Expr, low: Expr, close: Expr for all parameters to ensure the similar function signatures when calling them 本文件中参数全用 open_: Expr, high: Expr, low: Expr, close: Expr 统一的好处是在使用时不用考虑函数调用区别 """ from polars import Expr from polars import max_horizontal, min_horizontal from polars_ta import TA_EPSILON # https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_utility.h#L327 def real_body(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """实体""" return (close - open_).abs() def upper_shadow(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """上影""" return high - max_horizontal(open_, close) def lower_shadow(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """下影""" return min_horizontal(open_, close) - low def high_low_range(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """总长""" return high - low def upper_body(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """实体上沿""" return max_horizontal(open_, close) def lower_body(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """实体下沿""" return min_horizontal(open_, close) def shadows(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """阴影""" return high_low_range(open_, high, low, close) - real_body(open_, high, low, close) def efficiency_ratio(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """ abs(close-open) / (2 * (high-low) - abs(close-open) + TA_EPSILON) K线内的市场效率。两个总长减去一个实体长就是路程 比较粗略的计算市场效率的方法。丢失了部分路程信息,所以结果会偏大 """ displacement = real_body(open_, high, low, close) distance = 2 * high_low_range(open_, high, low, close) - displacement return displacement / (distance + TA_EPSILON) # ==================================== def candle_color(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """K线颜色""" return close >= open_ def four_price_doji(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """一字""" return low >= (high - TA_EPSILON) def doji(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """十字(含一字、T字)""" return (open_ - close).abs() <= TA_EPSILON def dragonfly(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """正T字""" return doji(open_, high, low, close) & (low < close - TA_EPSILON) def gravestone(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """倒T字""" return doji(open_, high, low, close) & (high > close + TA_EPSILON) # def candle_range(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # return real_body(open_, high, low, close) # return high_low_range(open_, high, low, close) # return shadows(open_, high, low, close) # # # def candle_average(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # """平均值""" # return high_low_range(open_, high, low, close) ================================================ FILE: polars_ta/candles/cdl1_limit.py ================================================ """ In this file we use open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr for all parameters to ensure the similar function signatures when calling them 本文件中参数全用 open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr 统一的好处是在使用时不用考虑函数调用区别 """ from polars import Expr from polars_ta import TA_EPSILON from polars_ta.candles.cdl1 import four_price_doji, dragonfly, gravestone def limit_up(price: Expr, high_limit: Expr) -> Expr: """ unified handles for reached `high_limit` at `open_` reached `high_limit` at `close` reached `high_limit` at `high` but `close` < `high_limit` (at least once reached `high_limit` in this day) 开盘 涨停 收盘 涨停 最高 曾涨停 """ return price >= high_limit - TA_EPSILON def limit_up_at_open(open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr) -> Expr: """ reached `high_limit` at `open_` 开盘 涨停 """ return limit_up(open_, high_limit) def limit_up_at_close(open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr) -> Expr: """ reached `high_limit` at `close` 收盘 涨停 """ return limit_up(close, high_limit) def limit_up_at_high(open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr) -> Expr: """ reached `high_limit` at `high` but `close` < `high_limit` (at least once reached `high_limit` in this day) 曾涨停 """ return limit_up(high, high_limit) & ~limit_up(close, high_limit) def limit_up_four_price_doji(open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr) -> Expr: """ `low` = `high` = `open_` = `close` = `high_limit` 一字涨停 """ return four_price_doji(open_, high, low, close) & limit_up(close, high_limit) def limit_up_dragonfly(open_: Expr, high: Expr, low: Expr, close: Expr, high_limit: Expr) -> Expr: """ `low` < `high` = `open_` = `close` = `high_limit` T字涨停""" return limit_up(close, high_limit) & dragonfly(open_, high, low, close) # ====================================== def limit_down(price: Expr, low_limit: Expr) -> Expr: """ unified handles for reached `low_limit` at `open_` reached `low_limit` at `close` reached `low_limit` at `high` but `close` > `low_limit` (at least once reached `low_limit` in this day) 开盘 跌停 收盘 跌停 最低 曾跌停 """ return price <= low_limit + TA_EPSILON def limit_down_at_open(open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr) -> Expr: """ reached `low_limit` at `open_` 开盘 跌停 """ return limit_down(open_, low_limit) def limit_down_at_close(open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr) -> Expr: """ reached `low_limit` at `close` 收盘 跌停 """ return limit_down(close, low_limit) def limit_down_at_high(open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr) -> Expr: """ reached `low_limit` at `high` but `close` > `low_limit` (at least once reached `low_limit` in this day) 曾跌停 """ return limit_down(low, low_limit) & ~limit_down(close, low_limit) def limit_down_four_price_doji(open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr) -> Expr: """ `low` = `high` = `open_` = `close` = `low_limit` 一字跌停 """ return four_price_doji(open_, high, low, close) & limit_down(close, low_limit) def limit_down_gravestone(open_: Expr, high: Expr, low: Expr, close: Expr, low_limit: Expr) -> Expr: """ `high` > `low` = `open_` = `close` = `low_limit` T字跌停 """ return limit_down(open_, low_limit) & gravestone(open_, high, low, close) ================================================ FILE: polars_ta/candles/cdl2.py ================================================ """ In this file we use open_: Expr, high: Expr, low: Expr, close: Expr for all parameters to ensure the similar function signatures when calling them 本文件中参数全用 open_: Expr, high: Expr, low: Expr, close: Expr 统一的好处是在使用时不用考虑函数调用区别 """ from polars import Expr from polars_ta.candles.cdl1 import lower_body, upper_body # https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_utility.h#L360 def ts_gap_up(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """跳空高开""" return low > high.shift(1) def ts_gap_down(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """跳空低开""" return high < low.shift(1) def ts_real_body_gap_up(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """实体跳空高开""" return lower_body(open_, high, low, close) > upper_body(open_, high, low, close).shift(1) def ts_real_body_gap_down(open_: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """实体跳空低开""" return upper_body(open_, high, low, close) < lower_body(open_, high, low, close).shift(1) ================================================ FILE: polars_ta/labels/__init__.py ================================================ from polars_ta.labels.future import * # noqa ================================================ FILE: polars_ta/labels/_nb.py ================================================ import numpy as np from numba import jit from numpy import full @jit(nopython=True, nogil=True, cache=True) def _triple_barrier(close: np.ndarray, high: np.ndarray, low: np.ndarray, window: int, take_profit: float, stop_loss: float) -> np.ndarray: """三重障碍打标法""" out = full(close.shape[0], np.nan, dtype=np.float64) for i in range(close.shape[0] - window + 1): entry_price = close[i] if np.isnan(entry_price): # out[i] = 0 continue upper_barrier = entry_price * (1 + take_profit) lower_barrier = entry_price * (1 - stop_loss) for j in range(i + 1, i + window): hit_upper = high[j] >= upper_barrier hit_lower = low[j] <= lower_barrier if hit_upper and hit_lower: # TODO 同一天无法知道是先触发止损还是先触发止盈 # 1. 假定离收盘价远的先触发 if high[j] - close[j] > close[j] - low[j]: out[i] = 1 # 最高价更远,触发止盈 else: out[i] = -1 # 最低价更远,触发止损 # out[i] = -1 # 2. 简化处理认为先触发止损 break if hit_upper: out[i] = 1 # 止盈 break if hit_lower: out[i] = -1 # 止损 break else: # out[i] = 0 # 1. 时间到了触发平仓 out[i] = np.sign(close[j] / entry_price - 1) # 2. 时间到了触发平仓 return out ================================================ FILE: polars_ta/labels/future.py ================================================ """ 由于标签的定义比较灵活,所以以下代码主要用于参考 Notes ----- 标签都是未来数据,在机器学习中,只能用于`y`,不能用于`X`。 References ---------- https://mp.weixin.qq.com/s/XtgYezFsslOfW-QyIMr0VA https://github.com/Rachnog/Advanced-Deep-Trading/blob/master/bars-labels-diff/Labeling.ipynb """ from polars import Expr, struct, Float64 from polars_ta.labels._nb import _triple_barrier from polars_ta.utils.numba_ import batches_i2_o1, struct_to_numpy from polars_ta.wq import cut, ts_delay, ts_log_diff, log def ts_log_return(close: Expr, n: int = 5) -> Expr: """将未来数据当成卖出价后移到买入价位置,计算对数收益率 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 10, 11, 12, 9, 12, 13], }).with_columns( out1=ts_log_return(pl.col('a'), 3), out2=_ts_log_return(pl.col('a'), 3), ) shape: (7, 3) ┌──────┬───────────┬───────────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪═══════════╪═══════════╡ │ null ┆ null ┆ null │ │ 10 ┆ -0.105361 ┆ -0.105361 │ │ 11 ┆ 0.087011 ┆ 0.087011 │ │ 12 ┆ 0.080043 ┆ 0.080043 │ │ 9 ┆ null ┆ null │ │ 12 ┆ null ┆ null │ │ 13 ┆ null ┆ null │ └──────┴───────────┴───────────┘ ``` """ # return (close.shift(-n) / close).log() return log(ts_delay(close, -n) / close) def _ts_log_return(close: Expr, n: int = 5) -> Expr: """计算对数收益率,但将结果后移 如果打标签方式复杂,这种最终结果后移的方法更方便 """ # return (close / close.shift(n)).log().shift(-n) return ts_delay(ts_log_diff(close, n), -n) def ts_simple_return(close: Expr, n: int = 5, threshold: float = 0.0, *more_threshold) -> Expr: """简单收益率标签。支持二分类、三分类等。对收益率使用`cut`进行分类 Parameters ---------- close n:int 未来n天 threshold:float 收益率阈值,小于该值为0,大于等于该值为1 more_threshold:float 更多的阈值,用于三分类等。小于该值为1,大于等于该值为2,以此类推 Returns ------- Expr 标签列, 类型为UInt32, 取值为0, 1, 2, ... Examples -------- ```python df = pl.DataFrame({ 'a': [None, 10., 9.99, 9., 10., 11., 11.], }).with_columns( out1=label_simple_return(pl.col('a'), 1, 0), out2=label_simple_return(pl.col('a'), 1, -0.001, 0.001), ) shape: (7, 3) ┌──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ f64 ┆ u32 ┆ u32 │ ╞══════╪══════╪══════╡ │ null ┆ null ┆ null │ │ 10.0 ┆ 0 ┆ 0 │ │ 9.99 ┆ 0 ┆ 0 │ │ 9.0 ┆ 1 ┆ 2 │ │ 10.0 ┆ 1 ┆ 2 │ │ 11.0 ┆ 0 ┆ 1 │ │ 11.0 ┆ null ┆ null │ └──────┴──────┴──────┘ """ return cut(close.pct_change(n).shift(-n), threshold, *more_threshold) def ts_triple_barrier(close: Expr, high: Expr, low: Expr, d: int = 5, take_profit: float = 0.1, stop_loss: float = 0.05) -> Expr: """三重障碍打标法 Parameters ---------- close:Expr 收盘价 high:Expr 最高价 low:Expr 最低价 d:int 时间窗口 take_profit:float 止盈比例 stop_loss:float 止损比例 Returns ------- Expr 标签列。取值为-1止损, 1止盈,0时间到期 Notes ----- 1. `high`, `low`在粗略情况下可用`close`代替 2. 时间到期时,根据盈亏返回不同的标签 Examples -------- ```python df = pl.DataFrame({ "close": [np.nan, 1, 1, 1.0], "high": [np.nan, 1, 1.1, 1], "low": [np.nan, 1, 1, 0.95], }).with_columns( out=ts_triple_barrier(pl.col("close"), pl.col("high"), pl.col("low"), 2, 0.1, 0.05) ) shape: (4, 4) ┌───────┬──────┬──────┬──────┐ │ close ┆ high ┆ low ┆ out │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 ┆ f64 │ ╞═══════╪══════╪══════╪══════╡ │ NaN ┆ NaN ┆ NaN ┆ null │ │ 1.0 ┆ 1.0 ┆ 1.0 ┆ 1.0 │ │ 1.0 ┆ 1.1 ┆ 1.0 ┆ -1.0 │ │ 1.0 ┆ 1.0 ┆ 0.95 ┆ null │ └───────┴──────┴──────┴──────┘ ``` """ return struct(f0=close, f1=high, f2=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3), _triple_barrier, d, take_profit, stop_loss), return_dtype=Float64) ================================================ FILE: polars_ta/noise.py ================================================ """ The following comes from Trading Systems and Methods, Chapter 1, Measuring Noise We do not provide ATR and STD here. ATR与STD也是一种度量波动的方法,这里不再提供 以下方法来自于Trading Systems and Methods, Chapter 1, Measuring Noise References ---------- https://zhuanlan.zhihu.com/p/544744582 """ import numpy as np from polars import Expr def ts_efficiency_ratio(close: Expr, timeperiod: int = 14) -> Expr: """效率系数。值越大,噪音越小。最大值为1,最小值为0 本质上是位移除以路程 """ t1 = close.diff(timeperiod).abs() t2 = close.diff(1).abs().rolling_sum(timeperiod) return t1 / t2 def ts_price_density(high: Expr, low: Expr, timeperiod: int = 14) -> Expr: """价格密度。值越大,噪音越大 如果K线高低相连,上涨为1,下跌也为1 如果K线高低平行,值大于1,最大为timeperiod """ t1 = (high - low).rolling_sum(timeperiod) t2 = high.rolling_max(timeperiod) - low.rolling_min(timeperiod) return t1 / t2 def ts_fractal_dimension(high: Expr, low: Expr, close: Expr, timeperiod: int = 14) -> Expr: """分形维度。值越大,噪音越大""" n1 = (1 / timeperiod) ** 2 n2 = np.log(2) n3 = np.log(2 * timeperiod) t1 = high.rolling_max(timeperiod) - low.rolling_min(timeperiod) t2 = close.diff(1) L = (n1 + t2 / t1).sqrt().rolling_sum(timeperiod) return 1 + (L.log() + n2) / n3 ================================================ FILE: polars_ta/performance/__init__.py ================================================ ================================================ FILE: polars_ta/performance/drawdown.py ================================================ from polars import Expr def ts_max_drawdown(close: Expr) -> Expr: """最大回撤""" return close - close.cum_max() def ts_max_drawdown_rate(close: Expr) -> Expr: """最大回撤率""" return close / close.cum_max() - 1 ================================================ FILE: polars_ta/performance/returns.py ================================================ from polars import Expr from polars_ta.wq.arithmetic import log1p, expm1 def ts_cum_return(close: Expr) -> Expr: """ cumulative return 累计收益 """ return close / close.drop_nulls().first() def simple_to_log_return(x: Expr) -> Expr: """简单收益率 转 对数收益率""" return log1p(x) def log_to_simple_return(x: Expr) -> Expr: """对数收益率 转 简单收益率""" return expm1(x) ================================================ FILE: polars_ta/prefix/__init__.py ================================================ ================================================ FILE: polars_ta/prefix/cdl.py ================================================ from polars_ta.candles import * # noqa ================================================ FILE: polars_ta/prefix/labels.py ================================================ from polars_ta.labels import * # noqa ================================================ FILE: polars_ta/prefix/reports.py ================================================ from polars_ta.reports import * # noqa ================================================ FILE: polars_ta/prefix/ta.py ================================================ # this code is auto generated by tools/prefix_ta.py from polars_ta.ta.momentum import APO as ts_APO # noqa from polars_ta.ta.momentum import AROON as ts_AROON # noqa from polars_ta.ta.momentum import MACD as ts_MACD # noqa from polars_ta.ta.momentum import MOM as ts_MOM # noqa from polars_ta.ta.momentum import PPO as ts_PPO # noqa from polars_ta.ta.momentum import ROC as ts_ROC # noqa from polars_ta.ta.momentum import ROCP as ts_ROCP # noqa from polars_ta.ta.momentum import ROCR as ts_ROCR # noqa from polars_ta.ta.momentum import ROCR100 as ts_ROCR100 # noqa from polars_ta.ta.momentum import RSI as ts_RSI # noqa from polars_ta.ta.momentum import RSV as ts_RSV # noqa from polars_ta.ta.momentum import STOCHF as ts_STOCHF # noqa from polars_ta.ta.momentum import TRIX as ts_TRIX # noqa from polars_ta.ta.momentum import WILLR as ts_WILLR # noqa from polars_ta.ta.operators import ADD # noqa from polars_ta.ta.operators import DIV # noqa from polars_ta.ta.operators import MAX as ts_MAX # noqa from polars_ta.ta.operators import MAXINDEX as ts_MAXINDEX # noqa from polars_ta.ta.operators import MIN as ts_MIN # noqa from polars_ta.ta.operators import MININDEX as ts_MININDEX # noqa from polars_ta.ta.operators import MUL # noqa from polars_ta.ta.operators import SUB # noqa from polars_ta.ta.operators import SUM as ts_SUM # noqa from polars_ta.ta.overlap import BBANDS # noqa from polars_ta.ta.overlap import DEMA as ts_DEMA # noqa from polars_ta.ta.overlap import EMA as ts_EMA # noqa from polars_ta.ta.overlap import KAMA as ts_KAMA # noqa from polars_ta.ta.overlap import MIDPOINT as ts_MIDPOINT # noqa from polars_ta.ta.overlap import MIDPRICE as ts_MIDPRICE # noqa from polars_ta.ta.overlap import RMA as ts_RMA # noqa from polars_ta.ta.overlap import SMA as ts_SMA # noqa from polars_ta.ta.overlap import TEMA as ts_TEMA # noqa from polars_ta.ta.overlap import TRIMA as ts_TRIMA # noqa from polars_ta.ta.overlap import WMA as ts_WMA # noqa from polars_ta.ta.price import AVGPRICE # noqa from polars_ta.ta.price import MEDPRICE # noqa from polars_ta.ta.price import TYPPRICE # noqa from polars_ta.ta.price import WCLPRICE # noqa from polars_ta.ta.statistic import BETA as ts_BETA # noqa from polars_ta.ta.statistic import CORREL as ts_CORREL # noqa from polars_ta.ta.statistic import LINEARREG as ts_LINEARREG # noqa from polars_ta.ta.statistic import LINEARREG_ANGLE as ts_LINEARREG_ANGLE # noqa from polars_ta.ta.statistic import LINEARREG_INTERCEPT as ts_LINEARREG_INTERCEPT # noqa from polars_ta.ta.statistic import LINEARREG_SLOPE as ts_LINEARREG_SLOPE # noqa from polars_ta.ta.statistic import STDDEV as ts_STDDEV # noqa from polars_ta.ta.statistic import TSF as ts_TSF # noqa from polars_ta.ta.statistic import VAR as ts_VAR # noqa from polars_ta.ta.transform import ACOS # noqa from polars_ta.ta.transform import ASIN # noqa from polars_ta.ta.transform import ATAN # noqa from polars_ta.ta.transform import CEIL # noqa from polars_ta.ta.transform import COS # noqa from polars_ta.ta.transform import COSH # noqa from polars_ta.ta.transform import EXP # noqa from polars_ta.ta.transform import FLOOR # noqa from polars_ta.ta.transform import LN # noqa from polars_ta.ta.transform import LOG10 # noqa from polars_ta.ta.transform import SIN # noqa from polars_ta.ta.transform import SINH # noqa from polars_ta.ta.transform import SQRT # noqa from polars_ta.ta.transform import TAN # noqa from polars_ta.ta.transform import TANH # noqa from polars_ta.ta.volatility import ATR as ts_ATR # noqa from polars_ta.ta.volatility import NATR as ts_NATR # noqa from polars_ta.ta.volatility import TRANGE as ts_TRANGE # noqa from polars_ta.ta.volume import AD as ts_AD # noqa from polars_ta.ta.volume import ADOSC as ts_ADOSC # noqa from polars_ta.ta.volume import OBV as ts_OBV # noqa ================================================ FILE: polars_ta/prefix/talib.py ================================================ # this code is auto generated by tools/prefix_talib.py from polars_ta.talib import HT_DCPERIOD as ts_HT_DCPERIOD # noqa from polars_ta.talib import HT_DCPHASE as ts_HT_DCPHASE # noqa from polars_ta.talib import HT_PHASOR as ts_HT_PHASOR # noqa from polars_ta.talib import HT_SINE as ts_HT_SINE # noqa from polars_ta.talib import HT_TRENDMODE as ts_HT_TRENDMODE # noqa from polars_ta.talib import ADD # noqa from polars_ta.talib import DIV # noqa from polars_ta.talib import MAX # noqa from polars_ta.talib import MAXINDEX # noqa from polars_ta.talib import MIN # noqa from polars_ta.talib import MININDEX # noqa from polars_ta.talib import MINMAX # noqa from polars_ta.talib import MINMAXINDEX # noqa from polars_ta.talib import MULT # noqa from polars_ta.talib import SUB # noqa from polars_ta.talib import SUM # noqa from polars_ta.talib import ACOS # noqa from polars_ta.talib import ASIN # noqa from polars_ta.talib import ATAN # noqa from polars_ta.talib import CEIL # noqa from polars_ta.talib import COS # noqa from polars_ta.talib import COSH # noqa from polars_ta.talib import EXP # noqa from polars_ta.talib import FLOOR # noqa from polars_ta.talib import LN # noqa from polars_ta.talib import LOG10 # noqa from polars_ta.talib import SIN # noqa from polars_ta.talib import SINH # noqa from polars_ta.talib import SQRT # noqa from polars_ta.talib import TAN # noqa from polars_ta.talib import TANH # noqa from polars_ta.talib import ADX as ts_ADX # noqa from polars_ta.talib import ADXR as ts_ADXR # noqa from polars_ta.talib import APO as ts_APO # noqa from polars_ta.talib import AROON as ts_AROON # noqa from polars_ta.talib import AROONOSC as ts_AROONOSC # noqa from polars_ta.talib import BOP as ts_BOP # noqa from polars_ta.talib import CCI as ts_CCI # noqa from polars_ta.talib import CMO as ts_CMO # noqa from polars_ta.talib import DX as ts_DX # noqa from polars_ta.talib import MACD as ts_MACD # noqa from polars_ta.talib import MACDEXT as ts_MACDEXT # noqa from polars_ta.talib import MACDFIX as ts_MACDFIX # noqa from polars_ta.talib import MFI as ts_MFI # noqa from polars_ta.talib import MINUS_DI as ts_MINUS_DI # noqa from polars_ta.talib import MINUS_DM as ts_MINUS_DM # noqa from polars_ta.talib import MOM as ts_MOM # noqa from polars_ta.talib import PLUS_DI as ts_PLUS_DI # noqa from polars_ta.talib import PLUS_DM as ts_PLUS_DM # noqa from polars_ta.talib import PPO as ts_PPO # noqa from polars_ta.talib import ROC as ts_ROC # noqa from polars_ta.talib import ROCP as ts_ROCP # noqa from polars_ta.talib import ROCR as ts_ROCR # noqa from polars_ta.talib import ROCR100 as ts_ROCR100 # noqa from polars_ta.talib import RSI as ts_RSI # noqa from polars_ta.talib import STOCH as ts_STOCH # noqa from polars_ta.talib import STOCHF as ts_STOCHF # noqa from polars_ta.talib import STOCHRSI as ts_STOCHRSI # noqa from polars_ta.talib import TRIX as ts_TRIX # noqa from polars_ta.talib import ULTOSC as ts_ULTOSC # noqa from polars_ta.talib import WILLR as ts_WILLR # noqa from polars_ta.talib import BBANDS as ts_BBANDS # noqa from polars_ta.talib import DEMA as ts_DEMA # noqa from polars_ta.talib import EMA as ts_EMA # noqa from polars_ta.talib import HT_TRENDLINE as ts_HT_TRENDLINE # noqa from polars_ta.talib import KAMA as ts_KAMA # noqa from polars_ta.talib import MA as ts_MA # noqa from polars_ta.talib import MAMA as ts_MAMA # noqa from polars_ta.talib import MAVP as ts_MAVP # noqa from polars_ta.talib import MIDPOINT as ts_MIDPOINT # noqa from polars_ta.talib import MIDPRICE as ts_MIDPRICE # noqa from polars_ta.talib import SAR as ts_SAR # noqa from polars_ta.talib import SAREXT as ts_SAREXT # noqa from polars_ta.talib import SMA as ts_SMA # noqa from polars_ta.talib import T3 as ts_T3 # noqa from polars_ta.talib import TEMA as ts_TEMA # noqa from polars_ta.talib import TRIMA as ts_TRIMA # noqa from polars_ta.talib import WMA as ts_WMA # noqa from polars_ta.talib import CDL2CROWS as ts_CDL2CROWS # noqa from polars_ta.talib import CDL3BLACKCROWS as ts_CDL3BLACKCROWS # noqa from polars_ta.talib import CDL3INSIDE as ts_CDL3INSIDE # noqa from polars_ta.talib import CDL3LINESTRIKE as ts_CDL3LINESTRIKE # noqa from polars_ta.talib import CDL3OUTSIDE as ts_CDL3OUTSIDE # noqa from polars_ta.talib import CDL3STARSINSOUTH as ts_CDL3STARSINSOUTH # noqa from polars_ta.talib import CDL3WHITESOLDIERS as ts_CDL3WHITESOLDIERS # noqa from polars_ta.talib import CDLABANDONEDBABY as ts_CDLABANDONEDBABY # noqa from polars_ta.talib import CDLADVANCEBLOCK as ts_CDLADVANCEBLOCK # noqa from polars_ta.talib import CDLBELTHOLD as ts_CDLBELTHOLD # noqa from polars_ta.talib import CDLBREAKAWAY as ts_CDLBREAKAWAY # noqa from polars_ta.talib import CDLCLOSINGMARUBOZU as ts_CDLCLOSINGMARUBOZU # noqa from polars_ta.talib import CDLCONCEALBABYSWALL as ts_CDLCONCEALBABYSWALL # noqa from polars_ta.talib import CDLCOUNTERATTACK as ts_CDLCOUNTERATTACK # noqa from polars_ta.talib import CDLDARKCLOUDCOVER as ts_CDLDARKCLOUDCOVER # noqa from polars_ta.talib import CDLDOJI as ts_CDLDOJI # noqa from polars_ta.talib import CDLDOJISTAR as ts_CDLDOJISTAR # noqa from polars_ta.talib import CDLDRAGONFLYDOJI as ts_CDLDRAGONFLYDOJI # noqa from polars_ta.talib import CDLENGULFING as ts_CDLENGULFING # noqa from polars_ta.talib import CDLEVENINGDOJISTAR as ts_CDLEVENINGDOJISTAR # noqa from polars_ta.talib import CDLEVENINGSTAR as ts_CDLEVENINGSTAR # noqa from polars_ta.talib import CDLGAPSIDESIDEWHITE as ts_CDLGAPSIDESIDEWHITE # noqa from polars_ta.talib import CDLGRAVESTONEDOJI as ts_CDLGRAVESTONEDOJI # noqa from polars_ta.talib import CDLHAMMER as ts_CDLHAMMER # noqa from polars_ta.talib import CDLHANGINGMAN as ts_CDLHANGINGMAN # noqa from polars_ta.talib import CDLHARAMI as ts_CDLHARAMI # noqa from polars_ta.talib import CDLHARAMICROSS as ts_CDLHARAMICROSS # noqa from polars_ta.talib import CDLHIGHWAVE as ts_CDLHIGHWAVE # noqa from polars_ta.talib import CDLHIKKAKE as ts_CDLHIKKAKE # noqa from polars_ta.talib import CDLHIKKAKEMOD as ts_CDLHIKKAKEMOD # noqa from polars_ta.talib import CDLHOMINGPIGEON as ts_CDLHOMINGPIGEON # noqa from polars_ta.talib import CDLIDENTICAL3CROWS as ts_CDLIDENTICAL3CROWS # noqa from polars_ta.talib import CDLINNECK as ts_CDLINNECK # noqa from polars_ta.talib import CDLINVERTEDHAMMER as ts_CDLINVERTEDHAMMER # noqa from polars_ta.talib import CDLKICKING as ts_CDLKICKING # noqa from polars_ta.talib import CDLKICKINGBYLENGTH as ts_CDLKICKINGBYLENGTH # noqa from polars_ta.talib import CDLLADDERBOTTOM as ts_CDLLADDERBOTTOM # noqa from polars_ta.talib import CDLLONGLEGGEDDOJI as ts_CDLLONGLEGGEDDOJI # noqa from polars_ta.talib import CDLLONGLINE as ts_CDLLONGLINE # noqa from polars_ta.talib import CDLMARUBOZU as ts_CDLMARUBOZU # noqa from polars_ta.talib import CDLMATCHINGLOW as ts_CDLMATCHINGLOW # noqa from polars_ta.talib import CDLMATHOLD as ts_CDLMATHOLD # noqa from polars_ta.talib import CDLMORNINGDOJISTAR as ts_CDLMORNINGDOJISTAR # noqa from polars_ta.talib import CDLMORNINGSTAR as ts_CDLMORNINGSTAR # noqa from polars_ta.talib import CDLONNECK as ts_CDLONNECK # noqa from polars_ta.talib import CDLPIERCING as ts_CDLPIERCING # noqa from polars_ta.talib import CDLRICKSHAWMAN as ts_CDLRICKSHAWMAN # noqa from polars_ta.talib import CDLRISEFALL3METHODS as ts_CDLRISEFALL3METHODS # noqa from polars_ta.talib import CDLSEPARATINGLINES as ts_CDLSEPARATINGLINES # noqa from polars_ta.talib import CDLSHOOTINGSTAR as ts_CDLSHOOTINGSTAR # noqa from polars_ta.talib import CDLSHORTLINE as ts_CDLSHORTLINE # noqa from polars_ta.talib import CDLSPINNINGTOP as ts_CDLSPINNINGTOP # noqa from polars_ta.talib import CDLSTALLEDPATTERN as ts_CDLSTALLEDPATTERN # noqa from polars_ta.talib import CDLSTICKSANDWICH as ts_CDLSTICKSANDWICH # noqa from polars_ta.talib import CDLTAKURI as ts_CDLTAKURI # noqa from polars_ta.talib import CDLTASUKIGAP as ts_CDLTASUKIGAP # noqa from polars_ta.talib import CDLTHRUSTING as ts_CDLTHRUSTING # noqa from polars_ta.talib import CDLTRISTAR as ts_CDLTRISTAR # noqa from polars_ta.talib import CDLUNIQUE3RIVER as ts_CDLUNIQUE3RIVER # noqa from polars_ta.talib import CDLUPSIDEGAP2CROWS as ts_CDLUPSIDEGAP2CROWS # noqa from polars_ta.talib import CDLXSIDEGAP3METHODS as ts_CDLXSIDEGAP3METHODS # noqa from polars_ta.talib import AVGPRICE # noqa from polars_ta.talib import MEDPRICE # noqa from polars_ta.talib import TYPPRICE # noqa from polars_ta.talib import WCLPRICE # noqa from polars_ta.talib import BETA as ts_BETA # noqa from polars_ta.talib import CORREL as ts_CORREL # noqa from polars_ta.talib import LINEARREG as ts_LINEARREG # noqa from polars_ta.talib import LINEARREG_ANGLE as ts_LINEARREG_ANGLE # noqa from polars_ta.talib import LINEARREG_INTERCEPT as ts_LINEARREG_INTERCEPT # noqa from polars_ta.talib import LINEARREG_SLOPE as ts_LINEARREG_SLOPE # noqa from polars_ta.talib import STDDEV as ts_STDDEV # noqa from polars_ta.talib import TSF as ts_TSF # noqa from polars_ta.talib import VAR as ts_VAR # noqa from polars_ta.talib import ATR as ts_ATR # noqa from polars_ta.talib import NATR as ts_NATR # noqa from polars_ta.talib import TRANGE as ts_TRANGE # noqa from polars_ta.talib import AD as ts_AD # noqa from polars_ta.talib import ADOSC as ts_ADOSC # noqa from polars_ta.talib import OBV as ts_OBV # noqa ================================================ FILE: polars_ta/prefix/tdx.py ================================================ # this code is auto generated by tools/prefix_tdx.py from polars_ta.tdx.arithmetic import ABS # noqa from polars_ta.tdx.arithmetic import ACOS # noqa from polars_ta.tdx.arithmetic import ADD # noqa from polars_ta.tdx.arithmetic import ASIN # noqa from polars_ta.tdx.arithmetic import ATAN # noqa from polars_ta.tdx.arithmetic import BETWEEN # noqa from polars_ta.tdx.arithmetic import CEILING # noqa from polars_ta.tdx.arithmetic import COS # noqa from polars_ta.tdx.arithmetic import EXP # noqa from polars_ta.tdx.arithmetic import FLOOR # noqa from polars_ta.tdx.arithmetic import FRACPART # noqa from polars_ta.tdx.arithmetic import LN # noqa from polars_ta.tdx.arithmetic import LOG # noqa from polars_ta.tdx.arithmetic import MAX # noqa from polars_ta.tdx.arithmetic import MIN # noqa from polars_ta.tdx.arithmetic import MOD # noqa from polars_ta.tdx.arithmetic import POW # noqa from polars_ta.tdx.arithmetic import REVERSE # noqa from polars_ta.tdx.arithmetic import ROUND # noqa from polars_ta.tdx.arithmetic import ROUND2 # noqa from polars_ta.tdx.arithmetic import SGN # noqa from polars_ta.tdx.arithmetic import SIGN # noqa from polars_ta.tdx.arithmetic import SIN # noqa from polars_ta.tdx.arithmetic import SQRT # noqa from polars_ta.tdx.arithmetic import SUB # noqa from polars_ta.tdx.arithmetic import TAN # noqa from polars_ta.tdx.choice import IF # noqa from polars_ta.tdx.choice import IFF # noqa from polars_ta.tdx.choice import IFN # noqa from polars_ta.tdx.choice import VALUEWHEN as ts_VALUEWHEN # noqa from polars_ta.tdx.energy import BRAR_AR as ts_BRAR_AR # noqa from polars_ta.tdx.energy import BRAR_BR as ts_BRAR_BR # noqa from polars_ta.tdx.energy import CR as ts_CR # noqa from polars_ta.tdx.energy import MASS as ts_MASS # noqa from polars_ta.tdx.energy import PSY as ts_PSY # noqa from polars_ta.tdx.logical import ALL as ts_ALL # noqa from polars_ta.tdx.logical import ANY as ts_ANY # noqa from polars_ta.tdx.logical import CROSS as ts_CROSS # noqa from polars_ta.tdx.logical import DOWNNDAY as ts_DOWNNDAY # noqa from polars_ta.tdx.logical import EVERY as ts_EVERY # noqa from polars_ta.tdx.logical import EXIST as ts_EXIST # noqa from polars_ta.tdx.logical import EXISTR as ts_EXISTR # noqa from polars_ta.tdx.logical import LAST as ts_LAST # noqa from polars_ta.tdx.logical import LONGCROSS as ts_LONGCROSS # noqa from polars_ta.tdx.logical import NDAY as ts_NDAY # noqa from polars_ta.tdx.logical import NOT # noqa from polars_ta.tdx.logical import UPNDAY as ts_UPNDAY # noqa from polars_ta.tdx.moving_average import BBI as ts_BBI # noqa from polars_ta.tdx.over_bought_over_sold import ATR as ts_ATR # noqa from polars_ta.tdx.over_bought_over_sold import BIAS as ts_BIAS # noqa from polars_ta.tdx.over_bought_over_sold import CCI as ts_CCI # noqa from polars_ta.tdx.over_bought_over_sold import KDJ as ts_KDJ # noqa from polars_ta.tdx.over_bought_over_sold import MFI as ts_MFI # noqa from polars_ta.tdx.over_bought_over_sold import MTM as ts_MTM # noqa from polars_ta.tdx.over_bought_over_sold import RSI as ts_RSI # noqa from polars_ta.tdx.over_bought_over_sold import RSV as ts_RSV # noqa from polars_ta.tdx.pattern import ts_WINNER_COST # noqa from polars_ta.tdx.pattern_feature import 仙人指路 as ts_仙人指路 # noqa from polars_ta.tdx.pattern_feature import 低开大阳线 as ts_低开大阳线 # noqa from polars_ta.tdx.pattern_feature import 低点搜寻 as ts_低点搜寻 # noqa from polars_ta.tdx.pattern_feature import 出水芙蓉 as ts_出水芙蓉 # noqa from polars_ta.tdx.pattern_feature import 出水芙蓉II as ts_出水芙蓉II # noqa from polars_ta.tdx.pattern_feature import 剑 as ts_剑 # noqa from polars_ta.tdx.pattern_feature import 单阳不破选股 as ts_单阳不破选股 # noqa from polars_ta.tdx.pattern_feature import 四串阳 as ts_四串阳 # noqa from polars_ta.tdx.pattern_feature import 四串阴 as ts_四串阴 # noqa from polars_ta.tdx.pattern_feature import 回补跳空向上缺口 as ts_回补跳空向上缺口 # noqa from polars_ta.tdx.pattern_feature import 均线多头排列 as ts_均线多头排列 # noqa from polars_ta.tdx.pattern_feature import 均线空头排列 as ts_均线空头排列 # noqa from polars_ta.tdx.pattern_feature import 天量法则 as ts_天量法则 # noqa from polars_ta.tdx.pattern_feature import 强势整理 as ts_强势整理 # noqa from polars_ta.tdx.pattern_feature import 揉搓线 as ts_揉搓线 # noqa from polars_ta.tdx.pattern_feature import 早晨之星 as ts_早晨之星 # noqa from polars_ta.tdx.pattern_feature import 旭日初升 as ts_旭日初升 # noqa from polars_ta.tdx.pattern_feature import 突破 as ts_突破 # noqa from polars_ta.tdx.pattern_feature import 老鸭头 as ts_老鸭头 # noqa from polars_ta.tdx.pattern_feature import 蜻蜓点水 as ts_蜻蜓点水 # noqa from polars_ta.tdx.pattern_feature import 跳空缺口选股 as ts_跳空缺口选股 # noqa from polars_ta.tdx.pattern_feature import 近日创历史新低 as ts_近日创历史新低 # noqa from polars_ta.tdx.pattern_feature import 近日创历史新高 as ts_近日创历史新高 # noqa from polars_ta.tdx.pattern_feature import 高开大阴线 as ts_高开大阴线 # noqa from polars_ta.tdx.pattern_feature import 鸳鸯底 as ts_鸳鸯底 # noqa from polars_ta.tdx.pressure_support import BOLL as ts_BOLL # noqa from polars_ta.tdx.pressure_support import BOLL_M as ts_BOLL_M # noqa from polars_ta.tdx.reference import BARSLAST as ts_BARSLAST # noqa from polars_ta.tdx.reference import BARSLASTCOUNT as ts_BARSLASTCOUNT # noqa from polars_ta.tdx.reference import BARSSINCE as ts_BARSSINCE # noqa from polars_ta.tdx.reference import BARSSINCEN as ts_BARSSINCEN # noqa from polars_ta.tdx.reference import COUNT as ts_COUNT # noqa from polars_ta.tdx.reference import CUMSUM as ts_CUMSUM # noqa from polars_ta.tdx.reference import DIFF as ts_DIFF # noqa from polars_ta.tdx.reference import DMA as ts_DMA # noqa from polars_ta.tdx.reference import EMA as ts_EMA # noqa from polars_ta.tdx.reference import EXPMA as ts_EXPMA # noqa from polars_ta.tdx.reference import EXPMEMA as ts_EXPMEMA # noqa from polars_ta.tdx.reference import FILTER as ts_FILTER # noqa from polars_ta.tdx.reference import HHV as ts_HHV # noqa from polars_ta.tdx.reference import HHVBARS as ts_HHVBARS # noqa from polars_ta.tdx.reference import HOD as ts_HOD # noqa from polars_ta.tdx.reference import LLV as ts_LLV # noqa from polars_ta.tdx.reference import LLVBARS as ts_LLVBARS # noqa from polars_ta.tdx.reference import LOD as ts_LOD # noqa from polars_ta.tdx.reference import LOWRANGE # noqa from polars_ta.tdx.reference import MA as ts_MA # noqa from polars_ta.tdx.reference import MAX # noqa from polars_ta.tdx.reference import MEMA as ts_MEMA # noqa from polars_ta.tdx.reference import MIN # noqa from polars_ta.tdx.reference import MULAR as ts_MULAR # noqa from polars_ta.tdx.reference import RANGE # noqa from polars_ta.tdx.reference import REF as ts_REF # noqa from polars_ta.tdx.reference import REFX as ts_REFX # noqa from polars_ta.tdx.reference import SMA_CN as ts_SMA_CN # noqa from polars_ta.tdx.reference import SUM as ts_SUM # noqa from polars_ta.tdx.reference import SUMIF as ts_SUMIF # noqa from polars_ta.tdx.reference import TMA as ts_TMA # noqa from polars_ta.tdx.reference import TR as ts_TR # noqa from polars_ta.tdx.reference import WMA as ts_WMA # noqa from polars_ta.tdx.statistic import AVEDEV as ts_AVEDEV # noqa from polars_ta.tdx.statistic import COVAR as ts_COVAR # noqa from polars_ta.tdx.statistic import DEVSQ as ts_DEVSQ # noqa from polars_ta.tdx.statistic import RELATE as ts_RELATE # noqa from polars_ta.tdx.statistic import SLOPE as ts_SLOPE # noqa from polars_ta.tdx.statistic import STD as ts_STD # noqa from polars_ta.tdx.statistic import STDDEV as ts_STDDEV # noqa from polars_ta.tdx.statistic import STDP as ts_STDP # noqa from polars_ta.tdx.statistic import VAR as ts_VAR # noqa from polars_ta.tdx.statistic import VARP as ts_VARP # noqa from polars_ta.tdx.statistic import ts_up_stat # noqa from polars_ta.tdx.times import FROMOPEN # noqa from polars_ta.tdx.times import FROMOPEN_1 # noqa from polars_ta.tdx.trend import ADX as ts_ADX # noqa from polars_ta.tdx.trend import ADXR as ts_ADXR # noqa from polars_ta.tdx.trend import DPO as ts_DPO # noqa from polars_ta.tdx.trend import EMV as ts_EMV # noqa from polars_ta.tdx.trend import MINUS_DI as ts_MINUS_DI # noqa from polars_ta.tdx.trend import MINUS_DM as ts_MINUS_DM # noqa from polars_ta.tdx.trend import PLUS_DI as ts_PLUS_DI # noqa from polars_ta.tdx.trend import PLUS_DM as ts_PLUS_DM # noqa from polars_ta.tdx.trend_feature import N天内出现以涨停收盘 as ts_N天内出现以涨停收盘 # noqa from polars_ta.tdx.trend_feature import N天内出现涨停 as ts_N天内出现涨停 # noqa from polars_ta.tdx.trend_feature import N天内有跳空向上缺口 as ts_N天内有跳空向上缺口 # noqa from polars_ta.tdx.trend_feature import N天内经常涨停 as ts_N天内经常涨停 # noqa from polars_ta.tdx.trend_feature import N日内上涨多于下跌 as ts_N日内上涨多于下跌 # noqa from polars_ta.tdx.trend_feature import N日内下跌多于上涨 as ts_N日内下跌多于上涨 # noqa from polars_ta.tdx.trend_feature import N日内创新低 as ts_N日内创新低 # noqa from polars_ta.tdx.trend_feature import N日内创新高 as ts_N日内创新高 # noqa from polars_ta.tdx.trend_feature import N日内阳线多于阴线 as ts_N日内阳线多于阴线 # noqa from polars_ta.tdx.trend_feature import N日内阴线多于阳线 as ts_N日内阴线多于阳线 # noqa from polars_ta.tdx.trend_feature import 下跌多日再放量上涨 as ts_下跌多日再放量上涨 # noqa from polars_ta.tdx.trend_feature import 价量渐低后阳包阴 as ts_价量渐低后阳包阴 # noqa from polars_ta.tdx.trend_feature import 单日放量 as ts_单日放量 # noqa from polars_ta.tdx.trend_feature import 小步碎阳 as ts_小步碎阳 # noqa from polars_ta.tdx.trend_feature import 平台整理 as ts_平台整理 # noqa from polars_ta.tdx.trend_feature import 拉升后多日调整 as ts_拉升后多日调整 # noqa from polars_ta.tdx.trend_feature import 持续放量 as ts_持续放量 # noqa from polars_ta.tdx.trend_feature import 持续缩量 as ts_持续缩量 # noqa from polars_ta.tdx.trend_feature import 放量上攻 as ts_放量上攻 # noqa from polars_ta.tdx.trend_feature import 昨日底部十字星 as ts_昨日底部十字星 # noqa from polars_ta.tdx.trend_feature import 温和放量上攻 as ts_温和放量上攻 # noqa from polars_ta.tdx.trend_feature import 突然放量 as ts_突然放量 # noqa from polars_ta.tdx.trend_feature import 突破长期盘整 as ts_突破长期盘整 # noqa from polars_ta.tdx.trend_feature import 跳空高开或低开 as ts_跳空高开或低开 # noqa from polars_ta.tdx.trend_feature import 连续N天收阳线 as ts_连续N天收阳线 # noqa from polars_ta.tdx.trend_feature import 连续N天收阴线 as ts_连续N天收阴线 # noqa from polars_ta.tdx.trend_feature import 间隔放量 as ts_间隔放量 # noqa from polars_ta.tdx.trend_feature import 阶段放量 as ts_阶段放量 # noqa from polars_ta.tdx.trend_feature import 阶段缩量 as ts_阶段缩量 # noqa from polars_ta.tdx.volume import OBV as ts_OBV # noqa from polars_ta.tdx.volume import VR as ts_VR # noqa ================================================ FILE: polars_ta/prefix/vec.py ================================================ # this code is auto generated by tools/prefix_vec.py from polars_ta.wq.vector import vec_avg as cs_vec_avg # noqa from polars_ta.wq.vector import vec_choose as cs_vec_choose # noqa from polars_ta.wq.vector import vec_count as cs_vec_count # noqa from polars_ta.wq.vector import vec_ir as cs_vec_ir # noqa from polars_ta.wq.vector import vec_kurtosis as cs_vec_kurtosis # noqa from polars_ta.wq.vector import vec_l2_norm as cs_vec_l2_norm # noqa from polars_ta.wq.vector import vec_max as cs_vec_max # noqa from polars_ta.wq.vector import vec_median as cs_vec_median # noqa from polars_ta.wq.vector import vec_min as cs_vec_min # noqa from polars_ta.wq.vector import vec_norm as cs_vec_norm # noqa from polars_ta.wq.vector import vec_percentage as cs_vec_percentage # noqa from polars_ta.wq.vector import vec_powersum as cs_vec_powersum # noqa from polars_ta.wq.vector import vec_range as cs_vec_range # noqa from polars_ta.wq.vector import vec_skewness as cs_vec_skewness # noqa from polars_ta.wq.vector import vec_stddev as cs_vec_stddev # noqa from polars_ta.wq.vector import vec_sum as cs_vec_sum # noqa ================================================ FILE: polars_ta/prefix/wq.py ================================================ from polars_ta.wq import * # noqa ================================================ FILE: polars_ta/reports/__init__.py ================================================ from polars_ta.reports.cicc import * # noqa ================================================ FILE: polars_ta/reports/cicc.py ================================================ from polars import Expr from polars_ta.wq import ts_corr, ts_zscore, ts_std_dev, ts_regression_slope def ts_RSRS_R2(high: Expr, low: Expr, n: int = 18, m: int = 600) -> Expr: """光大RSRS指标,中金QRS指标,R^2调整 References ---------- 中金:金融工程视角下的技术择时艺术 """ a = ts_corr(high, low, n) return ts_zscore(ts_std_dev(high, n) / ts_std_dev(low, n) * a, m) * (a ** 2) def ts_RSRS(high: Expr, low: Expr, n: int = 18, m: int = 600) -> Expr: """光大RSRS指标,中金QRS指标 References ---------- 中金:金融工程视角下的技术择时艺术 """ return ts_zscore(ts_regression_slope(high, low, n), m) ================================================ FILE: polars_ta/ta/README.md ================================================ # polars_ta.ta 1. Files in this folder mimic `talib`, and implement `polars` versions for the same functions 2. Since we reduce the functino calls between `Python` and `C` code, it should be faster than `talib`. 3. We first try to import from `ta`, then from `wq`, and only implement the function if it is not available. 4. When there is a circular dependency, we use `polars` instead. 1. 本文件夹中模仿`talib`,实现同名函数的`polars`版 2. 由于减少了python与c来回调用,理论上比直接调用`talib`快 3. 优先从`ta`中导入,然后从`wq`中导入,没有的才实现 4. 出现循环依赖时,使用`polars` ================================================ FILE: polars_ta/ta/__init__.py ================================================ from polars_ta.ta.momentum import * # noqa from polars_ta.ta.operators import * # noqa from polars_ta.ta.overlap import * # noqa from polars_ta.ta.price import * # noqa from polars_ta.ta.statistic import * # noqa from polars_ta.ta.transform import * # noqa from polars_ta.ta.volatility import * # noqa from polars_ta.ta.volume import * # noqa ================================================ FILE: polars_ta/ta/momentum.py ================================================ from polars import Expr, when, struct from polars_ta import TA_EPSILON from polars_ta.ta.operators import MAX from polars_ta.ta.operators import MIN from polars_ta.ta.overlap import EMA from polars_ta.ta.overlap import RMA from polars_ta.ta.overlap import SMA from polars_ta.wq.arithmetic import max_ from polars_ta.wq.time_series import ts_delta, ts_arg_max, ts_arg_min from polars_ta.wq.time_series import ts_returns def APO(close: Expr, fastperiod: int = 12, slowperiod: int = 26, matype: int = 0) -> Expr: if matype == 0: return SMA(close, fastperiod) - SMA(close, slowperiod) else: return EMA(close, fastperiod) - EMA(close, slowperiod) def AROON(high: Expr, low: Expr, timeperiod: int = 14) -> Expr: """ 下轨:(N-LLVBARS(L,N))/N*100,COLORGREEN; 上轨:(N-HHVBARS(H,N))/N*100,COLORRED; Notes ----- You cannot use pd.Series.rolling().arg_max() with reverse order, which leads to a larger result when there are two or more high points so we don't use pandas pd.Series.rolling().arg_max()没有逆序,导致出现两个及以上最高点时,结果偏大 """ aroondown = 1 - ts_arg_min(low, timeperiod, reverse=True) / timeperiod aroonup = 1 - ts_arg_max(high, timeperiod, reverse=True) / timeperiod return struct(aroondown=aroondown, aroonup=aroonup) def MACD(close: Expr, fastperiod: int = 12, slowperiod: int = 26, signalperiod: int = 9) -> Expr: """MACD Notes ----- When counting how many data we have we refer to `slowperiod`, while `talib.MACD` refers to `fastperiod` talib.MACD有效数据按fastperiod,而本项目按slowperiod """ macd = EMA(close, fastperiod) - EMA(close, slowperiod) macdsignal = EMA(macd, signalperiod) macdhist = (macd - macdsignal) # * 2 # 中国版多了乘2 return struct(macd=macd, macdsignal=macdsignal, macdhist=macdhist) def MOM(close: Expr, timeperiod: int = 10) -> Expr: """MOM = (price - prevPrice) [Momentum] References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_MOM.c#L200 """ return ts_delta(close, timeperiod) def PPO(close: Expr, fastperiod: int = 12, slowperiod: int = 26, matype: int = 0) -> Expr: if matype == 0: return SMA(close, fastperiod) / SMA(close, slowperiod) - 1 else: return EMA(close, fastperiod) / EMA(close, slowperiod) - 1 def ROC(close: Expr, timeperiod: int = 10) -> Expr: """ROC = ((price/prevPrice)-1)*100 [Rate of change] References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_ROC.c#L200 """ return ROCP(close, timeperiod) * 100 def ROCP(close: Expr, timeperiod: int = 10) -> Expr: """ROCP = (price-prevPrice)/prevPrice [Rate of change Percentage] References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_ROCP.c#L202 """ return ts_returns(close, timeperiod) def ROCR(close: Expr, timeperiod: int = 10) -> Expr: """ROCR = (price/prevPrice) [Rate of change ratio] References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_ROCR.c#L203 """ return close / close.shift(timeperiod) def ROCR100(close: Expr, timeperiod: int = 10) -> Expr: """ROCR100 = (price/prevPrice)*100 [Rate of change ratio 100 Scale] References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_ROCR100.c#L203 """ return ROCR(close, timeperiod) * 100 def RSI(close: Expr, timeperiod: int = 14) -> Expr: dif = close.diff().fill_null(0) return RMA(max_(dif, 0), timeperiod) / (RMA(dif.abs(), timeperiod) + TA_EPSILON) # * 100 def STOCHF(high: Expr, low: Expr, close: Expr, fastk_period: int = 5, fastd_period: int = 3) -> Expr: fastk = RSV(high, low, close, fastk_period) fastd = SMA(fastk, fastd_period) return struct(fastk=fastk, fastd=fastd) def TRIX(close: Expr, timeperiod: int = 30) -> Expr: EMA1 = EMA(close, timeperiod) EMA2 = EMA(EMA1, timeperiod) EMA3 = EMA(EMA2, timeperiod) return ROCP(EMA3, 1) def RSV(high: Expr, low: Expr, close: Expr, timeperiod: int = 5) -> Expr: """RSV=STOCHF_FASTK (Today's Close - LowestLow) FASTK(Kperiod) = --------------------------- * 100 (HighestHigh - LowestLow) Notes ----- RSV = STOCHF_FASTK。没乘以100 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_STOCHF.c#L279 """ a = MAX(high, timeperiod) b = MIN(low, timeperiod) # return (close - b) / (a - b + TA_EPSILON) return when(a != b).then((close - b) / (a - b)).otherwise(0) def WILLR(high: Expr, low: Expr, close: Expr, timeperiod: int = 14) -> Expr: """威廉指标 Notes ----- WILLR=1-RSV References ---------- - https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_WILLR.c#L294 - https://www.investopedia.com/terms/w/williamsr.asp - https://school.stockcharts.com/doku.php?id=technical_indicators:williams_r """ a = MAX(high, timeperiod) b = MIN(low, timeperiod) # return (a - close) / (a - b + TA_EPSILON) return when(a != b).then((a - close) / (a - b)).otherwise(0) ================================================ FILE: polars_ta/ta/operators.py ================================================ """ 通过`import`直接导入或更名的函数 ```python from polars_ta.wq.arithmetic import add as ADD # noqa from polars_ta.wq.arithmetic import divide as DIV # noqa from polars_ta.wq.arithmetic import multiply as MUL # noqa from polars_ta.wq.arithmetic import subtract as SUB # noqa from polars_ta.wq.time_series import ts_max as MAX # noqa from polars_ta.wq.time_series import ts_min as MIN # noqa from polars_ta.wq.time_series import ts_sum as SUM # noqa ``` """ from polars import Expr from polars_ta.wq.arithmetic import add as ADD # noqa from polars_ta.wq.arithmetic import divide as DIV # noqa from polars_ta.wq.arithmetic import multiply as MUL # noqa from polars_ta.wq.arithmetic import subtract as SUB # noqa from polars_ta.wq.time_series import ts_arg_max from polars_ta.wq.time_series import ts_arg_min from polars_ta.wq.time_series import ts_max as MAX # noqa from polars_ta.wq.time_series import ts_min as MIN # noqa from polars_ta.wq.time_series import ts_sum as SUM # noqa def MAXINDEX(close: Expr, timeperiod: int = 30) -> Expr: """ Notes ----- Comparing to `ts_arg_max` this also marks the abs. position of the max value 与ts_arg_max的区别是,标记了每个区间最大值的绝对位置,可用来画图标记 Examples -------- ```python from polars_ta.ta import MAXINDEX as ta_MAXINDEX from polars_ta.talib import MAXINDEX as talib_MAXINDEX from polars_ta.wq import ts_arg_max df = pl.DataFrame({ 'a': [6, 2, 8, 5, 9, 4], }).with_columns( out1=ts_arg_max(pl.col('a'), 3), out2=ta_MAXINDEX(pl.col('a'), 3), out3=talib_MAXINDEX(pl.col('a'), 3), ) shape: (6, 4) ┌─────┬──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ u16 ┆ i64 ┆ i32 │ ╞═════╪══════╪══════╪══════╡ │ 6 ┆ null ┆ null ┆ 0 │ │ 2 ┆ null ┆ null ┆ 0 │ │ 8 ┆ 0 ┆ 2 ┆ 2 │ │ 5 ┆ 1 ┆ 2 ┆ 2 │ │ 9 ┆ 0 ┆ 4 ┆ 4 │ │ 4 ┆ 1 ┆ 4 ┆ 4 │ └─────┴──────┴──────┴──────┘ ``` """ a = close.cum_count() b = ts_arg_max(close, timeperiod) return a - b - 1 def MININDEX(close: Expr, timeperiod: int = 30) -> Expr: """ Examples -------- ```python from polars_ta.ta import MININDEX as ta_MININDEX from polars_ta.talib import MININDEX as talib_MININDEX from polars_ta.wq import ts_arg_min df = pl.DataFrame({ 'a': [6, 2, 8, 5, 9, 4], }).with_columns( out1=ts_arg_min(pl.col('a'), 3), out2=ta_MININDEX(pl.col('a'), 3), out3=talib_MININDEX(pl.col('a'), 3), ) shape: (6, 4) ┌─────┬──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ u16 ┆ i64 ┆ i32 │ ╞═════╪══════╪══════╪══════╡ │ 6 ┆ null ┆ null ┆ 0 │ │ 2 ┆ null ┆ null ┆ 0 │ │ 8 ┆ 1 ┆ 1 ┆ 1 │ │ 5 ┆ 2 ┆ 1 ┆ 1 │ │ 9 ┆ 1 ┆ 3 ┆ 3 │ │ 4 ┆ 0 ┆ 5 ┆ 5 │ └─────┴──────┴──────┴──────┘ ``` """ a = close.cum_count() b = ts_arg_min(close, timeperiod) return a - b - 1 ================================================ FILE: polars_ta/ta/overlap.py ================================================ from math import ceil, floor from polars import Expr, struct from polars_ta.ta.operators import MAX from polars_ta.ta.operators import MIN from polars_ta.ta.statistic import STDDEV from polars_ta.wq.time_series import ts_decay_linear as WMA # noqa from polars_ta.wq.time_series import ts_mean as SMA # noqa def BBANDS(close: Expr, timeperiod: float = 5.0, nbdevup: float = 2.0, nbdevdn: float = 2.0, matype: float = 0.0) -> Expr: middleband = SMA(close, timeperiod) stddev = STDDEV(close, timeperiod) return struct(upperband=middleband + stddev * nbdevup, middleband=middleband, lowerband=middleband - stddev * nbdevdn) def DEMA(close: Expr, timeperiod: int = 30) -> Expr: EMA1 = EMA(close, timeperiod) EMA2 = EMA(EMA1, timeperiod) return EMA1 * 2 - EMA2 def EMA(close: Expr, timeperiod: int = 30) -> Expr: """ References ---------- https://pola-rs.github.io/polars/py-polars/html/reference/expressions/api/polars.Expr.ewm_mean.html#polars.Expr.ewm_mean """ # 相当于alpha=2/(1+timeperiod) return close.ewm_mean(span=timeperiod, adjust=False, min_samples=timeperiod) def KAMA(close: Expr, timeperiod: int = 30) -> Expr: raise def MIDPOINT(close: Expr, timeperiod: int = 14) -> Expr: """MIDPOINT = (Highest Value + Lowest Value)/2 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_MIDPOINT.c#L198 """ return (MAX(close, timeperiod) + MIN(close, timeperiod)) / 2 def MIDPRICE(high: Expr, low: Expr, timeperiod: int = 14) -> Expr: """MIDPRICE = (Highest High + Lowest Low)/2 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_MIDPRICE.c#L202 """ return (MAX(high, timeperiod) + MIN(low, timeperiod)) / 2 def RMA(close: Expr, timeperiod: int = 30) -> Expr: """TA-Lib does not provide this algorithm explicitly, it is just put here for convenience TA-Lib没有明确的提供此算法,这里只是为了调用方便而放在此处 References ---------- https://pola-rs.github.io/polars/py-polars/html/reference/expressions/api/polars.Expr.ewm_mean.html#polars.Expr.ewm_mean https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/overlap/rma.py """ return close.ewm_mean(alpha=1 / timeperiod, adjust=False, min_samples=timeperiod) def TEMA(close: Expr, timeperiod: int = 30) -> Expr: """ Notes ----- todo: if the nesting level is too deep, maybe call talib.TEMA directly 嵌套层数过多,也许直接调用talib.TEMA更快 """ EMA1 = EMA(close, timeperiod) EMA2 = EMA(EMA1, timeperiod) EMA3 = EMA(EMA2, timeperiod) return (EMA1 - EMA2) * 3 + EMA3 def TRIMA(close: Expr, timeperiod: int = 30) -> Expr: SMA1 = SMA(close, ceil(timeperiod / 2)) return SMA(SMA1, floor(timeperiod / 2) + 1) ================================================ FILE: polars_ta/ta/price.py ================================================ from polars import Expr def AVGPRICE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: """(open + high + low + close) / 4 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_AVGPRICE.c#L187 """ return (open + high + low + close) / 4 def MEDPRICE(high: Expr, low: Expr) -> Expr: """(high + low) / 2 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_MEDPRICE.c#L180 """ return (high + low) / 2 def TYPPRICE(high: Expr, low: Expr, close: Expr) -> Expr: """(high + low + close) / 3 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_TYPPRICE.c#L185 """ return (high + low + close) / 3 def WCLPRICE(high: Expr, low: Expr, close: Expr) -> Expr: """(high + low + close * 2) / 4 References ---------- https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_WCLPRICE.c#L184 """ return (high + low + close * 2) / 4 ================================================ FILE: polars_ta/ta/statistic.py ================================================ """ 通过`import`直接导入或更名的函数 ```python from polars_ta.wq.time_series import ts_corr as CORREL # noqa ``` """ from polars import Expr from polars_ta.wq.time_series import ts_corr as CORREL # noqa from polars_ta.wq.time_series import ts_std_dev def BETA(high: Expr, low: Expr, timeperiod: int = 5) -> Expr: raise def LINEARREG(close: Expr, timeperiod: int = 14) -> Expr: raise def LINEARREG_ANGLE(close: Expr, timeperiod: int = 14) -> Expr: raise def LINEARREG_INTERCEPT(close: Expr, timeperiod: int = 14) -> Expr: raise def LINEARREG_SLOPE(close: Expr, timeperiod: int = 14) -> Expr: raise def STDDEV(close: Expr, timeperiod: int = 5, nbdev: float = 1) -> Expr: return ts_std_dev(close, timeperiod, ddof=0) * nbdev def TSF(close: Expr, timeperiod: int = 14) -> Expr: raise def VAR(close: Expr, timeperiod: int = 5, nbdev: float = 1) -> Expr: return close.rolling_var(timeperiod, ddof=0) * nbdev ================================================ FILE: polars_ta/ta/transform.py ================================================ """ 通过`import`直接导入或更名的函数 ```python from polars_ta.wq.arithmetic import arc_cos as ACOS # noqa from polars_ta.wq.arithmetic import arc_sin as ASIN # noqa from polars_ta.wq.arithmetic import arc_tan as ATAN # noqa from polars_ta.wq.arithmetic import ceiling as CEIL # noqa from polars_ta.wq.arithmetic import cos as COS # noqa from polars_ta.wq.arithmetic import cosh as COSH # noqa from polars_ta.wq.arithmetic import exp as EXP # noqa from polars_ta.wq.arithmetic import floor as FLOOR # noqa from polars_ta.wq.arithmetic import log as LN # noqa from polars_ta.wq.arithmetic import log10 as LOG10 # noqa from polars_ta.wq.arithmetic import sin as SIN # noqa from polars_ta.wq.arithmetic import sinh as SINH # noqa from polars_ta.wq.arithmetic import sqrt as SQRT # noqa from polars_ta.wq.arithmetic import tan as TAN # noqa from polars_ta.wq.arithmetic import tanh as TANH # noqa ``` """ from polars_ta.wq.arithmetic import arc_cos as ACOS # noqa from polars_ta.wq.arithmetic import arc_sin as ASIN # noqa from polars_ta.wq.arithmetic import arc_tan as ATAN # noqa from polars_ta.wq.arithmetic import ceiling as CEIL # noqa from polars_ta.wq.arithmetic import cos as COS # noqa from polars_ta.wq.arithmetic import cosh as COSH # noqa from polars_ta.wq.arithmetic import exp as EXP # noqa from polars_ta.wq.arithmetic import floor as FLOOR # noqa from polars_ta.wq.arithmetic import log as LN # noqa from polars_ta.wq.arithmetic import log10 as LOG10 # noqa from polars_ta.wq.arithmetic import sin as SIN # noqa from polars_ta.wq.arithmetic import sinh as SINH # noqa from polars_ta.wq.arithmetic import sqrt as SQRT # noqa from polars_ta.wq.arithmetic import tan as TAN # noqa from polars_ta.wq.arithmetic import tanh as TANH # noqa ================================================ FILE: polars_ta/ta/volatility.py ================================================ from polars import Expr, max_horizontal from polars_ta.ta.overlap import RMA def TRANGE(high: Expr, low: Expr, close: Expr) -> Expr: """ Notes ----- the 0-th position is `x` rather than `nan` in talib 第0位置为max(x,nan,nan)=x,比talib多一个值 """ prev_close = close.shift(1) tr1 = high - low tr2 = (high - prev_close).abs() tr3 = (low - prev_close).abs() return max_horizontal(tr1, tr2, tr3) def ATR(high: Expr, low: Expr, close: Expr, timeperiod: int = 14) -> Expr: """""" return RMA(TRANGE(high, low, close), timeperiod) def NATR(high: Expr, low: Expr, close: Expr, timeperiod: int = 14) -> Expr: """ Notes ----- talib.ATR multiples another 100 talib.ATR版相当于多乘了100 """ return ATR(high, low, close, timeperiod) / close ================================================ FILE: polars_ta/ta/volume.py ================================================ from polars import Expr, when from polars_ta.ta.overlap import EMA def AD(high: Expr, low: Expr, close: Expr, volume: Expr) -> Expr: ad = when(high != low).then(((close - low) - (high - close)) / (high - low)).otherwise(0) return (ad * volume).cum_sum() def ADOSC(high: Expr, low: Expr, close: Expr, volume: Expr, fastperiod: int = 3, slowperiod: int = 10) -> Expr: ad = AD(high, low, close, volume) return EMA(ad, fastperiod) - EMA(ad, slowperiod) def OBV(close: Expr, volume: Expr) -> Expr: """""" # using volume for the first value will be exactly the same as talib.OBV # 第一个值用volume就与talib.OBV完全一样了 obv = close.diff().sign().fill_null(1) * volume return obv.cum_sum() ================================================ FILE: polars_ta/talib/README.md ================================================ # polars_ta.talib Inside this package, files are generated by `tools.codegen_talib2`. It is a wrapper of `talib` functions, with the following features: 1. Input and output are `Expr` instead of `Series` 2. ~~Add skipna feature (not efficient, will update when `polars` support backward fill)~~ 本包由`tools.codegen_talib2`自动生成,是对`talib`代码的封装。实现了以下功能 1. 输入输出由`Series`改`Expr` 2. ~~添加跳过空值功能(效率不高,等`polars`支持反向填充,此部分将更新)~~ ================================================ FILE: polars_ta/talib/__init__.py ================================================ # generated by codegen_talib.py import talib as _ta from polars import Expr, struct, Struct, Field, Float64, Int32 from polars_ta.utils.numba_ import batches_i1_o1, batches_i1_o2, batches_i2_o1, batches_i2_o2, struct_to_numpy def HT_DCPERIOD(close: Expr) -> Expr: # ['real'] """HT_DCPERIOD(ndarray real) HT_DCPERIOD(real) Hilbert Transform - Dominant Cycle Period (Cycle Indicators) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.HT_DCPERIOD), return_dtype=Float64) def HT_DCPHASE(close: Expr) -> Expr: # ['real'] """HT_DCPHASE(ndarray real) HT_DCPHASE(real) Hilbert Transform - Dominant Cycle Phase (Cycle Indicators) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.HT_DCPHASE), return_dtype=Float64) def HT_PHASOR(close: Expr) -> Expr: # ['inphase', 'quadrature'] """HT_PHASOR(ndarray real) HT_PHASOR(real) Hilbert Transform - Phasor Components (Cycle Indicators) Inputs: real: (any ndarray) Outputs: inphase quadrature""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.HT_PHASOR), return_dtype=dtype) def HT_SINE(close: Expr) -> Expr: # ['sine', 'leadsine'] """HT_SINE(ndarray real) HT_SINE(real) Hilbert Transform - SineWave (Cycle Indicators) Inputs: real: (any ndarray) Outputs: sine leadsine""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.HT_SINE), return_dtype=dtype) def HT_TRENDMODE(close: Expr) -> Expr: # ['integer'] """HT_TRENDMODE(ndarray real) HT_TRENDMODE(real) Hilbert Transform - Trend vs Cycle Mode (Cycle Indicators) Inputs: real: (any ndarray) Outputs: integer (values are -100, 0 or 100)""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.HT_TRENDMODE), return_dtype=Int32) def ADD(high: Expr, low: Expr) -> Expr: # ['real'] """ADD(ndarray real0, ndarray real1) ADD(real0, real1) Vector Arithmetic Add (Math Operators) Inputs: real0: (any ndarray) real1: (any ndarray) Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.ADD), return_dtype=Float64) def DIV(high: Expr, low: Expr) -> Expr: # ['real'] """DIV(ndarray real0, ndarray real1) DIV(real0, real1) Vector Arithmetic Div (Math Operators) Inputs: real0: (any ndarray) real1: (any ndarray) Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.DIV), return_dtype=Float64) def MAX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """MAX(ndarray real, int timeperiod=-0x80000000) MAX(real[, timeperiod=?]) Highest value over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MAX, timeperiod), return_dtype=Float64) def MAXINDEX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['integer'] """MAXINDEX(ndarray real, int timeperiod=-0x80000000) MAXINDEX(real[, timeperiod=?]) Index of highest value over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: integer (values are -100, 0 or 100)""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MAXINDEX, timeperiod), return_dtype=Int32) def MIN(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """MIN(ndarray real, int timeperiod=-0x80000000) MIN(real[, timeperiod=?]) Lowest value over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MIN, timeperiod), return_dtype=Float64) def MININDEX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['integer'] """MININDEX(ndarray real, int timeperiod=-0x80000000) MININDEX(real[, timeperiod=?]) Index of lowest value over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: integer (values are -100, 0 or 100)""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MININDEX, timeperiod), return_dtype=Int32) def MINMAX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['min', 'max'] """MINMAX(ndarray real, int timeperiod=-0x80000000) MINMAX(real[, timeperiod=?]) Lowest and highest values over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: min max""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MINMAX, timeperiod), return_dtype=dtype) def MINMAXINDEX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['minidx', 'maxidx'] """MINMAXINDEX(ndarray real, int timeperiod=-0x80000000) MINMAXINDEX(real[, timeperiod=?]) Indexes of lowest and highest values over a specified period (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: minidx maxidx""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MINMAXINDEX, timeperiod), return_dtype=dtype) def MULT(high: Expr, low: Expr) -> Expr: # ['real'] """MULT(ndarray real0, ndarray real1) MULT(real0, real1) Vector Arithmetic Mult (Math Operators) Inputs: real0: (any ndarray) real1: (any ndarray) Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.MULT), return_dtype=Float64) def SUB(high: Expr, low: Expr) -> Expr: # ['real'] """SUB(ndarray real0, ndarray real1) SUB(real0, real1) Vector Arithmetic Subtraction (Math Operators) Inputs: real0: (any ndarray) real1: (any ndarray) Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.SUB), return_dtype=Float64) def SUM(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """SUM(ndarray real, int timeperiod=-0x80000000) SUM(real[, timeperiod=?]) Summation (Math Operators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.SUM, timeperiod), return_dtype=Float64) def ACOS(close: Expr) -> Expr: # ['real'] """ACOS(ndarray real) ACOS(real) Vector Trigonometric ACos (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ACOS), return_dtype=Float64) def ASIN(close: Expr) -> Expr: # ['real'] """ASIN(ndarray real) ASIN(real) Vector Trigonometric ASin (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ASIN), return_dtype=Float64) def ATAN(close: Expr) -> Expr: # ['real'] """ATAN(ndarray real) ATAN(real) Vector Trigonometric ATan (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ATAN), return_dtype=Float64) def CEIL(close: Expr) -> Expr: # ['real'] """CEIL(ndarray real) CEIL(real) Vector Ceil (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.CEIL), return_dtype=Float64) def COS(close: Expr) -> Expr: # ['real'] """COS(ndarray real) COS(real) Vector Trigonometric Cos (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.COS), return_dtype=Float64) def COSH(close: Expr) -> Expr: # ['real'] """COSH(ndarray real) COSH(real) Vector Trigonometric Cosh (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.COSH), return_dtype=Float64) def EXP(close: Expr) -> Expr: # ['real'] """EXP(ndarray real) EXP(real) Vector Arithmetic Exp (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.EXP), return_dtype=Float64) def FLOOR(close: Expr) -> Expr: # ['real'] """FLOOR(ndarray real) FLOOR(real) Vector Floor (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.FLOOR), return_dtype=Float64) def LN(close: Expr) -> Expr: # ['real'] """LN(ndarray real) LN(real) Vector Log Natural (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LN), return_dtype=Float64) def LOG10(close: Expr) -> Expr: # ['real'] """LOG10(ndarray real) LOG10(real) Vector Log10 (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LOG10), return_dtype=Float64) def SIN(close: Expr) -> Expr: # ['real'] """SIN(ndarray real) SIN(real) Vector Trigonometric Sin (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.SIN), return_dtype=Float64) def SINH(close: Expr) -> Expr: # ['real'] """SINH(ndarray real) SINH(real) Vector Trigonometric Sinh (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.SINH), return_dtype=Float64) def SQRT(close: Expr) -> Expr: # ['real'] """SQRT(ndarray real) SQRT(real) Vector Square Root (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.SQRT), return_dtype=Float64) def TAN(close: Expr) -> Expr: # ['real'] """TAN(ndarray real) TAN(real) Vector Trigonometric Tan (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TAN), return_dtype=Float64) def TANH(close: Expr) -> Expr: # ['real'] """TANH(ndarray real) TANH(real) Vector Trigonometric Tanh (Math Transform) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TANH), return_dtype=Float64) def ADX(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """ADX(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) ADX(high, low, close[, timeperiod=?]) Average Directional Movement Index (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.ADX, timeperiod), return_dtype=Float64) def ADXR(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """ADXR(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) ADXR(high, low, close[, timeperiod=?]) Average Directional Movement Index Rating (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.ADXR, timeperiod), return_dtype=Float64) def APO(close: Expr, fastperiod: float = 12.0, slowperiod: float = 26.0, matype: float = 0.0) -> Expr: # ['real'] """APO(ndarray real, int fastperiod=-0x80000000, int slowperiod=-0x80000000, int matype=0) APO(real[, fastperiod=?, slowperiod=?, matype=?]) Absolute Price Oscillator (Momentum Indicators) Inputs: real: (any ndarray) Parameters: fastperiod: 12 slowperiod: 26 matype: 0 (Simple Moving Average) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.APO, fastperiod, slowperiod, matype), return_dtype=Float64) def AROON(high: Expr, low: Expr, timeperiod: float = 14.0) -> Expr: # ['aroondown', 'aroonup'] """AROON(ndarray high, ndarray low, int timeperiod=-0x80000000) AROON(high, low[, timeperiod=?]) Aroon (Momentum Indicators) Inputs: prices: ['high', 'low'] Parameters: timeperiod: 14 Outputs: aroondown aroonup""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, 2, dtype=float), _ta.AROON, timeperiod), return_dtype=dtype) def AROONOSC(high: Expr, low: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """AROONOSC(ndarray high, ndarray low, int timeperiod=-0x80000000) AROONOSC(high, low[, timeperiod=?]) Aroon Oscillator (Momentum Indicators) Inputs: prices: ['high', 'low'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.AROONOSC, timeperiod), return_dtype=Float64) def BOP(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['real'] """BOP(ndarray open, ndarray high, ndarray low, ndarray close) BOP(open, high, low, close) Balance Of Power (Momentum Indicators) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: real""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.BOP), return_dtype=Float64) def CCI(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """CCI(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) CCI(high, low, close[, timeperiod=?]) Commodity Channel Index (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.CCI, timeperiod), return_dtype=Float64) def CMO(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """CMO(ndarray real, int timeperiod=-0x80000000) CMO(real[, timeperiod=?]) Chande Momentum Oscillator (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.CMO, timeperiod), return_dtype=Float64) def DX(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """DX(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) DX(high, low, close[, timeperiod=?]) Directional Movement Index (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.DX, timeperiod), return_dtype=Float64) def MACD(close: Expr, fastperiod: float = 12.0, slowperiod: float = 26.0, signalperiod: float = 9.0) -> Expr: # ['macd', 'macdsignal', 'macdhist'] """MACD(ndarray real, int fastperiod=-0x80000000, int slowperiod=-0x80000000, int signalperiod=-0x80000000) MACD(real[, fastperiod=?, slowperiod=?, signalperiod=?]) Moving Average Convergence/Divergence (Momentum Indicators) Inputs: real: (any ndarray) Parameters: fastperiod: 12 slowperiod: 26 signalperiod: 9 Outputs: macd macdsignal macdhist""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(3)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MACD, fastperiod, slowperiod, signalperiod), return_dtype=dtype) def MACDEXT(close: Expr, fastperiod: float = 12.0, fastmatype: float = 0.0, slowperiod: float = 26.0, slowmatype: float = 0.0, signalperiod: float = 9.0, signalmatype: float = 0.0) -> Expr: # ['macd', 'macdsignal', 'macdhist'] """MACDEXT(ndarray real, int fastperiod=-0x80000000, int fastmatype=0, int slowperiod=-0x80000000, int slowmatype=0, int signalperiod=-0x80000000, int signalmatype=0) MACDEXT(real[, fastperiod=?, fastmatype=?, slowperiod=?, slowmatype=?, signalperiod=?, signalmatype=?]) MACD with controllable MA type (Momentum Indicators) Inputs: real: (any ndarray) Parameters: fastperiod: 12 fastmatype: 0 slowperiod: 26 slowmatype: 0 signalperiod: 9 signalmatype: 0 Outputs: macd macdsignal macdhist""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(3)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MACDEXT, fastperiod, fastmatype, slowperiod, slowmatype, signalperiod, signalmatype), return_dtype=dtype) def MACDFIX(close: Expr, signalperiod: float = 9.0) -> Expr: # ['macd', 'macdsignal', 'macdhist'] """MACDFIX(ndarray real, int signalperiod=-0x80000000) MACDFIX(real[, signalperiod=?]) Moving Average Convergence/Divergence Fix 12/26 (Momentum Indicators) Inputs: real: (any ndarray) Parameters: signalperiod: 9 Outputs: macd macdsignal macdhist""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(3)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MACDFIX, signalperiod), return_dtype=dtype) def MFI(high: Expr, low: Expr, close: Expr, volume: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """MFI(ndarray high, ndarray low, ndarray close, ndarray volume, int timeperiod=-0x80000000) MFI(high, low, close, volume[, timeperiod=?]) Money Flow Index (Momentum Indicators) Inputs: prices: ['high', 'low', 'close', 'volume'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close, f3=volume).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.MFI, timeperiod), return_dtype=Float64) def MINUS_DI(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """MINUS_DI(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) MINUS_DI(high, low, close[, timeperiod=?]) Minus Directional Indicator (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.MINUS_DI, timeperiod), return_dtype=Float64) def MINUS_DM(high: Expr, low: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """MINUS_DM(ndarray high, ndarray low, int timeperiod=-0x80000000) MINUS_DM(high, low[, timeperiod=?]) Minus Directional Movement (Momentum Indicators) Inputs: prices: ['high', 'low'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.MINUS_DM, timeperiod), return_dtype=Float64) def MOM(close: Expr, timeperiod: float = 10.0) -> Expr: # ['real'] """MOM(ndarray real, int timeperiod=-0x80000000) MOM(real[, timeperiod=?]) Momentum (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 10 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MOM, timeperiod), return_dtype=Float64) def PLUS_DI(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """PLUS_DI(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) PLUS_DI(high, low, close[, timeperiod=?]) Plus Directional Indicator (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.PLUS_DI, timeperiod), return_dtype=Float64) def PLUS_DM(high: Expr, low: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """PLUS_DM(ndarray high, ndarray low, int timeperiod=-0x80000000) PLUS_DM(high, low[, timeperiod=?]) Plus Directional Movement (Momentum Indicators) Inputs: prices: ['high', 'low'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.PLUS_DM, timeperiod), return_dtype=Float64) def PPO(close: Expr, fastperiod: float = 12.0, slowperiod: float = 26.0, matype: float = 0.0) -> Expr: # ['real'] """PPO(ndarray real, int fastperiod=-0x80000000, int slowperiod=-0x80000000, int matype=0) PPO(real[, fastperiod=?, slowperiod=?, matype=?]) Percentage Price Oscillator (Momentum Indicators) Inputs: real: (any ndarray) Parameters: fastperiod: 12 slowperiod: 26 matype: 0 (Simple Moving Average) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.PPO, fastperiod, slowperiod, matype), return_dtype=Float64) def ROC(close: Expr, timeperiod: float = 10.0) -> Expr: # ['real'] """ROC(ndarray real, int timeperiod=-0x80000000) ROC(real[, timeperiod=?]) Rate of change : ((real/prevPrice)-1)*100 (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 10 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ROC, timeperiod), return_dtype=Float64) def ROCP(close: Expr, timeperiod: float = 10.0) -> Expr: # ['real'] """ROCP(ndarray real, int timeperiod=-0x80000000) ROCP(real[, timeperiod=?]) Rate of change Percentage: (real-prevPrice)/prevPrice (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 10 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ROCP, timeperiod), return_dtype=Float64) def ROCR(close: Expr, timeperiod: float = 10.0) -> Expr: # ['real'] """ROCR(ndarray real, int timeperiod=-0x80000000) ROCR(real[, timeperiod=?]) Rate of change ratio: (real/prevPrice) (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 10 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ROCR, timeperiod), return_dtype=Float64) def ROCR100(close: Expr, timeperiod: float = 10.0) -> Expr: # ['real'] """ROCR100(ndarray real, int timeperiod=-0x80000000) ROCR100(real[, timeperiod=?]) Rate of change ratio 100 scale: (real/prevPrice)*100 (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 10 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.ROCR100, timeperiod), return_dtype=Float64) def RSI(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """RSI(ndarray real, int timeperiod=-0x80000000) RSI(real[, timeperiod=?]) Relative Strength Index (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.RSI, timeperiod), return_dtype=Float64) def STOCH(high: Expr, low: Expr, close: Expr, fastk_period: float = 5.0, slowk_period: float = 3.0, slowk_matype: float = 0.0, slowd_period: float = 3.0, slowd_matype: float = 0.0) -> Expr: # ['slowk', 'slowd'] """STOCH(ndarray high, ndarray low, ndarray close, int fastk_period=-0x80000000, int slowk_period=-0x80000000, int slowk_matype=0, int slowd_period=-0x80000000, int slowd_matype=0) STOCH(high, low, close[, fastk_period=?, slowk_period=?, slowk_matype=?, slowd_period=?, slowd_matype=?]) Stochastic (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: fastk_period: 5 slowk_period: 3 slowk_matype: 0 slowd_period: 3 slowd_matype: 0 Outputs: slowk slowd""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, 3, dtype=float), _ta.STOCH, fastk_period, slowk_period, slowk_matype, slowd_period, slowd_matype), return_dtype=dtype) def STOCHF(high: Expr, low: Expr, close: Expr, fastk_period: float = 5.0, fastd_period: float = 3.0, fastd_matype: float = 0.0) -> Expr: # ['fastk', 'fastd'] """STOCHF(ndarray high, ndarray low, ndarray close, int fastk_period=-0x80000000, int fastd_period=-0x80000000, int fastd_matype=0) STOCHF(high, low, close[, fastk_period=?, fastd_period=?, fastd_matype=?]) Stochastic Fast (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: fastk_period: 5 fastd_period: 3 fastd_matype: 0 Outputs: fastk fastd""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, 3, dtype=float), _ta.STOCHF, fastk_period, fastd_period, fastd_matype), return_dtype=dtype) def STOCHRSI(close: Expr, timeperiod: float = 14.0, fastk_period: float = 5.0, fastd_period: float = 3.0, fastd_matype: float = 0.0) -> Expr: # ['fastk', 'fastd'] """STOCHRSI(ndarray real, int timeperiod=-0x80000000, int fastk_period=-0x80000000, int fastd_period=-0x80000000, int fastd_matype=0) STOCHRSI(real[, timeperiod=?, fastk_period=?, fastd_period=?, fastd_matype=?]) Stochastic Relative Strength Index (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 14 fastk_period: 5 fastd_period: 3 fastd_matype: 0 Outputs: fastk fastd""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.STOCHRSI, timeperiod, fastk_period, fastd_period, fastd_matype), return_dtype=dtype) def TRIX(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """TRIX(ndarray real, int timeperiod=-0x80000000) TRIX(real[, timeperiod=?]) 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA (Momentum Indicators) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TRIX, timeperiod), return_dtype=Float64) def ULTOSC(high: Expr, low: Expr, close: Expr, timeperiod1: float = 7.0, timeperiod2: float = 14.0, timeperiod3: float = 28.0) -> Expr: # ['real'] """ULTOSC(ndarray high, ndarray low, ndarray close, int timeperiod1=-0x80000000, int timeperiod2=-0x80000000, int timeperiod3=-0x80000000) ULTOSC(high, low, close[, timeperiod1=?, timeperiod2=?, timeperiod3=?]) Ultimate Oscillator (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod1: 7 timeperiod2: 14 timeperiod3: 28 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.ULTOSC, timeperiod1, timeperiod2, timeperiod3), return_dtype=Float64) def WILLR(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """WILLR(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) WILLR(high, low, close[, timeperiod=?]) Williams' %R (Momentum Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.WILLR, timeperiod), return_dtype=Float64) def BBANDS(close: Expr, timeperiod: float = 5.0, nbdevup: float = 2.0, nbdevdn: float = 2.0, matype: float = 0.0) -> Expr: # ['upperband', 'middleband', 'lowerband'] """BBANDS(ndarray real, int timeperiod=-0x80000000, double nbdevup=-4e37, double nbdevdn=-4e37, int matype=0) BBANDS(real[, timeperiod=?, nbdevup=?, nbdevdn=?, matype=?]) Bollinger Bands (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 5 nbdevup: 2.0 nbdevdn: 2.0 matype: 0 (Simple Moving Average) Outputs: upperband middleband lowerband""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(3)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.BBANDS, timeperiod, nbdevup, nbdevdn, matype), return_dtype=dtype) def DEMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """DEMA(ndarray real, int timeperiod=-0x80000000) DEMA(real[, timeperiod=?]) Double Exponential Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.DEMA, timeperiod), return_dtype=Float64) def EMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """EMA(ndarray real, int timeperiod=-0x80000000) EMA(real[, timeperiod=?]) Exponential Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.EMA, timeperiod), return_dtype=Float64) def HT_TRENDLINE(close: Expr) -> Expr: # ['real'] """HT_TRENDLINE(ndarray real) HT_TRENDLINE(real) Hilbert Transform - Instantaneous Trendline (Overlap Studies) Inputs: real: (any ndarray) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.HT_TRENDLINE), return_dtype=Float64) def KAMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """KAMA(ndarray real, int timeperiod=-0x80000000) KAMA(real[, timeperiod=?]) Kaufman Adaptive Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.KAMA, timeperiod), return_dtype=Float64) def MA(close: Expr, timeperiod: float = 30.0, matype: float = 0.0) -> Expr: # ['real'] """MA(ndarray real, int timeperiod=-0x80000000, int matype=0) MA(real[, timeperiod=?, matype=?]) Moving average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 matype: 0 (Simple Moving Average) Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MA, timeperiod, matype), return_dtype=Float64) def MAMA(close: Expr, fastlimit: float = 0.5, slowlimit: float = 0.05) -> Expr: # ['mama', 'fama'] """MAMA(ndarray real, double fastlimit=-4e37, double slowlimit=-4e37) MAMA(real[, fastlimit=?, slowlimit=?]) MESA Adaptive Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: fastlimit: 0.5 slowlimit: 0.05 Outputs: mama fama""" dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return close.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), _ta.MAMA, fastlimit, slowlimit), return_dtype=dtype) def MAVP(close: Expr, periods: Expr, minperiod: float = 2.0, maxperiod: float = 30.0, matype: float = 0.0) -> Expr: # ['real'] """MAVP(ndarray real, ndarray periods, int minperiod=-0x80000000, int maxperiod=-0x80000000, int matype=0) MAVP(real, periods[, minperiod=?, maxperiod=?, matype=?]) Moving average with variable period (Overlap Studies) Inputs: real: (any ndarray) periods: (any ndarray) Parameters: minperiod: 2 maxperiod: 30 matype: 0 (Simple Moving Average) Outputs: real""" return struct(f0=close, f1=periods).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.MAVP, minperiod, maxperiod, matype), return_dtype=Float64) def MIDPOINT(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """MIDPOINT(ndarray real, int timeperiod=-0x80000000) MIDPOINT(real[, timeperiod=?]) MidPoint over period (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.MIDPOINT, timeperiod), return_dtype=Float64) def MIDPRICE(high: Expr, low: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """MIDPRICE(ndarray high, ndarray low, int timeperiod=-0x80000000) MIDPRICE(high, low[, timeperiod=?]) Midpoint Price over period (Overlap Studies) Inputs: prices: ['high', 'low'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.MIDPRICE, timeperiod), return_dtype=Float64) def SAR(high: Expr, low: Expr, acceleration: float = 0.02, maximum: float = 0.2) -> Expr: # ['real'] """SAR(ndarray high, ndarray low, double acceleration=0.02, double maximum=0.2) SAR(high, low[, acceleration=?, maximum=?]) Parabolic SAR (Overlap Studies) Inputs: prices: ['high', 'low'] Parameters: acceleration: 0.02 maximum: 0.2 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.SAR, acceleration, maximum), return_dtype=Float64) def SAREXT(high: Expr, low: Expr, startvalue: float = 0.0, offsetonreverse: float = 0.0, accelerationinitlong: float = 0.02, accelerationlong: float = 0.02, accelerationmaxlong: float = 0.2, accelerationinitshort: float = 0.02, accelerationshort: float = 0.02, accelerationmaxshort: float = 0.2) -> Expr: # ['real'] """SAREXT(ndarray high, ndarray low, double startvalue=-4e37, double offsetonreverse=-4e37, double accelerationinitlong=-4e37, double accelerationlong=-4e37, double accelerationmaxlong=-4e37, double accelerationinitshort=-4e37, double accelerationshort=-4e37, double accelerationmaxshort=-4e37) SAREXT(high, low[, startvalue=?, offsetonreverse=?, accelerationinitlong=?, accelerationlong=?, accelerationmaxlong=?, accelerationinitshort=?, accelerationshort=?, accelerationmaxshort=?]) Parabolic SAR - Extended (Overlap Studies) Inputs: prices: ['high', 'low'] Parameters: startvalue: 0.0 offsetonreverse: 0.0 accelerationinitlong: 0.02 accelerationlong: 0.02 accelerationmaxlong: 0.2 accelerationinitshort: 0.02 accelerationshort: 0.02 accelerationmaxshort: 0.2 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.SAREXT, startvalue, offsetonreverse, accelerationinitlong, accelerationlong, accelerationmaxlong, accelerationinitshort, accelerationshort, accelerationmaxshort), return_dtype=Float64) def SMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """SMA(ndarray real, int timeperiod=-0x80000000) SMA(real[, timeperiod=?]) Simple Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.SMA, timeperiod), return_dtype=Float64) def T3(close: Expr, timeperiod: float = 5.0, vfactor: float = 0.7) -> Expr: # ['real'] """T3(ndarray real, int timeperiod=-0x80000000, double vfactor=-4e37) T3(real[, timeperiod=?, vfactor=?]) Triple Exponential Moving Average (T3) (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 5 vfactor: 0.7 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.T3, timeperiod, vfactor), return_dtype=Float64) def TEMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """TEMA(ndarray real, int timeperiod=-0x80000000) TEMA(real[, timeperiod=?]) Triple Exponential Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TEMA, timeperiod), return_dtype=Float64) def TRIMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """TRIMA(ndarray real, int timeperiod=-0x80000000) TRIMA(real[, timeperiod=?]) Triangular Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TRIMA, timeperiod), return_dtype=Float64) def WMA(close: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """WMA(ndarray real, int timeperiod=-0x80000000) WMA(real[, timeperiod=?]) Weighted Moving Average (Overlap Studies) Inputs: real: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.WMA, timeperiod), return_dtype=Float64) def CDL2CROWS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL2CROWS(ndarray open, ndarray high, ndarray low, ndarray close) CDL2CROWS(open, high, low, close) Two Crows (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL2CROWS), return_dtype=Int32) def CDL3BLACKCROWS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3BLACKCROWS(ndarray open, ndarray high, ndarray low, ndarray close) CDL3BLACKCROWS(open, high, low, close) Three Black Crows (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3BLACKCROWS), return_dtype=Int32) def CDL3INSIDE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3INSIDE(ndarray open, ndarray high, ndarray low, ndarray close) CDL3INSIDE(open, high, low, close) Three Inside Up/Down (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3INSIDE), return_dtype=Int32) def CDL3LINESTRIKE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3LINESTRIKE(ndarray open, ndarray high, ndarray low, ndarray close) CDL3LINESTRIKE(open, high, low, close) Three-Line Strike (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3LINESTRIKE), return_dtype=Int32) def CDL3OUTSIDE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3OUTSIDE(ndarray open, ndarray high, ndarray low, ndarray close) CDL3OUTSIDE(open, high, low, close) Three Outside Up/Down (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3OUTSIDE), return_dtype=Int32) def CDL3STARSINSOUTH(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3STARSINSOUTH(ndarray open, ndarray high, ndarray low, ndarray close) CDL3STARSINSOUTH(open, high, low, close) Three Stars In The South (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3STARSINSOUTH), return_dtype=Int32) def CDL3WHITESOLDIERS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDL3WHITESOLDIERS(ndarray open, ndarray high, ndarray low, ndarray close) CDL3WHITESOLDIERS(open, high, low, close) Three Advancing White Soldiers (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDL3WHITESOLDIERS), return_dtype=Int32) def CDLABANDONEDBABY(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.3) -> Expr: # ['integer'] """CDLABANDONEDBABY(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.3) CDLABANDONEDBABY(open, high, low, close[, penetration=?]) Abandoned Baby (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.3 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLABANDONEDBABY, penetration), return_dtype=Int32) def CDLADVANCEBLOCK(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLADVANCEBLOCK(ndarray open, ndarray high, ndarray low, ndarray close) CDLADVANCEBLOCK(open, high, low, close) Advance Block (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLADVANCEBLOCK), return_dtype=Int32) def CDLBELTHOLD(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLBELTHOLD(ndarray open, ndarray high, ndarray low, ndarray close) CDLBELTHOLD(open, high, low, close) Belt-hold (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLBELTHOLD), return_dtype=Int32) def CDLBREAKAWAY(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLBREAKAWAY(ndarray open, ndarray high, ndarray low, ndarray close) CDLBREAKAWAY(open, high, low, close) Breakaway (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLBREAKAWAY), return_dtype=Int32) def CDLCLOSINGMARUBOZU(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLCLOSINGMARUBOZU(ndarray open, ndarray high, ndarray low, ndarray close) CDLCLOSINGMARUBOZU(open, high, low, close) Closing Marubozu (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLCLOSINGMARUBOZU), return_dtype=Int32) def CDLCONCEALBABYSWALL(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLCONCEALBABYSWALL(ndarray open, ndarray high, ndarray low, ndarray close) CDLCONCEALBABYSWALL(open, high, low, close) Concealing Baby Swallow (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLCONCEALBABYSWALL), return_dtype=Int32) def CDLCOUNTERATTACK(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLCOUNTERATTACK(ndarray open, ndarray high, ndarray low, ndarray close) CDLCOUNTERATTACK(open, high, low, close) Counterattack (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLCOUNTERATTACK), return_dtype=Int32) def CDLDARKCLOUDCOVER(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.5) -> Expr: # ['integer'] """CDLDARKCLOUDCOVER(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.5) CDLDARKCLOUDCOVER(open, high, low, close[, penetration=?]) Dark Cloud Cover (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.5 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLDARKCLOUDCOVER, penetration), return_dtype=Int32) def CDLDOJI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLDOJI(ndarray open, ndarray high, ndarray low, ndarray close) CDLDOJI(open, high, low, close) Doji (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLDOJI), return_dtype=Int32) def CDLDOJISTAR(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLDOJISTAR(ndarray open, ndarray high, ndarray low, ndarray close) CDLDOJISTAR(open, high, low, close) Doji Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLDOJISTAR), return_dtype=Int32) def CDLDRAGONFLYDOJI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLDRAGONFLYDOJI(ndarray open, ndarray high, ndarray low, ndarray close) CDLDRAGONFLYDOJI(open, high, low, close) Dragonfly Doji (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLDRAGONFLYDOJI), return_dtype=Int32) def CDLENGULFING(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLENGULFING(ndarray open, ndarray high, ndarray low, ndarray close) CDLENGULFING(open, high, low, close) Engulfing Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLENGULFING), return_dtype=Int32) def CDLEVENINGDOJISTAR(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.3) -> Expr: # ['integer'] """CDLEVENINGDOJISTAR(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.3) CDLEVENINGDOJISTAR(open, high, low, close[, penetration=?]) Evening Doji Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.3 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLEVENINGDOJISTAR, penetration), return_dtype=Int32) def CDLEVENINGSTAR(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.3) -> Expr: # ['integer'] """CDLEVENINGSTAR(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.3) CDLEVENINGSTAR(open, high, low, close[, penetration=?]) Evening Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.3 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLEVENINGSTAR, penetration), return_dtype=Int32) def CDLGAPSIDESIDEWHITE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLGAPSIDESIDEWHITE(ndarray open, ndarray high, ndarray low, ndarray close) CDLGAPSIDESIDEWHITE(open, high, low, close) Up/Down-gap side-by-side white lines (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLGAPSIDESIDEWHITE), return_dtype=Int32) def CDLGRAVESTONEDOJI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLGRAVESTONEDOJI(ndarray open, ndarray high, ndarray low, ndarray close) CDLGRAVESTONEDOJI(open, high, low, close) Gravestone Doji (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLGRAVESTONEDOJI), return_dtype=Int32) def CDLHAMMER(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHAMMER(ndarray open, ndarray high, ndarray low, ndarray close) CDLHAMMER(open, high, low, close) Hammer (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHAMMER), return_dtype=Int32) def CDLHANGINGMAN(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHANGINGMAN(ndarray open, ndarray high, ndarray low, ndarray close) CDLHANGINGMAN(open, high, low, close) Hanging Man (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHANGINGMAN), return_dtype=Int32) def CDLHARAMI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHARAMI(ndarray open, ndarray high, ndarray low, ndarray close) CDLHARAMI(open, high, low, close) Harami Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHARAMI), return_dtype=Int32) def CDLHARAMICROSS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHARAMICROSS(ndarray open, ndarray high, ndarray low, ndarray close) CDLHARAMICROSS(open, high, low, close) Harami Cross Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHARAMICROSS), return_dtype=Int32) def CDLHIGHWAVE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHIGHWAVE(ndarray open, ndarray high, ndarray low, ndarray close) CDLHIGHWAVE(open, high, low, close) High-Wave Candle (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHIGHWAVE), return_dtype=Int32) def CDLHIKKAKE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHIKKAKE(ndarray open, ndarray high, ndarray low, ndarray close) CDLHIKKAKE(open, high, low, close) Hikkake Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHIKKAKE), return_dtype=Int32) def CDLHIKKAKEMOD(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHIKKAKEMOD(ndarray open, ndarray high, ndarray low, ndarray close) CDLHIKKAKEMOD(open, high, low, close) Modified Hikkake Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHIKKAKEMOD), return_dtype=Int32) def CDLHOMINGPIGEON(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLHOMINGPIGEON(ndarray open, ndarray high, ndarray low, ndarray close) CDLHOMINGPIGEON(open, high, low, close) Homing Pigeon (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLHOMINGPIGEON), return_dtype=Int32) def CDLIDENTICAL3CROWS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLIDENTICAL3CROWS(ndarray open, ndarray high, ndarray low, ndarray close) CDLIDENTICAL3CROWS(open, high, low, close) Identical Three Crows (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLIDENTICAL3CROWS), return_dtype=Int32) def CDLINNECK(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLINNECK(ndarray open, ndarray high, ndarray low, ndarray close) CDLINNECK(open, high, low, close) In-Neck Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLINNECK), return_dtype=Int32) def CDLINVERTEDHAMMER(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLINVERTEDHAMMER(ndarray open, ndarray high, ndarray low, ndarray close) CDLINVERTEDHAMMER(open, high, low, close) Inverted Hammer (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLINVERTEDHAMMER), return_dtype=Int32) def CDLKICKING(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLKICKING(ndarray open, ndarray high, ndarray low, ndarray close) CDLKICKING(open, high, low, close) Kicking (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLKICKING), return_dtype=Int32) def CDLKICKINGBYLENGTH(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLKICKINGBYLENGTH(ndarray open, ndarray high, ndarray low, ndarray close) CDLKICKINGBYLENGTH(open, high, low, close) Kicking - bull/bear determined by the longer marubozu (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLKICKINGBYLENGTH), return_dtype=Int32) def CDLLADDERBOTTOM(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLLADDERBOTTOM(ndarray open, ndarray high, ndarray low, ndarray close) CDLLADDERBOTTOM(open, high, low, close) Ladder Bottom (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLLADDERBOTTOM), return_dtype=Int32) def CDLLONGLEGGEDDOJI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLLONGLEGGEDDOJI(ndarray open, ndarray high, ndarray low, ndarray close) CDLLONGLEGGEDDOJI(open, high, low, close) Long Legged Doji (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLLONGLEGGEDDOJI), return_dtype=Int32) def CDLLONGLINE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLLONGLINE(ndarray open, ndarray high, ndarray low, ndarray close) CDLLONGLINE(open, high, low, close) Long Line Candle (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLLONGLINE), return_dtype=Int32) def CDLMARUBOZU(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLMARUBOZU(ndarray open, ndarray high, ndarray low, ndarray close) CDLMARUBOZU(open, high, low, close) Marubozu (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLMARUBOZU), return_dtype=Int32) def CDLMATCHINGLOW(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLMATCHINGLOW(ndarray open, ndarray high, ndarray low, ndarray close) CDLMATCHINGLOW(open, high, low, close) Matching Low (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLMATCHINGLOW), return_dtype=Int32) def CDLMATHOLD(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.5) -> Expr: # ['integer'] """CDLMATHOLD(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.5) CDLMATHOLD(open, high, low, close[, penetration=?]) Mat Hold (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.5 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLMATHOLD, penetration), return_dtype=Int32) def CDLMORNINGDOJISTAR(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.3) -> Expr: # ['integer'] """CDLMORNINGDOJISTAR(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.3) CDLMORNINGDOJISTAR(open, high, low, close[, penetration=?]) Morning Doji Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.3 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLMORNINGDOJISTAR, penetration), return_dtype=Int32) def CDLMORNINGSTAR(open: Expr, high: Expr, low: Expr, close: Expr, penetration: float = 0.3) -> Expr: # ['integer'] """CDLMORNINGSTAR(ndarray open, ndarray high, ndarray low, ndarray close, double penetration=0.3) CDLMORNINGSTAR(open, high, low, close[, penetration=?]) Morning Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Parameters: penetration: 0.3 Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLMORNINGSTAR, penetration), return_dtype=Int32) def CDLONNECK(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLONNECK(ndarray open, ndarray high, ndarray low, ndarray close) CDLONNECK(open, high, low, close) On-Neck Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLONNECK), return_dtype=Int32) def CDLPIERCING(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLPIERCING(ndarray open, ndarray high, ndarray low, ndarray close) CDLPIERCING(open, high, low, close) Piercing Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLPIERCING), return_dtype=Int32) def CDLRICKSHAWMAN(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLRICKSHAWMAN(ndarray open, ndarray high, ndarray low, ndarray close) CDLRICKSHAWMAN(open, high, low, close) Rickshaw Man (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLRICKSHAWMAN), return_dtype=Int32) def CDLRISEFALL3METHODS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLRISEFALL3METHODS(ndarray open, ndarray high, ndarray low, ndarray close) CDLRISEFALL3METHODS(open, high, low, close) Rising/Falling Three Methods (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLRISEFALL3METHODS), return_dtype=Int32) def CDLSEPARATINGLINES(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSEPARATINGLINES(ndarray open, ndarray high, ndarray low, ndarray close) CDLSEPARATINGLINES(open, high, low, close) Separating Lines (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSEPARATINGLINES), return_dtype=Int32) def CDLSHOOTINGSTAR(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSHOOTINGSTAR(ndarray open, ndarray high, ndarray low, ndarray close) CDLSHOOTINGSTAR(open, high, low, close) Shooting Star (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSHOOTINGSTAR), return_dtype=Int32) def CDLSHORTLINE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSHORTLINE(ndarray open, ndarray high, ndarray low, ndarray close) CDLSHORTLINE(open, high, low, close) Short Line Candle (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSHORTLINE), return_dtype=Int32) def CDLSPINNINGTOP(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSPINNINGTOP(ndarray open, ndarray high, ndarray low, ndarray close) CDLSPINNINGTOP(open, high, low, close) Spinning Top (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSPINNINGTOP), return_dtype=Int32) def CDLSTALLEDPATTERN(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSTALLEDPATTERN(ndarray open, ndarray high, ndarray low, ndarray close) CDLSTALLEDPATTERN(open, high, low, close) Stalled Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSTALLEDPATTERN), return_dtype=Int32) def CDLSTICKSANDWICH(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLSTICKSANDWICH(ndarray open, ndarray high, ndarray low, ndarray close) CDLSTICKSANDWICH(open, high, low, close) Stick Sandwich (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLSTICKSANDWICH), return_dtype=Int32) def CDLTAKURI(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLTAKURI(ndarray open, ndarray high, ndarray low, ndarray close) CDLTAKURI(open, high, low, close) Takuri (Dragonfly Doji with very long lower shadow) (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLTAKURI), return_dtype=Int32) def CDLTASUKIGAP(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLTASUKIGAP(ndarray open, ndarray high, ndarray low, ndarray close) CDLTASUKIGAP(open, high, low, close) Tasuki Gap (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLTASUKIGAP), return_dtype=Int32) def CDLTHRUSTING(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLTHRUSTING(ndarray open, ndarray high, ndarray low, ndarray close) CDLTHRUSTING(open, high, low, close) Thrusting Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLTHRUSTING), return_dtype=Int32) def CDLTRISTAR(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLTRISTAR(ndarray open, ndarray high, ndarray low, ndarray close) CDLTRISTAR(open, high, low, close) Tristar Pattern (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLTRISTAR), return_dtype=Int32) def CDLUNIQUE3RIVER(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLUNIQUE3RIVER(ndarray open, ndarray high, ndarray low, ndarray close) CDLUNIQUE3RIVER(open, high, low, close) Unique 3 River (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLUNIQUE3RIVER), return_dtype=Int32) def CDLUPSIDEGAP2CROWS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLUPSIDEGAP2CROWS(ndarray open, ndarray high, ndarray low, ndarray close) CDLUPSIDEGAP2CROWS(open, high, low, close) Upside Gap Two Crows (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLUPSIDEGAP2CROWS), return_dtype=Int32) def CDLXSIDEGAP3METHODS(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['integer'] """CDLXSIDEGAP3METHODS(ndarray open, ndarray high, ndarray low, ndarray close) CDLXSIDEGAP3METHODS(open, high, low, close) Upside/Downside Gap Three Methods (Pattern Recognition) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: integer (values are -100, 0 or 100)""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.CDLXSIDEGAP3METHODS), return_dtype=Int32) def AVGPRICE(open: Expr, high: Expr, low: Expr, close: Expr) -> Expr: # ['real'] """AVGPRICE(ndarray open, ndarray high, ndarray low, ndarray close) AVGPRICE(open, high, low, close) Average Price (Price Transform) Inputs: prices: ['open', 'high', 'low', 'close'] Outputs: real""" return struct(f0=open, f1=high, f2=low, f3=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.AVGPRICE), return_dtype=Float64) def MEDPRICE(high: Expr, low: Expr) -> Expr: # ['real'] """MEDPRICE(ndarray high, ndarray low) MEDPRICE(high, low) Median Price (Price Transform) Inputs: prices: ['high', 'low'] Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.MEDPRICE), return_dtype=Float64) def TYPPRICE(high: Expr, low: Expr, close: Expr) -> Expr: # ['real'] """TYPPRICE(ndarray high, ndarray low, ndarray close) TYPPRICE(high, low, close) Typical Price (Price Transform) Inputs: prices: ['high', 'low', 'close'] Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.TYPPRICE), return_dtype=Float64) def WCLPRICE(high: Expr, low: Expr, close: Expr) -> Expr: # ['real'] """WCLPRICE(ndarray high, ndarray low, ndarray close) WCLPRICE(high, low, close) Weighted Close Price (Price Transform) Inputs: prices: ['high', 'low', 'close'] Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.WCLPRICE), return_dtype=Float64) def BETA(high: Expr, low: Expr, timeperiod: float = 5.0) -> Expr: # ['real'] """BETA(ndarray real0, ndarray real1, int timeperiod=-0x80000000) BETA(real0, real1[, timeperiod=?]) Beta (Statistic Functions) Inputs: real0: (any ndarray) real1: (any ndarray) Parameters: timeperiod: 5 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.BETA, timeperiod), return_dtype=Float64) def CORREL(high: Expr, low: Expr, timeperiod: float = 30.0) -> Expr: # ['real'] """CORREL(ndarray real0, ndarray real1, int timeperiod=-0x80000000) CORREL(real0, real1[, timeperiod=?]) Pearson's Correlation Coefficient (r) (Statistic Functions) Inputs: real0: (any ndarray) real1: (any ndarray) Parameters: timeperiod: 30 Outputs: real""" return struct(f0=high, f1=low).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.CORREL, timeperiod), return_dtype=Float64) def LINEARREG(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """LINEARREG(ndarray real, int timeperiod=-0x80000000) LINEARREG(real[, timeperiod=?]) Linear Regression (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LINEARREG, timeperiod), return_dtype=Float64) def LINEARREG_ANGLE(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """LINEARREG_ANGLE(ndarray real, int timeperiod=-0x80000000) LINEARREG_ANGLE(real[, timeperiod=?]) Linear Regression Angle (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LINEARREG_ANGLE, timeperiod), return_dtype=Float64) def LINEARREG_INTERCEPT(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """LINEARREG_INTERCEPT(ndarray real, int timeperiod=-0x80000000) LINEARREG_INTERCEPT(real[, timeperiod=?]) Linear Regression Intercept (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LINEARREG_INTERCEPT, timeperiod), return_dtype=Float64) def LINEARREG_SLOPE(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """LINEARREG_SLOPE(ndarray real, int timeperiod=-0x80000000) LINEARREG_SLOPE(real[, timeperiod=?]) Linear Regression Slope (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.LINEARREG_SLOPE, timeperiod), return_dtype=Float64) def STDDEV(close: Expr, timeperiod: float = 5.0, nbdev: float = 1.0) -> Expr: # ['real'] """STDDEV(ndarray real, int timeperiod=-0x80000000, double nbdev=-4e37) STDDEV(real[, timeperiod=?, nbdev=?]) Standard Deviation (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 5 nbdev: 1.0 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.STDDEV, timeperiod, nbdev), return_dtype=Float64) def TSF(close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """TSF(ndarray real, int timeperiod=-0x80000000) TSF(real[, timeperiod=?]) Time Series Forecast (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 14 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.TSF, timeperiod), return_dtype=Float64) def VAR(close: Expr, timeperiod: float = 5.0, nbdev: float = 1.0) -> Expr: # ['real'] """VAR(ndarray real, int timeperiod=-0x80000000, double nbdev=-4e37) VAR(real[, timeperiod=?, nbdev=?]) Variance (Statistic Functions) Inputs: real: (any ndarray) Parameters: timeperiod: 5 nbdev: 1.0 Outputs: real""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _ta.VAR, timeperiod, nbdev), return_dtype=Float64) def ATR(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """ATR(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) ATR(high, low, close[, timeperiod=?]) Average True Range (Volatility Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.ATR, timeperiod), return_dtype=Float64) def NATR(high: Expr, low: Expr, close: Expr, timeperiod: float = 14.0) -> Expr: # ['real'] """NATR(ndarray high, ndarray low, ndarray close, int timeperiod=-0x80000000) NATR(high, low, close[, timeperiod=?]) Normalized Average True Range (Volatility Indicators) Inputs: prices: ['high', 'low', 'close'] Parameters: timeperiod: 14 Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.NATR, timeperiod), return_dtype=Float64) def TRANGE(high: Expr, low: Expr, close: Expr) -> Expr: # ['real'] """TRANGE(ndarray high, ndarray low, ndarray close) TRANGE(high, low, close) True Range (Volatility Indicators) Inputs: prices: ['high', 'low', 'close'] Outputs: real""" return struct(f0=high, f1=low, f2=close).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3, dtype=float), _ta.TRANGE), return_dtype=Float64) def AD(high: Expr, low: Expr, close: Expr, volume: Expr) -> Expr: # ['real'] """AD(ndarray high, ndarray low, ndarray close, ndarray volume) AD(high, low, close, volume) Chaikin A/D Line (Volume Indicators) Inputs: prices: ['high', 'low', 'close', 'volume'] Outputs: real""" return struct(f0=high, f1=low, f2=close, f3=volume).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.AD), return_dtype=Float64) def ADOSC(high: Expr, low: Expr, close: Expr, volume: Expr, fastperiod: float = 3.0, slowperiod: float = 10.0) -> Expr: # ['real'] """ADOSC(ndarray high, ndarray low, ndarray close, ndarray volume, int fastperiod=-0x80000000, int slowperiod=-0x80000000) ADOSC(high, low, close, volume[, fastperiod=?, slowperiod=?]) Chaikin A/D Oscillator (Volume Indicators) Inputs: prices: ['high', 'low', 'close', 'volume'] Parameters: fastperiod: 3 slowperiod: 10 Outputs: real""" return struct(f0=high, f1=low, f2=close, f3=volume).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _ta.ADOSC, fastperiod, slowperiod), return_dtype=Float64) def OBV(close: Expr, volume: Expr) -> Expr: # ['real'] """OBV(ndarray real, ndarray volume) OBV(real, volume) On Balance Volume (Volume Indicators) Inputs: real: (any ndarray) prices: ['volume'] Outputs: real""" return struct(f0=close, f1=volume).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2, dtype=float), _ta.OBV), return_dtype=Float64) ================================================ FILE: polars_ta/tdx/README.md ================================================ # polars_ta.tdx 1. Follows the `tdx` naming convention 2. Except for some element-wise functions, all functions are time-series functions. Pay special attention to `MAX` and similar functions. 3. First import from `tdx`, then from `wq`, and finally from `ta`. Only implement the function if it is not available. 1. 函数名称按照通达信来 2. 除了部分按元素计算的函数,其它都为时序函数,特别注意`MAX`等一类不要混淆 3. 优先从`tdx`中导入,然后从`wq`中导入,最后从`ta`,没有的才实现 ================================================ FILE: polars_ta/tdx/__init__.py ================================================ from polars_ta.tdx.arithmetic import * # noqa from polars_ta.tdx.choice import * # noqa from polars_ta.tdx.energy import * # noqa from polars_ta.tdx.logical import * # noqa from polars_ta.tdx.moving_average import * # noqa from polars_ta.tdx.over_bought_over_sold import * # noqa from polars_ta.tdx.pattern import * # noqa from polars_ta.tdx.pattern_feature import * # noqa from polars_ta.tdx.pressure_support import * # noqa from polars_ta.tdx.reference import * # noqa from polars_ta.tdx.statistic import * # noqa from polars_ta.tdx.times import * # noqa from polars_ta.tdx.trend import * # noqa from polars_ta.tdx.trend_feature import * # noqa from polars_ta.tdx.volume import * # noqa ================================================ FILE: polars_ta/tdx/_chip.py ================================================ import numba import numpy as np @numba.jit(nopython=True, nogil=True, fastmath=True, cache=True) def nb_chip(high, low, avg, turnover, start=None, stop=None, step=0.2): """筹码分布,可用于WINNER或COST指标 不可能完全还原真实的筹码分布,只能接近。所以做了一下特别处理 1. 三角分布,比平均分布更接近 2. 步长。没有必要每个价格都统计,特别是复权后价格也无法正好是0.01间隔 高价股建议步长设大些,低价股步长需设小些 Parameters ---------- high low avg 一维序列 turnover: 换手率,需要在外转成0~1范围内 start 开始价格 stop 结束价格 step 步长。一字涨停时,三角分布的底为1,高为2。但无法当成梯形计算面积,所以从中用半步长切开计算 Returns ------- out 筹码分布 columns 价格表头 """ # 网格范围 if start is None: start = np.min(low) if stop is None: stop = np.max(high) left = round(start / step) * 2 - 1 right = round(stop / step) * 2 + 1 # 最小最大值左右要留半格,range是左闭右开,长度必须为2n+1 columns = np.arange(left, right + 1) grid_shape = (len(turnover), len(columns)) # numba中round写法特殊 _high = np.empty_like(high) _low = np.empty_like(low) _avg = np.empty_like(avg) # high和low必须落到边缘上 _high = np.round(high / step, 0, _high) * 2 + 1 _low = np.round(low / step, 0, _low) * 2 - 1 # avg必须落在实体上 _avg = np.round(avg / step, 0, _avg) * 2 tri_height = 2 / ((_high - _low) // 2) # 三角形高度 # 得到三组值在网格中的位置 high_arg = np.argwhere(columns == _high.reshape(-1, 1))[:, 1] avg_arg = np.argwhere(columns == _avg.reshape(-1, 1))[:, 1] low_arg = np.argwhere(columns == _low.reshape(-1, 1))[:, 1] # 高度表 height = np.zeros(grid_shape) for i in range(len(height)): la = low_arg[i] aa = avg_arg[i] ha = high_arg[i] th = tri_height[i] height[i, la:aa + 1] = np.linspace(0, th, aa - la + 1) height[i, aa:ha + 1] = np.linspace(th, 0, ha - aa + 1) # 计算半块面积, 三角形的高变成了梯形的上下底,梯形高固定为0.5,*0.5/2=/4 # 宽度-1,例如,原长度为5,-1后为4 area = (height[:, :-1] + height[:, 1:]) / 4 # 合成一块。宽度/2,例如原长度为4,/2后为2 weight = area[:, ::2] + area[:, 1::2] # 输出 out = np.zeros_like(weight) # 剩余换手率 turnover2 = 1 - turnover # 第一天其实应当用上市发行价,过于麻烦,还是将第一天等权 # 取巧方法,利用-1的特性,可减少if判断, out[-1] = weight[0] # 这里现在用的numpy, 还要快可考虑numba for i in range(len(turnover)): out[i] = out[i - 1] * turnover2[i] + weight[i] * turnover[i] # print(out.sum(axis=1)) return out, (step / 2) * columns[1::2] @numba.jit(nopython=True, nogil=True, fastmath=True, cache=True) def _WINNER_COST(high, low, avg, turnover, close, cost, step): out, columns = nb_chip(high, low, avg, turnover, step=step) # WINNER cheap = np.where(columns <= close.reshape(-1, 1), out, 0) sum_cheap = np.sum(cheap, axis=1) # COST # cum = np.cumsum(out, axis=1) cum = np.copy(out) for i in range(0, out.shape[0]): cum[i, :] = np.cumsum(out[i, :]) prices = np.where(cum <= cost.reshape(-1, 1), columns, 0) # np.max(prices, axis=1) max_price = prices[:, 0] for i in range(0, out.shape[0]): max_price[i] = np.max(prices[i, :]) return sum_cheap, max_price ================================================ FILE: polars_ta/tdx/_nb.py ================================================ import numpy as np from numba import jit from numpy import mean, abs, full, argmax from numpy.lib.stride_tricks import sliding_window_view @jit(nopython=True, nogil=True, cache=True) def roll_avedev(x1, window): out = full(x1.shape, np.nan, dtype=np.float64) if len(x1) < window: return out a1 = sliding_window_view(x1, window) for i, v1 in enumerate(a1): out[i + window - 1] = mean(abs(v1 - mean(v1))) return out @jit(nopython=True, nogil=True, fastmath=True, cache=True) def roll_bars_since_n(x1, window): """BARSSINCEN(X,N): the distance of the first observation that `X != 0` in `N` periods BARSSINCEN(X,N):N周期内第一次X不为0到现在的天数 TODO what if all values are 0? TODO 如果一个周期内,都不满足,值取多少?0表当前值满足条件, window-1表示的是区间第0位置的值 TODO 用window来表示都不满足 """ out = full(x1.shape, np.nan, dtype=np.float64) if len(x1) < window: return out a1 = sliding_window_view(x1, window) for i, v1 in enumerate(a1): p = argmax(v1) out[i + window - 1] = window - 1 - p if p or v1[0] else window return out @jit(nopython=True, nogil=True, fastmath=True, cache=True) def _up_stat(a, d: int = 3): """T天N板,最稀疏为5天2板 最近几天涨停但当天没涨停也会有记录,如6天2板,所以要与涨停一起使用 """ out1 = full(a.shape, 0, dtype=np.int64) out2 = full(a.shape, 0, dtype=np.int64) out3 = full(a.shape, 0, dtype=np.int64) t = 0 # T天 n = 0 # N板 k = 0 # 连续False个数 f = True # 前面的False不处理 for i in range(0, a.shape[0]): if a[i]: k = 0 t += 1 n += 1 f = False out1[i] = t out2[i] = n out3[i] = k else: if f: continue k += 1 t += 1 if k > d: # 超过指定天数才会重置 t = 0 n = 0 out1[i] = t out2[i] = n out3[i] = k return out1, out2, out3 ================================================ FILE: polars_ta/tdx/_slow.py ================================================ from polars import Series, Expr def _avedev(x: Series) -> Series: # x is Series rather than Expr. Making this function slow # 可惜rolling_map后这里已经由Expr变成了Series return (x - x.mean()).abs().mean() def AVEDEV(close: Expr, timeperiod: int = 5) -> Expr: """Avoid using this function, it is slow. 平均绝对偏差 """ return close.rolling_map(_avedev, timeperiod) ================================================ FILE: polars_ta/tdx/arithmetic.py ================================================ """ 通过`import`直接导入或更名的函数 ```python from polars_ta.wq.arithmetic import abs_ as ABS # noqa from polars_ta.wq.arithmetic import add as ADD # noqa from polars_ta.wq.arithmetic import arc_cos as ACOS # noqa from polars_ta.wq.arithmetic import arc_sin as ASIN # noqa from polars_ta.wq.arithmetic import arc_tan as ATAN # noqa from polars_ta.wq.arithmetic import ceiling as CEILING # noqa from polars_ta.wq.arithmetic import cos as COS # noqa from polars_ta.wq.arithmetic import exp as EXP # noqa from polars_ta.wq.arithmetic import floor as FLOOR # noqa from polars_ta.wq.arithmetic import fraction as FRACPART # noqa from polars_ta.wq.arithmetic import log as LN # noqa # 自然对数 (log base e) from polars_ta.wq.arithmetic import log10 as LOG # noqa # 10为底的对数 (log base 10) from polars_ta.wq.arithmetic import max_ as MAX # noqa from polars_ta.wq.arithmetic import min_ as MIN # noqa from polars_ta.wq.arithmetic import mod as MOD # noqa from polars_ta.wq.arithmetic import power as POW # noqa from polars_ta.wq.arithmetic import reverse as REVERSE # noqa from polars_ta.wq.arithmetic import round_ as _round # noqa from polars_ta.wq.arithmetic import sign as SIGN # noqa from polars_ta.wq.arithmetic import sin as SIN # noqa from polars_ta.wq.arithmetic import sqrt as SQRT # noqa from polars_ta.wq.arithmetic import subtract as SUB # noqa from polars_ta.wq.arithmetic import tan as TAN # noqa from polars_ta.wq.transformational import int_ as INTPART # noqa ``` """ from polars import Expr from polars_ta.wq.arithmetic import abs_ as ABS # noqa from polars_ta.wq.arithmetic import add as ADD # noqa from polars_ta.wq.arithmetic import arc_cos as ACOS # noqa from polars_ta.wq.arithmetic import arc_sin as ASIN # noqa from polars_ta.wq.arithmetic import arc_tan as ATAN # noqa from polars_ta.wq.arithmetic import ceiling as CEILING # noqa from polars_ta.wq.arithmetic import cos as COS # noqa from polars_ta.wq.arithmetic import exp as EXP # noqa from polars_ta.wq.arithmetic import floor as FLOOR # noqa from polars_ta.wq.arithmetic import fraction as FRACPART # noqa from polars_ta.wq.arithmetic import log as LN # noqa # 自然对数 (log base e) from polars_ta.wq.arithmetic import log10 as LOG # noqa # 10为底的对数 (log base 10) from polars_ta.wq.arithmetic import max_ as MAX # noqa from polars_ta.wq.arithmetic import min_ as MIN # noqa from polars_ta.wq.arithmetic import mod as MOD # noqa from polars_ta.wq.arithmetic import power as POW # noqa from polars_ta.wq.arithmetic import reverse as REVERSE # noqa from polars_ta.wq.arithmetic import round_ as _round # noqa from polars_ta.wq.arithmetic import sign as SIGN # noqa from polars_ta.wq.arithmetic import sin as SIN # noqa from polars_ta.wq.arithmetic import sqrt as SQRT # noqa from polars_ta.wq.arithmetic import subtract as SUB # noqa from polars_ta.wq.arithmetic import tan as TAN # noqa from polars_ta.wq.transformational import int_ as INTPART # noqa SGN = SIGN def ROUND(x: Expr) -> Expr: """Round input to closest integer.""" return _round(x, 0) def ROUND2(x: Expr, decimals: int = 0) -> Expr: """Round input to closest integer.""" return _round(x, decimals) def BETWEEN(a: Expr, b: Expr, c: Expr) -> Expr: """BETWEEN(A,B,C)表示A处于B和C之间时返回1,否则返回0""" x1 = (b <= a) & (a <= c) x2 = (c <= a) & (a <= b) return x1 | x2 ================================================ FILE: polars_ta/tdx/choice.py ================================================ from polars import Boolean from polars import Expr from polars import when from polars_ta.wq.logical import if_else def IF(condition: Expr, a: Expr, b: Expr) -> Expr: """A if X != 0 else B IF(X,A,B)若X不为0则返回A,否则返回B """ return if_else(condition.cast(Boolean), a, b) def IFN(condition: Expr, a: Expr, b: Expr) -> Expr: """B if X != 0 else A IFN(X,A,B)若X不为0则返回B,否则返回A """ return if_else(condition.cast(Boolean), b, a) def VALUEWHEN(condition: Expr, x: Expr) -> Expr: """VALUEWHEN(COND,X) 当COND条件成立时,取X的当前值,否则取VALUEWHEN的上个值. """ return when(condition).then(x).otherwise(None).forward_fill() IFF = IF ================================================ FILE: polars_ta/tdx/energy.py ================================================ from polars import Expr from polars_ta import TA_EPSILON from polars_ta.ta.price import MEDPRICE from polars_ta.tdx.reference import MA from polars_ta.tdx.reference import MAX from polars_ta.tdx.reference import REF from polars_ta.tdx.reference import SUM def BRAR_AR(OPEN: Expr, HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 26) -> Expr: """ BR:SUM(MAX(0,HIGH-REF(CLOSE,1)),N)/SUM(MAX(0,REF(CLOSE,1)-LOW),N)*100; AR:SUM(HIGH-OPEN,N)/SUM(OPEN-LOW,N)*100; """ AR = SUM(HIGH - OPEN, N) / (SUM(OPEN - LOW, N) + TA_EPSILON) # * 100 return AR def BRAR_BR(OPEN: Expr, HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 26) -> Expr: """ BR:SUM(MAX(0,HIGH-REF(CLOSE,1)),N)/SUM(MAX(0,REF(CLOSE,1)-LOW),N)*100; AR:SUM(HIGH-OPEN,N)/SUM(OPEN-LOW,N)*100; """ LC = REF(CLOSE, 1) BR = SUM(MAX(0, HIGH - LC), N) / (SUM(MAX(0, LC - LOW), N) + TA_EPSILON) # * 100 return BR def CR(HIGH: Expr, LOW: Expr, N: int = 26) -> Expr: """ MID:=REF(HIGH+LOW,1)/2; CR:SUM(MAX(0,HIGH-MID),N)/SUM(MAX(0,MID-LOW),N)*100; MA1:REF(MA(CR,M1),M1/2.5+1); MA2:REF(MA(CR,M2),M2/2.5+1); MA3:REF(MA(CR,M3),M3/2.5+1); MA4:REF(MA(CR,M4),M4/2.5+1); """ MID = REF(MEDPRICE(HIGH, LOW), 1) return SUM(MAX(0, HIGH - MID), N) / (SUM(MAX(0, MID - LOW), N) + TA_EPSILON) # *100 def PSY(CLOSE: Expr, N: int = 12) -> Expr: """ PSY:COUNT(CLOSE>REF(CLOSE,1),N)/N*100; PSYMA:MA(PSY,M); """ return MA(CLOSE > REF(CLOSE, 1), N) def MASS(HIGH: Expr, LOW: Expr, N1: int = 9, N2: int = 25) -> Expr: """ MASS:SUM(MA(HIGH-LOW,N1)/MA(MA(HIGH-LOW,N1),N1),N2); MAMASS:MA(MASS,M); """ MHL = MA(HIGH - LOW, N1) return SUM(MHL / MA(MHL, N1), N2) ================================================ FILE: polars_ta/tdx/logical.py ================================================ from polars import Expr from polars_ta import TA_EPSILON from polars_ta.tdx.reference import COUNT from polars_ta.wq.logical import not_ def CROSS(a: Expr, b: Expr) -> Expr: """上穿 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 1, 1, 2], 'b': [None, -1, 0, 1, 2], 'c': [None, 0, 0, 0, 0], 'd': [None, False, False, True, True], }).with_columns( out1=CROSS(pl.col('a'), pl.col('c')), out2=CROSS(pl.col('b'), pl.col('c')), out3=CROSS(0, pl.col('b')), out4=CROSS(pl.col('d'), 0.5), ) shape: (5, 8) ┌──────┬──────┬──────┬───────┬───────┬───────┬───────┬───────┐ │ a ┆ b ┆ c ┆ d ┆ out1 ┆ out2 ┆ out3 ┆ out4 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool │ ╞══════╪══════╪══════╪═══════╪═══════╪═══════╪═══════╪═══════╡ │ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ null │ │ -1 ┆ -1 ┆ 0 ┆ false ┆ false ┆ false ┆ null ┆ false │ │ 1 ┆ 0 ┆ 0 ┆ false ┆ true ┆ false ┆ false ┆ false │ │ 1 ┆ 1 ┆ 0 ┆ true ┆ false ┆ true ┆ false ┆ true │ │ 2 ┆ 2 ┆ 0 ┆ true ┆ false ┆ false ┆ false ┆ false │ └──────┴──────┴──────┴───────┴───────┴───────┴───────┴───────┘ ``` """ c = a < b - TA_EPSILON d = abs(a - b) < TA_EPSILON e = a > b + TA_EPSILON # 1. 小于大于 # 2. 小于等于大于 # 3. 小于等于等于大于。中间等了两期,暂时不定义为上穿 return e & (c.shift(1) | (d.shift(1) & c.shift(2))) def DOWNNDAY(close: Expr, N: int) -> Expr: """返回周期数内是否连跌""" return NDAY(close.shift(), close, N) def EVERY(condition: Expr, N: int) -> Expr: """一直存在""" return COUNT(condition, N) == N def EXIST(condition: Expr, N: int) -> Expr: """是否存在""" return COUNT(condition, N) > 0 def EXISTR(condition: Expr, a: int, b: int) -> Expr: """从前a日到前b日内是否存在""" # 不支持a参数为0,用一季来代替 if a == 0: a = 60 + b return EXIST(condition, a - b).shift(b) def LAST(condition: Expr, a: int, b: int) -> Expr: """从前a日到前b日内一直存在""" # 不支持a参数为0,用一季来代替 if a == 0: a = 60 + b return EVERY(condition, a - b).shift(b) def LONGCROSS(a: Expr, b: Expr, N: int) -> Expr: """两条线维持一定周期后交叉""" return CROSS(a, b) & EVERY(a < (b - TA_EPSILON), N).shift(1) def NDAY(close: Expr, open_: Expr, N: int) -> Expr: """返回是否持续存在X>Y""" return EVERY(close > (open_ + TA_EPSILON), N) def NOT(condition: Expr) -> Expr: """求逻辑非""" return not_(condition) def UPNDAY(close: Expr, N: int) -> Expr: """返回周期数内是否连涨""" return NDAY(close, close.shift(), N) # Eastmoney has these two functions # 东方财富中有此两函数 ALL = EVERY ANY = EXIST ================================================ FILE: polars_ta/tdx/moving_average.py ================================================ from polars import Expr from polars_ta.ta.price import AVGPRICE from polars_ta.tdx.reference import MA def BBI(CLOSE: Expr, M1: int = 3, M2: int = 6, M3: int = 12, M4: int = 20) -> Expr: """ BBI:(MA(CLOSE,M1)+MA(CLOSE,M2)+MA(CLOSE,M3)+MA(CLOSE,M4))/4; """ return AVGPRICE(MA(CLOSE, M1), MA(CLOSE, M2), MA(CLOSE, M3), MA(CLOSE, M4)) ================================================ FILE: polars_ta/tdx/over_bought_over_sold.py ================================================ from polars import Expr, struct from polars_ta import TA_EPSILON from polars_ta.ta.momentum import RSI # noqa from polars_ta.ta.momentum import RSV from polars_ta.ta.price import TYPPRICE from polars_ta.tdx.choice import IF from polars_ta.tdx.reference import DIFF from polars_ta.tdx.reference import MA from polars_ta.tdx.reference import REF from polars_ta.tdx.reference import SMA_CN from polars_ta.tdx.reference import SUM from polars_ta.tdx.reference import TR from polars_ta.tdx.statistic import AVEDEV def ATR(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14) -> Expr: """ Notes ----- Warning: it is different with talib.ATR 与talib.ATR不同 """ return MA(TR(HIGH, LOW, CLOSE), N) def BIAS(CLOSE: Expr, N: int = 6) -> Expr: """ BIAS1 :(CLOSE-MA(CLOSE,N1))/MA(CLOSE,N1)*100; BIAS2 :(CLOSE-MA(CLOSE,N2))/MA(CLOSE,N2)*100; BIAS3 :(CLOSE-MA(CLOSE,N3))/MA(CLOSE,N3)*100; """ return CLOSE / MA(CLOSE, N) - 1 # * 100 def CCI(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14) -> Expr: """ TYP:=(HIGH+LOW+CLOSE)/3; CCI:(TYP-MA(TYP,N))*1000/(15*AVEDEV(TYP,N)); Notes ----- Avoid using `AVEDEV`, it is slow AVEDEV计算慢,少用 """ TYP = TYPPRICE(HIGH, LOW, CLOSE) return (TYP - MA(TYP, N)) / (0.015 * AVEDEV(TYP, N)) def KDJ(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 9, M1: int = 3, M2: int = 3) -> Expr: """ RSV:=(CLOSE-LLV(LOW,N))/(HHV(HIGH,N)-LLV(LOW,N))*100; K:SMA(RSV,M1,1); D:SMA(K,M2,1); J:3*K-2*D; """ rsv = RSV(HIGH, LOW, CLOSE, N) k = SMA_CN(rsv, M1, 1) d = SMA_CN(k, M2, 1) j = k * 3 - d * 2 return struct(K=k, D=d, J=j) def MTM(CLOSE: Expr, N: int = 12) -> Expr: """ MTM:CLOSE-REF(CLOSE,MIN(BARSCOUNT(C),N)); MTMMA:MA(MTM,M); """ # return CLOSE - REF(CLOSE, N) return DIFF(CLOSE, N) def MFI(CLOSE: Expr, HIGH: Expr, LOW: Expr, VOL: Expr, N: int = 14) -> Expr: """ TYP := (HIGH + LOW + CLOSE)/3; V1:=SUM(IF(TYP>REF(TYP,1),TYP*VOL,0),N)/SUM(IF(TYP LT, TYP * VOL, 0), N) / (SUM(IF(TYP < LT, TYP * VOL, 0), N) + TA_EPSILON) return (1 - (1 / (1 + V1))) # * 100 ================================================ FILE: polars_ta/tdx/pattern.py ================================================ from polars import Expr, Struct, Field, Float64, struct from polars_ta.tdx._chip import _WINNER_COST from polars_ta.utils.numba_ import batches_i2_o2, struct_to_numpy def ts_WINNER_COST(high: Expr, low: Expr, avg: Expr, turnover: Expr, close: Expr, cost: Expr = 0.5, step: float = 0.1) -> Expr: """ 获利盘比例 WINNER(CLOSE),表示以当前收市价卖出的获利盘比例,例如返回0.1表示10%获利盘;WINNER(10.5)表示10.5元价格的获利盘比例 成本分布价 COST(10),表示10%获利盘的价格是多少,即有10%的持仓量在该价格以下,其余90%在该价格以上,为套牢盘 Parameters ---------- high 最高价 low 最低价 avg 平均价。可以用vwap turnover: 换手率。需要在外转成0~1范围内 close 判断获利比例的价格,可以用收盘价,也可以用均价 cost 成本比例,0~1 step 步长。一字涨停时,三角分布的底为1,高为2。但无法当成梯形计算面积,所以从中用半步长切开计算 Returns ------- winner 获利盘比例 cost 成本分布价 Examples -------- >>> WINNER_COST = ts_WINNER_COST(HIGH, LOW, VWAP, turnover_ratio / 100, CLOSE, 0.5) Notes ----- 该函数仅对日线分析周期有效 """ dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return struct(f0=high, f1=low, f2=avg, f3=turnover, f4=close, f5=cost).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, 6, dtype=float), _WINNER_COST, step), return_dtype=dtype) ================================================ FILE: polars_ta/tdx/pattern_feature.py ================================================ from polars import Expr from polars_ta.tdx.arithmetic import ABS, BETWEEN from polars_ta.tdx.arithmetic import MAX from polars_ta.tdx.arithmetic import MIN from polars_ta.tdx.choice import IF from polars_ta.tdx.logical import EVERY, CROSS, LAST, EXIST, NOT from polars_ta.tdx.reference import COUNT, BARSLASTCOUNT, BARSLAST, EMA, FILTER, LOWRANGE from polars_ta.tdx.reference import HHV from polars_ta.tdx.reference import LLV from polars_ta.tdx.reference import MA from polars_ta.tdx.reference import REF from polars_ta.wq.time_series import ts_cum_max, ts_cum_min def 早晨之星(OPEN: Expr, CLOSE: Expr) -> Expr: """MSTAR 早晨之星""" A1 = REF(CLOSE, 2) / REF(OPEN, 2) < 0.95 A2 = REF(OPEN, 1) < REF(CLOSE, 2) A3 = ABS(REF(OPEN, 1) - REF(CLOSE, 1)) / REF(CLOSE, 1) < 0.03 A4 = CLOSE / OPEN > 1.05 A5 = CLOSE > REF(CLOSE, 2) return A1 & A2 & A3 & A4 & A5 def 剑(OPEN: Expr, HIGH: Expr, LOW: Expr, CLOSE: Expr, VOL: Expr, CAPITAL: Expr) -> Expr: """SWORD 剑""" AA = (VOL > REF(VOL, 1)) | (VOL > CAPITAL) BB = (OPEN >= REF(HIGH, 1)) & (REF(HIGH, 1) > REF(HIGH, 2) * 1.06) CC = CLOSE > (REF(CLOSE, 1) - REF(CLOSE, 1) * 0.01) DD = (CLOSE < HIGH * 0.965) & (HIGH > OPEN * 1.05) EE = (LOW < OPEN) & (LOW < CLOSE) & (HIGH > REF(CLOSE, 1) * 1.06) FF = (HIGH - MAX(OPEN, CLOSE)) / 2 > (MIN(OPEN, CLOSE) - LOW) GG = (ABS(OPEN - CLOSE)) / 2 < (MIN(OPEN, CLOSE) - LOW) return AA & BB & CC & DD & EE & FF & GG def 天量法则(OPEN: Expr, CLOSE: Expr) -> Expr: """TLFZ 天量法则""" A1 = CLOSE > OPEN A2 = HHV(CLOSE, 50) == CLOSE # DYNAINFO(37) > 0.1 & & # DYNAINFO(13) < 0.14; raise def 四串阴(OPEN: Expr, CLOSE: Expr) -> Expr: """GREEN4 四串阴""" return EVERY(CLOSE < OPEN, 4) def 四串阳(OPEN: Expr, CLOSE: Expr) -> Expr: """RED4 四串阳""" return EVERY(CLOSE > OPEN, 4) def 鸳鸯底(O: Expr, LOW: Expr, C: Expr, V: Expr, N: int = 50) -> Expr: """YYD 鸳鸯底""" return (C > O) & (REF(C, 1) < REF(O, 1)) & (C > REF(O, 1)) & (V > REF(V, 1)) & EXIST(LOWRANGE(LOW) > N, 3) def 出水芙蓉(OPEN: Expr, CLOSE: Expr, S: int = 20, M: int = 40, N: int = 60) -> Expr: """CSFR 出水芙蓉""" AAA = CLOSE > OPEN BBB = AAA & (CLOSE > MA(CLOSE, S)) & (CLOSE > MA(CLOSE, M)) & (CLOSE > MA(CLOSE, N)) CCC = BBB & (OPEN < MA(CLOSE, M)) & (OPEN < MA(CLOSE, N)) return CCC & (CLOSE - OPEN > 0.0618 * CLOSE) def 出水芙蓉II(C: Expr, V: Expr, N: float = 0.05, M: float = 2.0) -> Expr: """CSFR2 出水芙蓉II""" ZF = C / REF(C, 1) FLTJ = V > REF(V, 1) * M A1 = CROSS(C, MA(C, 5)) & CROSS(C, MA(C, 10)) & CROSS(C, MA(C, 20)) & CROSS(C, MA(C, 60)) return (ZF > 1 + N) & FLTJ & A1 def 近日创历史新高(HIGH: Expr, N: int = 3, M: int = 0) -> Expr: """NHIGH 近日创历史新高""" if M == 0: return HHV(HIGH, N) == ts_cum_max(HIGH) else: return HHV(HIGH, N) == HHV(HIGH, M) def 近日创历史新低(LOW: Expr, N: int = 3, M: int = 0) -> Expr: """NLOW 近日创历史新低""" if M == 0: return LLV(LOW, N) == ts_cum_min(LOW) else: return LLV(LOW, N) == LLV(LOW, M) def 旭日初升(CLOSE: Expr, VOL: Expr, N: int = 120) -> Expr: """XRDS 旭日初升""" BUY1 = LAST(CLOSE < MA(CLOSE, N), 0, 5) return (CLOSE > MA(CLOSE, N)) & (VOL > MA(VOL, 5) * 2) & BUY1 def 蜻蜓点水(CLOSE: Expr, N: int = 120) -> Expr: """QTDS 蜻蜓点水""" BUY1 = LAST(CLOSE > MA(CLOSE, N), 0, 5) BUY2 = EXIST(CLOSE < MA(CLOSE, N), 5) return (CLOSE > MA(CLOSE, N)) & BUY1 & BUY2 def 均线多头排列(OPEN: Expr, CLOSE: Expr, N: int = 5, N1: int = 10, N2: int = 20, N3: int = 30) -> Expr: """DTPL 均线多头排列""" A1 = MA(CLOSE, N) A2 = MA(CLOSE, N1) A3 = MA(CLOSE, N2) A4 = MA(CLOSE, N3) return (CLOSE > A1) & (A1 > A2) & (A2 > A3) & (A3 > A4) & (CLOSE > OPEN) def 均线空头排列(OPEN: Expr, CLOSE: Expr, N: int = 5, N1: int = 10, N2: int = 20, N3: int = 30) -> Expr: """KTPL 均线空头排列""" A1 = MA(CLOSE, N) A2 = MA(CLOSE, N1) A3 = MA(CLOSE, N2) A4 = MA(CLOSE, N3) return (CLOSE < A1) & (A1 < A2) & (A2 < A3) & (A3 < A4) & (CLOSE < OPEN) def 强势整理(OPEN: Expr, CLOSE: Expr, N: int = 2, M: float = 0.05) -> Expr: """QSZL 强势整理""" A1 = ABS(CLOSE - OPEN) / OPEN < 0.015 A2 = COUNT(A1, N) == N A3 = (REF(OPEN, N) < REF(CLOSE, N)) & (REF(CLOSE, N) / REF(CLOSE, N + 1) > 1 + M) return A2 & A3 def 高开大阴线(OPEN: Expr, CLOSE: Expr, N: float = 0.06, M: float = 0.04) -> Expr: """W103 高开大阴线""" A1 = OPEN / REF(CLOSE, 1) >= 1 + M A2 = CLOSE / OPEN <= 1 - N return A1 & A2 def 低开大阳线(OPEN: Expr, CLOSE: Expr, N: float = 0.06, M: float = 0.04) -> Expr: """W104 低开大阳线""" A1 = OPEN / REF(CLOSE, 1) <= 1 - M A2 = CLOSE / OPEN >= 1 + N return A1 & A2 def 跳空缺口选股(HIGH: Expr, LOW: Expr) -> Expr: """W105 跳空缺口选股""" A1 = HIGH < (REF(LOW, 1) - 0.001) A2 = LOW > (REF(HIGH, 1) + 0.001) return A1 | A2 def 单阳不破选股(O: Expr, H: Expr, L: Expr, C: Expr, N1: int = 2, N2: int = 7) -> Expr: """W106 单阳不破选股 """ A0 = ((C > O * 1.08) | (C > REF(C, 1) * 1.08)) & NOT(H == L) & NOT((H == C) & (H == O)) # {大阳超8%,排除当天一字、T字板} A1 = A0 & BARSLASTCOUNT(A0) == 1 A2 = BARSLAST(A1) # {距离大阳几根K} ZCX = REF(O, A2) # {获取大阳位置的开盘价作为支撑线} ZHX = REF(C, A2) # {获取大阳位置的收盘价作为选股最高区间} ZD = LLV(L, A2) # {大阳之后的最低价} ZH = HHV(H, A2) # {大阳之后的最高价} A3 = BARSLASTCOUNT(ZD >= ZCX) return (A3 <= N2) & (A3 > N1) & BETWEEN(C, ZCX, ZHX) & (ZH < ZHX) & (A2 > 0) def 回补跳空向上缺口(O: Expr, H: Expr, L: Expr, C: Expr, N1: int = 2, N2: int = 7) -> Expr: """W107 回补跳空向上缺口""" raise def 揉搓线(O: Expr, H: Expr, L: Expr, C: Expr, V: Expr, N: int = 50) -> Expr: """RUBLINE 揉搓线 """ A1 = (REF(H, 1) - MAX(REF(C, 1), REF(O, 1))) / (REF(H, 1) - REF(L, 1)) * 100 > N # {上影线占K线的N % 以上} A2 = (MIN(O, C) - L) / (H - L) * 100 > N # {下影线占K线的N % 以上} A3 = ABS(C - REF(C, 1)) / MIN(C, REF(C, 1)) * 100 < 2 # {下影K的跌幅不能超过2 %} A4 = REF(C, 2) > REF(C, 3) # {揉搓形态前收涨} A5 = V < REF(V, 1) # {缩量} return A1 & A2 & A3 & A4 & A5 def 老鸭头(L: Expr, C: Expr, V: Expr) -> Expr: """OLDDUCK 老鸭头 """ E1 = EMA(C, 13) E2 = EMA(C, 55) A1 = (COUNT(E1 < REF(E1, 1), 5) >= 3) & (E1 > REF(E1, 1)) A2 = (COUNT(E2 > REF(E2, 1), 13) >= 8) & (E2 < REF(E2, 1)) A3 = LLV((L / E2 - 1), 13) <= 0.1 A4 = COUNT(E1 > E2, 13) == 13 A5 = COUNT(C > E2, 5) == 5 A6 = CROSS(C, E1) A7 = V > MA(V, 5) YT = A1 & A2 & A3 & A4 & A5 & A6 & A7 LYT = FILTER(YT, 10) # FXG:=FINANCE(42)>100; # NTP:=DYNAINFO(8)>0; # LYT AND FXG AND NTP; return LYT def 仙人指路(O: Expr, H: Expr, C: Expr) -> Expr: """WISEWAY 仙人指路 """ TUPO = 0.5 * (C + H) > HHV(REF(C, 1), 60) SSQS = (MA(C, 5) > MA(C, 60)) & (MA(C, 10) > MA(C, 60)) YINX = ((H - MAX(O, C)) / REF(C, 1) > 0.045) & (ABS(C - O) / REF(C, 1) < 0.035) & ((H - MAX(O, C)) > 2.0 * ABS(C - O)) QIANG1 = (REF(C, 1) / REF(C, 6) > 1.04) & (REF(C, 1) / REF(C, 6) < 1.18) QIANG2 = (REF(C, 1) / REF(C, 6) > 1.04) & (REF(C, 1) / REF(C, 6) < 1.27) QIANG = IF(True, QIANG2, QIANG1) # TODO 要改 QIANK = (REF((H - C), 1) / REF(C, 2) < 1.045) & (REF(C, 1) / REF(C, 2) > 0.97) return TUPO & SSQS & YINX & QIANG & QIANK def 低点搜寻(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 5) -> Expr: """SP 低点搜寻""" W = MA((LLV(LOW, 45) - CLOSE) / (HHV(HIGH, 45) - LLV(LOW, 45)), N) return CROSS(-0.05, W) def 突破(C: Expr, N1: int = 5, N2: int = 10, N3: int = 30) -> Expr: """TP 突破""" M1 = MA(C, N1) # {短期参数:5} M2 = MA(C, N2) # {中期参数:10} M3 = MA(C, N3) # {长期参数:30} # {以下计算交叉点距今的天数} D1 = BARSLAST(CROSS(M1, M2)) # {短上穿中} D2 = BARSLAST(CROSS(M1, M3)) # {短上穿长} D3 = BARSLAST(CROSS(M2, M3)) # {中上穿长} T1 = CROSS(M2, M3) # {今天中线上穿长线} T2 = (D1 >= D2) & (D2 >= D3) # {交叉按指定的先后出现} T3 = COUNT(CROSS(M2, M1) | CROSS(M3, M2) | CROSS(M3, M1), D1) == 0 # {中间无夹杂其它交叉} T4 = REF((M1 < M3) & (M2 < M3), D1 + 1) # {短上穿中前一天短、中线在长线之下} return T1 & T2 & T3 & T4 # {价托确定}; ================================================ FILE: polars_ta/tdx/pressure_support.py ================================================ from polars import Expr from polars_ta.tdx.arithmetic import SQRT from polars_ta.tdx.reference import MA from polars_ta.tdx.statistic import STDP def BOLL(close: Expr, M: int = 20, N: int = 2) -> Expr: """ BOLL:MA(CLOSE,M); UB:BOLL+2*STD(CLOSE,M); LB:BOLL-2*STD(CLOSE,M); """ ma = MA(close, M) # it should be total standard deviation, the value is smaller than sample standard deviation. # 这里是总体标准差,值比样本标准差小。部分软件使用样本标准差是错误的, std = STDP(close, M) return ma + std * N def BOLL_M(close: Expr, M: int = 20, N: int = 2) -> Expr: """ {参数 N: 2 250 20 } MID:=MA(C,N); VART1:=POW((C-MID),2); VART2:=MA(VART1,N); VART3:=SQRT(VART2); UPPER:=MID+2*VART3; LOWER:=MID-2*VART3; BOLL:REF(MID,1),COLORFFFFFF; UB:REF(UPPER,1),COLOR00FFFF; LB:REF(LOWER,1),COLORFF00FF; """ ma = MA(close, M) # 这里var不一样 # close-close.mean()与close-MA(close)的区别 std = SQRT(MA((close - ma) ** 2, M)) return ma + std * N ================================================ FILE: polars_ta/tdx/reference.py ================================================ """ 通过`import`直接导入或更名的函数 ```python from polars_ta.ta.overlap import SMA as MA from polars_ta.ta.volatility import TRANGE as TR # noqa from polars_ta.wq.arithmetic import max_ as MAX # noqa from polars_ta.wq.arithmetic import min_ as MIN # noqa from polars_ta.wq.time_series import ts_arg_max as HHVBARS # noqa from polars_ta.wq.time_series import ts_arg_min as LLVBARS # noqa from polars_ta.wq.time_series import ts_count as COUNT # noqa from polars_ta.wq.time_series import ts_decay_linear as WMA # noqa from polars_ta.wq.time_series import ts_delay as REF # noqa from polars_ta.wq.time_series import ts_delta as DIFF # noqa from polars_ta.wq.time_series import ts_max as HHV # noqa from polars_ta.wq.time_series import ts_min as LLV # noqa from polars_ta.wq.time_series import ts_product as MULAR # noqa from polars_ta.wq.time_series import ts_sum as SUM ``` """ from polars import Boolean, Int32, UInt16 from polars import Expr from polars import when from polars_ta.ta.overlap import EMA as _ema from polars_ta.ta.overlap import SMA as MA from polars_ta.ta.volatility import TRANGE as TR # noqa from polars_ta.tdx._nb import roll_bars_since_n from polars_ta.utils.numba_ import batches_i1_o1 from polars_ta.wq.arithmetic import max_ as MAX # noqa from polars_ta.wq.arithmetic import min_ as MIN # noqa from polars_ta.wq.time_series import ts_arg_max as HHVBARS # noqa from polars_ta.wq.time_series import ts_arg_min as LLVBARS # noqa from polars_ta.wq.time_series import ts_count as COUNT # noqa from polars_ta.wq.time_series import ts_decay_linear as WMA # noqa from polars_ta.wq.time_series import ts_delay as REF # noqa from polars_ta.wq.time_series import ts_delta as DIFF # noqa from polars_ta.wq.time_series import ts_max as HHV # noqa from polars_ta.wq.time_series import ts_min as LLV # noqa from polars_ta.wq.time_series import ts_product as MULAR # noqa from polars_ta.wq.time_series import ts_sum as SUM def BARSLAST(condition: Expr) -> Expr: """# of Observations since last time condition was true 上一次X不为0到现在的天数""" a = condition.cum_count() b = when(condition.cast(Boolean)).then(a).otherwise(None).forward_fill() return a - b def BARSLASTCOUNT(condition: Expr) -> Expr: """Cumulative count of continuous true observations 统计连续满足条件的周期数""" a = condition.cast(Int32).cum_sum() b = when(~condition.cast(Boolean)).then(a).otherwise(None).forward_fill().fill_null(0) return a - b def BARSSINCE(condition: Expr) -> Expr: """# of observations since the first time condition was true 第一次X不为0到现在的天数""" a = condition.cum_count() b = condition.cast(Boolean).arg_true().first() return a - b def BARSSINCEN(condition: Expr, N: int = 30) -> Expr: """# of Observations since the first time condition was true (rolling within N observations) N周期内第一次X不为0到现在的天数""" return condition.cast(Boolean).map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_bars_since_n, N, dtype=UInt16), return_dtype=UInt16) def CUMSUM(close: Expr) -> Expr: """SUM(close, 0)""" return close.cum_sum() def DMA(close: Expr, alpha: float = 0.5) -> Expr: """DMA(X,alpha), (Exponential moving average given alpha) Y = alpha * X + (1 - alpha) * last_Y requires 0 < alpha < 1 求X的动态移动平均. 算法:Y=A*X+(1-A)*Y',其中Y'表示上一周期Y值,A必须大于0且小于1.A支持变量""" return close.ewm_mean(alpha=alpha, adjust=False, min_samples=1) def EMA(close: Expr, N: int = 30) -> Expr: """EMA(X,N): Exponential moving average given N Y = X * 2/(N+1) + last_Y * (N-1)/(N+1) X的N日指数移动平均.算法:Y=(X*2+Y'*(N-1))/(N+1) EMA(X,N)相当于SMA(X,N+1,2),N支持变量""" return _ema(close, N) def EXPMA(close: Expr, N: int = 30) -> Expr: return _ema(close, N) def EXPMEMA(close: Expr, N: int = 30) -> Expr: """Slow version of EMA. Do not use it unless you have to EXPMEMA(X,M),X的M日指数平滑移动平均。EXPMEMA同EMA(即EXPMA)的差别在于他的起始值为一平滑值 Notes ----- 等价于talib.EMA,由于比EMA慢,少用 """ sma = MA(close, N) x = when(close.cum_count() < N).then(sma).otherwise(close) return x.ewm_mean(span=N, adjust=False, min_samples=1) def HOD(close: Expr, N: int = 30) -> Expr: """rolling rank of each data in descending order HOD(X,N):求当前X数据是N周期内的第几个高值,N=0则从第一个有效值开始""" return (-close).rolling_rank(N, method="min") def LOD(close: Expr, N: int = 30) -> Expr: """rolling rank of each data in ascending order LOD(X,N):求当前X数据是N周期内的第几个低值""" return close.rolling_rank(N, method="min") def LOWRANGE(close: Expr) -> Expr: """rolling rank of each data in ascending order LOD(X,N):求当前X数据是N周期内的第几个低值""" raise def MEMA(close: Expr, N: int = 30) -> Expr: """Exponential moving average given N Y = X / N + last_Y * (N-1) / N MEMA(X,N):X的N日平滑移动平均,如Y=(X+Y'*(N-1))/N MEMA(X,N)相当于SMA(X,N,1)""" raise def RANGE(a: Expr, b: Expr, c: Expr) -> Expr: """A在B和C范围之间,B Expr: """Exponential Moving average given alpha = M/N Y = X * M/N + last_Y * (N-M)/N 用法:SMA(X,N,M),X的N日移动平均,M为权重,若Y=SMA(X,N,M)则Y=(X*M+Y'*(N-M))/N !!!为防止与talib版SMA误用,这里去了默认值1 """ return X.ewm_mean(alpha=M / N, adjust=False, min_samples=1) def SUMIF(condition: Expr, close: Expr, N: int = 30) -> Expr: return SUM(condition.cast(Boolean).cast(Int32) * close, N) def TMA(close: Expr, N: int = 30) -> Expr: """TMA(X,A,B),A和B必须小于1,算法 Y=(A*Y'+B*X),其中Y'表示上一周期Y值.初值为X""" raise def FILTER(close: Expr, N: int = 30) -> Expr: raise def REFX(close: Expr, N: int = 30) -> Expr: """属于未来函数,引用若干周期后的数据""" return REF(close, -N) ================================================ FILE: polars_ta/tdx/statistic.py ================================================ from polars import Expr, Struct, Field, Int64, Float64 from polars_ta.tdx._nb import roll_avedev, _up_stat from polars_ta.utils.numba_ import batches_i1_o1, batches_i1_o2 from polars_ta.wq.time_series import ts_corr as RELATE # noqa from polars_ta.wq.time_series import ts_covariance as COVAR # noqa from polars_ta.wq.time_series import ts_std_dev as _ts_std_dev def AVEDEV(close: Expr, timeperiod: int = 5) -> Expr: """mean absolute deviation 平均绝对偏差""" return close.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_avedev, timeperiod), return_dtype=Float64) def DEVSQ(close: Expr, timeperiod: int = 5) -> Expr: raise def SLOPE(close: Expr, timeperiod: int = 5) -> Expr: raise def STD(close: Expr, timeperiod: int = 5) -> Expr: """std dev with ddof = 1 估算标准差""" return _ts_std_dev(close, timeperiod, 1) def STDDEV(close: Expr, timeperiod: int = 5) -> Expr: """标准偏差?""" raise def STDP(close: Expr, timeperiod: int = 5) -> Expr: """std dev with ddof = 0 总体标准差""" return _ts_std_dev(close, timeperiod, 0) def VAR(close: Expr, timeperiod: int = 5) -> Expr: return close.rolling_var(timeperiod, ddof=1) def VARP(close: Expr, timeperiod: int = 5) -> Expr: return close.rolling_var(timeperiod, ddof=0) def ts_up_stat(x: Expr) -> Expr: """T天N板统计,与通达信结果一样,最简为5天2板 Parameters ---------- x: Expr 布尔序列,True表示涨停 Returns ------- Expr 1. T天 2. N板 3. 离上次涨停距离 Examples -------- ```python df = pl.DataFrame({ 'a': [False, True, True, False, True, False, False, False, False, False], }).with_columns( out=ts_up_stat(pl.col('a')) ) ┌───────┬───────────┐ │ a ┆ out │ │ --- ┆ --- │ │ bool ┆ struct[3] │ ╞═══════╪═══════════╡ │ false ┆ {0,0,0} │ │ true ┆ {1,1,0} │ │ true ┆ {2,2,0} │ │ false ┆ {3,2,1} │ │ true ┆ {4,3,0} │ │ false ┆ {5,3,1} │ │ false ┆ {6,3,2} │ │ false ┆ {7,3,3} │ │ false ┆ {0,0,4} │ │ false ┆ {0,0,5} │ └───────┴───────────┘ ``` """ dtype = Struct([Field(f"column_{i}", Int64) for i in range(3)]) return x.map_batches(lambda x1: batches_i1_o2(x1.to_numpy(), _up_stat), return_dtype=dtype) ================================================ FILE: polars_ta/tdx/times.py ================================================ from datetime import time, timedelta from polars import Expr, when def FROMOPEN(t: Expr) -> Expr: """返回当前时刻距开盘有多少分钟 范围0~240,开盘前为0,10点为31 Examples -------- from datetime import datetime import polars as pl from polars_ta.tdx.times import FROMOPEN, FROMOPEN1 df = pl.DataFrame({'datetime': [ datetime(2025, 1, 1, 0, 0), datetime(2025, 1, 1, 9, 25), datetime(2025, 1, 1, 9, 30, 57), datetime(2025, 1, 1, 9, 31), datetime(2025, 1, 1, 10, 0), datetime(2025, 1, 1, 13, 0), ]}) df = df.with_columns( FROMOPEN=FROMOPEN(pl.col('datetime')), FROMOPEN1=FROMOPEN_1(pl.col('datetime'), 0), FROMOPEN2=FROMOPEN_1(pl.col('datetime'), 60), ) shape: (6, 4) ┌─────────────────────┬──────────┬───────────┬───────────┐ │ datetime ┆ FROMOPEN ┆ FROMOPEN1 ┆ FROMOPEN2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ datetime[μs] ┆ i64 ┆ i64 ┆ i64 │ ╞═════════════════════╪══════════╪═══════════╪═══════════╡ │ 2025-01-01 00:00:00 ┆ 0 ┆ 240 ┆ 240 │ │ 2025-01-01 09:25:00 ┆ 0 ┆ 1 ┆ 1 │ │ 2025-01-01 09:30:57 ┆ 1 ┆ 1 ┆ 2 │ │ 2025-01-01 09:31:00 ┆ 2 ┆ 2 ┆ 3 │ │ 2025-01-01 10:00:00 ┆ 31 ┆ 31 ┆ 32 │ │ 2025-01-01 13:00:00 ┆ 121 ┆ 121 ┆ 122 │ └─────────────────────┴──────────┴───────────┴───────────┘ """ am = (t.dt.time() - time(9, 29)).dt.total_minutes().clip(0, 120) pm = (t.dt.time() - time(12, 59)).dt.total_minutes().clip(0, 120) return am + pm def FROMOPEN_1(t: Expr, offset: int) -> Expr: """返回当前时刻距开盘有多少分钟。范围1~240 用于计算量比 1. 竞价量比,分母应当为1 2. 日线数据0~8点时,返回240 3. 日线数据9点时,返回1 Parameters ---------- t : Expr 时间列 offset : int 偏移量,单位秒 Notes ----- 每根K线结束时,标签是当前时间的50多秒,而结束时时间已经到下以分钟了,所以建议加60秒 """ return when(t.dt.time() >= time(8, 45)).then(FROMOPEN(t + timedelta(seconds=offset)).clip(1, 240)).otherwise(240) ================================================ FILE: polars_ta/tdx/trend.py ================================================ from polars import Expr from polars_ta import TA_EPSILON from polars_ta.tdx.arithmetic import ABS from polars_ta.tdx.choice import IF from polars_ta.tdx.reference import MA from polars_ta.tdx.reference import REF from polars_ta.tdx.reference import SUM from polars_ta.tdx.reference import TR def DPO(CLOSE: Expr, N: int = 20) -> Expr: """ DPO:CLOSE-REF(MA(CLOSE,N),N/2+1); MADPO:MA(DPO,M); """ return CLOSE - REF(MA(CLOSE, N), N // 2 + 1) def EMV(HIGH: Expr, LOW: Expr, VOL: Expr, N: int = 14) -> Expr: """ VOLUME:=MA(VOL,N)/VOL; MID:=100*(HIGH+LOW-REF(HIGH+LOW,1))/(HIGH+LOW); EMV:MA(MID*VOLUME*(HIGH-LOW)/MA(HIGH-LOW,N),N); MAEMV:MA(EMV,M); """ ADD = HIGH + LOW SUB = HIGH - LOW VOLUME = MA(VOL, N) / VOL MID = 100 * (ADD - REF(ADD, 1)) / ADD return MA(MID * VOLUME * SUB / (MA(SUB, N) + TA_EPSILON), N) def PLUS_DM(HIGH: Expr, LOW: Expr, N: int = 14) -> Expr: """ MTR:=SUM(MAX(MAX(HIGH-LOW,ABS(HIGH-REF(CLOSE,1))),ABS(REF(CLOSE,1)-LOW)),N); HD :=HIGH-REF(HIGH,1); LD :=REF(LOW,1)-LOW; DMP:=SUM(IF(HD>0&&HD>LD,HD,0),N); DMM:=SUM(IF(LD>0&&LD>HD,LD,0),N); """ HD = HIGH - REF(HIGH, 1) LD = REF(LOW, 1) - LOW DMP = SUM(IF((HD > 0) & (HD > LD), HD, 0), N) return DMP def MINUS_DM(HIGH: Expr, LOW: Expr, N: int = 14) -> Expr: HD = HIGH - REF(HIGH, 1) LD = REF(LOW, 1) - LOW DMM = SUM(IF((LD > 0) & (LD > HD), LD, 0), N) return DMM def PLUS_DI(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14) -> Expr: """ MTR:=SUM(MAX(MAX(HIGH-LOW,ABS(HIGH-REF(CLOSE,1))),ABS(REF(CLOSE,1)-LOW)),N); HD :=HIGH-REF(HIGH,1); LD :=REF(LOW,1)-LOW; DMP:=SUM(IF(HD>0&&HD>LD,HD,0),N); DMM:=SUM(IF(LD>0&&LD>HD,LD,0),N); PDI: DMP*100/MTR; MDI: DMM*100/MTR; """ MTR = SUM(TR(HIGH, LOW, CLOSE), N) DMP = PLUS_DM(HIGH, LOW, N) PDI = DMP / MTR return PDI # * 100 def MINUS_DI(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14) -> Expr: MTR = SUM(TR(HIGH, LOW, CLOSE), N) DMM = MINUS_DM(HIGH, LOW, N) MDI = DMM / MTR return MDI # * 100 def ADX(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14, M: int = 6) -> Expr: """ MTR:=SUM(MAX(MAX(HIGH-LOW,ABS(HIGH-REF(CLOSE,1))),ABS(REF(CLOSE,1)-LOW)),N); HD :=HIGH-REF(HIGH,1); LD :=REF(LOW,1)-LOW; DMP:=SUM(IF(HD>0&&HD>LD,HD,0),N); DMM:=SUM(IF(LD>0&&LD>HD,LD,0),N); PDI: DMP*100/MTR; MDI: DMM*100/MTR; ADX: MA(ABS(MDI-PDI)/(MDI+PDI)*100,M); ADXR:(ADX+REF(ADX,M))/2; """ MTR = SUM(TR(HIGH, LOW, CLOSE), N) HD = HIGH - REF(HIGH, 1) LD = REF(LOW, 1) - LOW DMP = SUM(IF((HD > 0) & (HD > LD), HD, 0), N) DMM = SUM(IF((LD > 0) & (LD > HD), LD, 0), N) PDI = DMP / MTR # * 100 MDI = DMM / MTR # * 100 return MA(ABS(MDI - PDI) / (MDI + PDI), M) # * 100 def ADXR(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 14, M: int = 6) -> Expr: adx = ADX(HIGH, LOW, CLOSE, N, M) adxr = (adx + REF(adx, M)) / 2 return adxr ================================================ FILE: polars_ta/tdx/trend_feature.py ================================================ from polars import Expr from polars_ta.tdx.arithmetic import ABS from polars_ta.tdx.logical import EXIST, EVERY from polars_ta.tdx.pattern import ts_WINNER_COST from polars_ta.tdx.reference import COUNT from polars_ta.tdx.reference import HHV from polars_ta.tdx.reference import LLV from polars_ta.tdx.reference import MA from polars_ta.tdx.reference import REF from polars_ta.tdx.reference import SUM from polars_ta.wq.time_series import ts_cum_max, ts_cum_min def N天内有跳空向上缺口(H: Expr, L: Expr, N: int = 1, M: float = 0.01) -> Expr: """C100 N天内有跳空向上缺口 Parameters ---------- N 天数 M 涨幅 """ XSTK = L > REF(H, 1) * (100 + M) return EXIST(XSTK, N) def N日内创新高(HIGH: Expr, N: int = 10) -> Expr: """C101 N日内创新高 """ return HHV(HIGH, N) == ts_cum_max(HIGH) def N日内创新低(LOW: Expr, N: int = 10) -> Expr: """C102 N日内创新低 """ return LLV(LOW, N) == ts_cum_min(LOW) def N日内阴线多于阳线(OPEN: Expr, CLOSE: Expr, N: int = 30, M: float = 0.6) -> Expr: """C103 M日内阴线多于阳线 Parameters ---------- N 天数 M 比例 """ return COUNT(OPEN > CLOSE, N) / N >= M def N日内阳线多于阴线(OPEN: Expr, CLOSE: Expr, N: int = 30, M: float = 0.6) -> Expr: """C104 M日内阳线多于阴线 Parameters ---------- N 天数 M 比例 """ return COUNT(OPEN < CLOSE, N) / N >= M def N日内上涨多于下跌(CLOSE: Expr, N: int = 120, M: float = 0.6) -> Expr: """C105 N日内上涨多于下跌 Parameters ---------- N 天数 M 比例 """ return COUNT(CLOSE > REF(CLOSE, 1), N) / N >= M def N日内下跌多于上涨(CLOSE: Expr, N: int = 120, M: float = 0.6) -> Expr: """C106 N日内下跌多于上涨 Parameters ---------- N 天数 M 比例 """ return COUNT(CLOSE < REF(CLOSE, 1), N) / N >= M def 连续N天收阳线(OPEN: Expr, CLOSE: Expr, N: int = 7) -> Expr: """C107 连续N天收阳线 Parameters ---------- N 天数 """ return EVERY(CLOSE > OPEN, N) def 连续N天收阴线(OPEN: Expr, CLOSE: Expr, N: int = 7) -> Expr: """C108 连续N天收阴线 Parameters ---------- N 天数 """ return EVERY(OPEN > CLOSE, N) def 单日放量(VOL: Expr, CAPITAL: Expr, N: float = 2, M: float = 0.15) -> Expr: """C110 单日放量 Parameters ---------- N 5日平均成交量的倍数 M 流通股本的倍数 """ A1 = MA(VOL, 5) A2 = REF(A1, 1) C1 = VOL / A2 > N C2 = VOL / CAPITAL > M return C1 & C2 def 阶段缩量(VOL: Expr, CAPITAL: Expr, N: int = 20, M: float = 0.02) -> Expr: """C111 阶段缩量 Parameters ---------- VOL 成交量 CAPITAL 流通股本 Notes ----- 成交量与流通股本单位要一致,都为手,或者都为股 """ return SUM(VOL, N) / CAPITAL <= M def 阶段放量(VOL: Expr, CAPITAL: Expr, N: int = 10, M: float = 2.0) -> Expr: """C112 阶段放量 Parameters ---------- VOL 成交量 CAPITAL 流通股本 """ return SUM(VOL, N) / CAPITAL >= M def 持续放量(VOL: Expr, M: int = 5) -> Expr: """C113 持续放量 Parameters ---------- VOL 成交量 """ return EVERY(VOL >= REF(VOL, 1), M) def 持续缩量(VOL: Expr, M: int = 5) -> Expr: """C114 持续缩量 Parameters ---------- VOL 成交量 """ return COUNT(VOL <= REF(VOL, 1), M) == M def 间隔放量(VOL: Expr, N: int = 30, N1: float = 4.0, N2: float = 2.0, N3: int = 3) -> Expr: """C115 间隔放量 Parameters ---------- VOL 成交量 N 均量周期 N1 最小均量与最大均量的倍数 N2 成交量与均量的倍数 N3 满足N2时的次数 """ A = MA(VOL, 5) C1 = HHV(A, N) < N1 * LLV(A, N) # 成交量最大与最小在一定范围内 C2 = COUNT(VOL > N2 * A, N) > N3 # 成交量大于均量一定倍数 return C1 & C2 def 放量上攻(CLOSE: Expr, VOL: Expr, CAPITAL: Expr, N: float = 0.01, N1: int = 3, N2: float = 0.2, N3: int = 4) -> Expr: """C116 放量上攻 Parameters ---------- CLOSE 复权收盘价 VOL 成交量 CAPITAL 流通股本 N 涨幅 N1, N2 N1天内成交量大于N2*流通股本 N3 连续N3天满足涨幅 """ A = (CLOSE - REF(CLOSE, 1)) / REF(CLOSE, 1) >= N C1 = SUM(VOL, N1) / CAPITAL >= N2 C2 = COUNT(VOL > REF(VOL, 1), N3) == N3 C3 = COUNT(A, N3) == N3 return C1 & C2 & C3 def 温和放量上攻(CLOSE: Expr, VOL: Expr, CAPITAL: Expr, N: int = 5) -> Expr: """C117 温和放量上攻 Parameters ---------- CLOSE 复权收盘价 VOL 成交量 CAPITAL 流通股本 N 观察天数 """ A1 = CLOSE / REF(CLOSE, 1) A2 = (A1 > 1) & (A1 < 1.03) # {股价小幅上扬} B1 = VOL / REF(VOL, 1) B2 = (B1 > 1) & (A1 < 2) # {成交量小幅上扬} C1 = MA(VOL, N) / CAPITAL < 0.05 # 日成交量小于流通股本的5% C2 = COUNT(A2 & B2, N) / N > 0.6 return C1 & C2 def 突然放量(VOL: Expr, N: int = 10, M: float = 3.0) -> Expr: """C118 突然放量 Parameters ---------- VOL 成交量 N 观察天数 M 倍数 """ return VOL > REF(HHV(VOL, N), 1) * M def 平台整理(CLOSE: Expr, N: int = 30, N1: float = 0.05) -> Expr: """C119 平台整理 Parameters ---------- N 天数 N1 幅度 """ return HHV(CLOSE, N) / LLV(CLOSE, N) <= 1 + N1 def 小步碎阳(O: Expr, H: Expr, L: Expr, C: Expr, avg: Expr, turnover_ratio: Expr, N: int = 4) -> Expr: """C120 小步碎阳 Parameters ---------- avg 成交均价 turnover_ratio 换手率 N 观察天数 """ AA = (C > REF(C, 1)) & (C > O) A1 = COUNT(AA, N) == N A2 = C / REF(C, N) < 1.05 A3 = ts_WINNER_COST(H, L, avg, turnover_ratio, C, 0.5).struct[0] > 0.75 return A1 & A2 & A3 def 突破长期盘整(HIGH: Expr, LOW: Expr, CLOSE: Expr, N: int = 30, N1: int = 5) -> Expr: """C123 突破长期盘整 Parameters ---------- N 天数 N1 涨幅 """ C1 = REF(HHV(HIGH, N) / LLV(LOW, N), 1) <= N1 + 1 C2 = CLOSE >= REF(HHV(HIGH, N), 1) return C1 & C2 def N天内出现以涨停收盘(收盘涨停: Expr, N: int = 10) -> Expr: """C128 N天内出现以涨停收盘 Parameters ---------- N 天数 """ return EXIST(收盘涨停, N) def N天内出现涨停(最高涨停: Expr, N: int = 20) -> Expr: """C129 N天内出现涨停 Parameters ---------- N 天数 """ return EXIST(最高涨停, N) def N天内经常涨停(收盘涨停: Expr, N: int = 100, M: int = 8) -> Expr: """C130 N天内经常涨停 Parameters ---------- N 天数 M 涨停天数 """ return COUNT(收盘涨停, N) >= M def 下跌多日再放量上涨(HIGH: Expr, CLOSE: Expr, VOL: Expr) -> Expr: """C131 下跌多日再放量上涨 """ A1 = REF(CLOSE, 5) > REF(CLOSE, 4) A2 = REF(CLOSE, 4) > REF(CLOSE, 3) A3 = REF(CLOSE, 3) > REF(CLOSE, 2) A4 = REF(CLOSE, 2) > REF(CLOSE, 1) A5 = (CLOSE > REF(HIGH, 1)) & (VOL > REF(VOL, 1)) return A1 & A2 & A3 & A4 & A5 def 跳空高开或低开(O: Expr, H: Expr, L: Expr, C: Expr, N: float = 0.03) -> Expr: """C132 跳空高开或低开 Parameters ---------- N 涨幅 """ if N > 0: A = (O > REF(H, 1)) & (O / REF(C, 1) > (1 + N)) return A else: B = (O < REF(L, 1)) & (O / REF(C, 1) < (1 + N)) return B def 拉升后多日调整(C: Expr, N: int = 3, ZF: float = 0.09) -> Expr: """C133 拉升后多日调整 Parameters ---------- N 天数 ZF 涨幅 """ C1 = REF(C, N) / REF(C, N + 1) > 1 + ZF C2 = EVERY(C < REF(C, 1), N) return C1 & C2 def 昨日底部十字星(O: Expr, H: Expr, L: Expr, C: Expr, N: int = 60) -> Expr: """C134 昨日底部十字星 Parameters ---------- N 天数 """ C1 = L <= LLV(L, N) C2 = ABS(C - O) / (H - L) < 0.05 C3 = H > L return REF(C1 & C2 & C3, 1) def 价量渐低后阳包阴(O: Expr, C: Expr, V: Expr) -> Expr: """C135 价量渐低后阳包阴 """ A1 = REF(C, 4) > REF(C, 3) A2 = REF(C, 3) > REF(C, 2) A3 = REF(C, 2) > REF(C, 1) B1 = REF(V, 3) > REF(V, 2) B2 = REF(V, 2) > REF(V, 1) AA1 = A1 & A2 & A3 & B1 & B2 AA2 = (REF(O, 1) > REF(C, 1)) & (C > REF(O, 1)) AA3 = (C > O) & (V < REF(V, 1)) return AA1 & AA2 & AA3 ================================================ FILE: polars_ta/tdx/volume.py ================================================ from polars import Expr from polars_ta.tdx.choice import IF from polars_ta.tdx.reference import REF from polars_ta.tdx.reference import SUM from polars_ta.tdx.reference import CUMSUM def OBV(CLOSE: Expr, VOL: Expr) -> Expr: """ VA:=IF(CLOSE>REF(CLOSE,1),VOL,-VOL); OBV:SUM(IF(CLOSE=REF(CLOSE,1),0,VA),0); MAOBV:MA(OBV,M); """ LC = REF(CLOSE, 1) VA = IF(CLOSE - LC, VOL, -VOL) return CUMSUM(IF(CLOSE == LC, 0, VA)) def VR(CLOSE: Expr, VOL: Expr, N: int = 26) -> Expr: """ TH:=SUM(IF(CLOSE>REF(CLOSE,1),VOL,0),N); TL:=SUM(IF(CLOSE LC, VOL, 0), N) TL = SUM(IF(CLOSE < LC, VOL, 0), N) TQ = SUM(IF(CLOSE == LC, VOL, 0), N) return (TH * 2 + TQ) / (TL * 2 + TQ) # * 100 ================================================ FILE: polars_ta/utils/__init__.py ================================================ ================================================ FILE: polars_ta/utils/factor.py ================================================ """ 复权算法 1. 针对现金分红进行复权(使用加减法) 2. 针对拆股进行复权(使用乘除法) factor1: 稀疏的复权因子 factor2: 稠密的复权因子,之后主要使用这个 复权因子使用方法 乘除复权法:factor2*close 加减复权法:factor2+close """ import polars as pl def calc_factor_muldiv(df: pl.DataFrame, by1: str = 'asset', by2: str = 'date', close: str = 'close', pre_close: str = 'pre_close') -> pl.DataFrame: """计算复权因子,乘除法。使用交易所发布的昨收盘价计算 Parameters ---------- df : pl.DataFrame 数据 by1 : str 分组字段 by2 : str 排序字段 close : str 收盘价字段 pre_close : str 昨收盘价字段 Notes ----- 不关心是否真发生了除权除息过程,只要知道前收盘价和收盘价不等就表示发生了除权除息 """ df = ( df .sort(by1, by2) .with_columns(factor1=(pl.col(close).shift(1, fill_value=pl.first(pre_close)) / pl.col(pre_close)).round(8).over(by1, order_by=by2)) .with_columns(factor2=(pl.col('factor1').cum_prod()).over(by1, order_by=by2)) ) return df def calc_factor_addsub(df: pl.DataFrame, by1: str = 'asset', by2: str = 'date', close: str = 'close', pre_close: str = 'pre_close') -> pl.DataFrame: """计算复权因子,加减法。使用交易所发布的昨收盘价计算 Parameters ---------- df : pl.DataFrame 数据 by1 : str 分组字段 by2 : str 排序字段 close : str 收盘价字段 pre_close : str 昨收盘价字段 Notes ----- 不关心是否真发生了除权除息过程,只要知道前收盘价和收盘价不等就表示发生了除权除息 """ df = ( df .sort(by1, by2) .with_columns(factor1=(pl.col(close).shift(1, fill_value=pl.first(pre_close)) - pl.col(pre_close)).round(8).over(by1, order_by=by2)) .with_columns(factor2=(pl.col('factor1').cum_sum()).over(by1, order_by=by2)) ) return df ================================================ FILE: polars_ta/utils/functions.py ================================================ import inspect from functools import wraps from inspect import currentframe import polars as pl import polars_ta def const_to_expr(func): """将表达式中不合法的常量,改成表达式""" def repeat_const(a, p): if p.annotation.__name__ == "Expr" and not isinstance(a, pl.Expr): return pl.repeat(a, pl.len()) else: return a @wraps(func) def decorated(*args): parameters = inspect.signature(func).parameters args = [repeat_const(a, p) for (n, p), a in zip(parameters.items(), args)] return func(*args) return decorated def apply_const_to_expr(): """应用常量转表达式功能到所有函数 对于不合法的表达式参数,可以一定程度上兼容,适用于遗传算法中表达式简化成常量的情况 """ frame = currentframe().f_back for k, v in frame.f_globals.items(): if inspect.isfunction(v) and v.__module__ and v.__module__.startswith(polars_ta.__package__): frame.f_locals[k] = const_to_expr(v) ================================================ FILE: polars_ta/utils/numba_.py ================================================ """ Demo for using numba to implement rolling functions. 本文件是使用numba实现rolling的函数,演示用 """ from functools import lru_cache from typing import List import numpy as np from numba import jit from numpy import full from numpy.lib.stride_tricks import sliding_window_view from polars import Series, Expr, struct, DataFrame, Float64 """ Series.to_numpy的操作在调用之前做,这样可控一些 batches_i1_o1这一类的函数输入不支持Series,只支持numpy。设计成在map_batches转换更可控 """ @jit(nopython=True, nogil=True, fastmath=True, cache=True) def isnan(x): # https://github.com/numba/numba/issues/2919#issuecomment-747377615 if int(x) == -9223372036854775808: return True else: return False @jit(nopython=True, nogil=True, cache=True) def full_with_window_size(arr, fill_value, dtype=None, window_size: int = 1): """创建一个更大的数组,填充后一截数据""" out = full(arr.shape[0] + window_size - 1, fill_value, dtype=dtype) out[window_size - 1:] = arr return out @jit(nopython=True, nogil=True, cache=True) def sliding_window_with_min_periods(arr, window_size: int, min_periods: int): """为rolling准备的数据,当数据长度不足时,用nan填充""" windows = sliding_window_view(arr, window_size) valid_counts = np.sum(~np.isnan(windows), axis=1) # 修改这一行,使用布尔索引而不是np.where result = windows.copy() result[valid_counts < min_periods] = np.nan return result @jit(nopython=True, nogil=True, cache=True) def _roll_1(x1: np.ndarray, window: int, min_periods: int, func): """TODO 只是模板演示,无法编译通过""" out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1).all(): continue out1[i] = func(v1) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _roll_2(x1, x2, window, min_periods, func): """TODO 只是模板演示,无法编译通过""" out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) out1[:] = np.nan for i, (v1, v2) in enumerate(zip(a1, a2)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue out1[i] = func(v1, v2) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _roll_3(x1, x2, x3, window, min_periods, func): """TODO 只是模板演示,无法编译通过""" out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) out3 = full_with_window_size(x3, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) a3 = sliding_window_with_min_periods(out3, window, min_periods) out1[:] = np.nan for i, (v1, v2, v3) in enumerate(zip(a1, a2, a3)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue if np.isnan(v3).all(): continue out1[i] = func(v1, v2, v3) return out1[:x1.shape[0]] def struct_to_numpy(xx, n: int, dtype=None): if dtype is None: return [xx.struct[i].to_numpy() for i in range(n)] else: return [xx.struct[i].to_numpy().astype(dtype) for i in range(n)] def batches_i1_o1(x1: np.ndarray, func, *args, dtype=None) -> Series: return Series(func(x1, *args), nan_to_null=True, dtype=dtype) def batches_i2_o1(xx: List[np.ndarray], func, *args, dtype=None) -> Series: return Series(func(*xx, *args), nan_to_null=True, dtype=dtype) def batches_i1_o2(x1: np.ndarray, func, *args, dtype=None) -> Series: return DataFrame(func(x1, *args), nan_to_null=True).to_struct() def batches_i2_o2(xx: List[np.ndarray], func, *args, dtype=None) -> Series: return DataFrame(func(*xx, *args), nan_to_null=True).to_struct() def batches_i2_o2_v2(xx: List[np.ndarray], func, *args, dtype=None) -> Series: """此写法也能用,速度差异不大""" out = func(*xx, *args) arr = np.empty((xx[0].shape[0], len(out)), dtype=dtype) for i, x in enumerate(out): arr[:, i] = x return Series(arr, nan_to_null=True).arr.to_struct() @jit(nopython=True, nogil=True, cache=True) def nb_roll_sum(x1, window): """Demo code. Use `pl.col('A').rolling_sum(10).alias('a1')` instead. 演示代码,请直接用 pl.col('A').rolling_sum(10).alias('a1')""" out = np.full(x1.shape, np.nan, dtype=np.float64) if len(x1) < window: return out a1 = sliding_window_view(x1, window) for i, v1 in enumerate(a1): out[i + window - 1] = np.sum(v1) return out @jit(nopython=True, nogil=True, cache=True) def nb_roll_cov(x1, x2, window): """Demo code. Use `pl.rolling_cov(pl.col('A'), pl.col('B'), window_size=10).alias('a6')` instead. 演示代码,pl.rolling_cov(pl.col('A'), pl.col('B'), window_size=10).alias('a6')""" out = np.full(x1.shape, np.nan, dtype=np.float64) if len(x1) < window: return out a1 = sliding_window_view(x1, window) a2 = sliding_window_view(x2, window) for i, (v1, v2) in enumerate(zip(a1, a2)): out[i + window - 1] = np.cov(v1, v2)[0, 1] return out def roll_sum(x: Expr, n: int) -> Expr: return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), nb_roll_sum, n), return_dtype=Float64) def roll_cov(a: Expr, b: Expr, n: int) -> Expr: return struct(f0=a, f1=b).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2), nb_roll_cov, n), return_dtype=Float64) @lru_cache @jit(nopython=True, nogil=True, fastmath=True, cache=True) def get_exponent_weights( window: int = 10, half_life: int = 5, ) -> np.ndarray: return np.repeat(0.5 ** (1 / half_life), window) ** np.arange(window - 1, -1, -1) ================================================ FILE: polars_ta/utils/pit.py ================================================ """ # Point In Time处理相关函数 ## 原始表 资产负债表 时点数据 利润表 区间数据 现金流量表 区间数据 ## 单季度数据 资产负债表 同原始表 利润表 本季-上季;一季度 现金流量表 本季-上季;一季度 ## TTM数据 资产负债表 四个报告期平均值;最新和同比两期的平均值;最新 利润表 年报;本季+去年报-同比;年化 现金流量表 年报;本季+去年报-同比;年化 ## 整理成PIT数据 例如:pit_prepare的功能为 1. ACBD 通过join_where成 2. A,AB,ACB,ACBD 通过sort+unique成 3. A,AB,ABC,ABCD 这样,每一组都满足以观察日视角没有未来数据,并只观察所能看到的最新值,可参考pit_calc示例实现其它处理 处理好的数据再pit_frist取最早公布的项目 例如:A1,A2,A3B3C3,C4D4 取first的结果为 A1B3C3D4 这时的数据就可以做因子了,但不能做时序计算,时序计算得回到pit_calc中实现 """ from typing import Tuple import polars as pl # 公布日。收盘后公布将会标记为下一日 # 其实行情数据的日期相当于报告日report_date,隐去了公布日announce_date,遇到要修正历史数据时才有公布日 LOOKBACK_DATE = '__LOOKBACK_DATE__' def pit_prepare(df: pl.DataFrame | pl.LazyFrame, by1: str = 'asset', by2: str = 'report_date', by3: str = 'announce_date', by4: str = LOOKBACK_DATE, lookback_year: str = '-5y') -> pl.DataFrame | pl.LazyFrame: """将原始的财务表根据公布日重新扩展,输出满足以下条件: 1. 根据股票和公布日分组 2. 组内没有未来数据 3. 组内的同一报告期数据只取最新 Returns ------- pl.DataFrame|pl.LazyFrame - asset - report_date - announce_date - LOOKBACK_DATE 是公布日也是观察日,在观察日可以看到最新的历史数据,看不到未来数据 """ def upsample_by_cum_max(df: pl.DataFrame) -> Tuple[pl.DataFrame, pl.DataFrame]: """分割数据,并将单调递增的数据进行重采样""" # 取递增 df1 = df.filter((pl.col(by2) == pl.col(by2).cum_max()).over(by1, order_by=[by3, by2])) # 6月30或9月30为一条数据时,upsample会是3月30,12月30,强行改成下月1日 df1 = df1.with_columns(pl.col(by2).dt.offset_by(by='1d')) df1 = df1.sort(by1, by2).upsample(by2, every='1q', group_by=by1).with_columns(pl.col(by1, by3).backward_fill()) # 还原成上月底 df1 = df1.with_columns(pl.col(by2).dt.offset_by(by='-1d')) # 取非递增 df2 = df.filter((pl.col(by2) < pl.col(by2).cum_max()).over(by1, order_by=[by3, by2])) return df1, df2 # ========================================= # ttm等操作shift(4)假定4期报告没有缺失,需要提前重采样补全 df2 = df.sort(by1, by3, by2) # 按发布日排序,过滤单调递增 if isinstance(df, pl.LazyFrame): df2 = df2.collect() dfs = [] while True: if df2.is_empty(): break df1, df2 = upsample_by_cum_max(df2) dfs.append(df1) # 合并 df3 = pl.concat(dfs) if isinstance(df, pl.LazyFrame): df3 = df3.lazy() del df1 del df2 del df del dfs # ========================================= # 根据发布日期,笛卡尔扩展,过滤掉未来数据 tmp1 = '__TMP_1__' tmp2 = '__TMP_2__' df1 = df3.select(pl.col(by1).alias(tmp1), pl.col(by3).dt.offset_by(lookback_year).alias(tmp2), pl.col(by3).alias(by4)).unique() df1 = df1.join_where(df3, (pl.col(tmp1) == pl.col(by1)) & (pl.col(by4) >= pl.col(by3)) & (pl.col(tmp2) <= pl.col(by2)) # 最多观察lookback_year年前报告,减少计算量 ).drop(tmp1, tmp2) del df3 # ========================================= # 过滤数据,同报告期只保留最新的一条 df1 = ( df1 .sort(by1, by4, by2, by3) .unique([by1, by4, by2], keep="last", maintain_order=True) ) return df1 def pit_calc(df: pl.DataFrame | pl.LazyFrame, by1: str = 'asset', by2: str = 'report_date', by4: str = LOOKBACK_DATE) -> pl.DataFrame | pl.LazyFrame: """输入PIT分组的财务数据,组内计算时序指标 同观察期下,同一报告期只有一条最新数据,所以没有了by3 """ df1 = ( df.with_columns( # TODO 补充其他各种时序指标,注意,不要少了`( ).over(by1, by4, order_by=by2)` net_profit_to_total_operate_revenue_ttm=(pl.col('net_profit').rolling_mean(4) / pl.col('total_operating_revenue').rolling_mean(4)).over(by1, by4, order_by=by2) ) ) return df1 def pit_frist(df: pl.DataFrame | pl.LazyFrame, by1: str = 'asset', by2: str = 'report_date', by3: str = 'announce_date', by4: str = LOOKBACK_DATE) -> pl.DataFrame | pl.LazyFrame: """输入PIT分组的财务数据,多组合并保留最先发布的数据 此数据不含未来数据,但原则上不能再做时序处理 Returns ------- pl.DataFrame|pl.LazyFrame - asset - report_date - announce_date Warnings -------- 结果不能时序计算,不能取历史,只能取最新 002509.XSHE 怎么处理?2019年报因故更新晚于2020年一季报,因为一季报先公布,所以2019年报显示永远为null """ df1 = ( df .sort(by1, by4, by2, by3) .unique([by1, by2], keep="first", maintain_order=True) .drop(by4) # 处理后by4的值与by2相等,可直接丢弃 ) return df1 def period_to_quarter(col: pl.Expr | str, quarter: pl.Expr | str) -> pl.Expr: """区间数据转成单季数据 1. `利润表`和`现金流量表`的`原始表`可以转成单季数据 2. `资产负债表`不能转单季 Examples -------- ```python df = df.with_columns( quarter=pl.col('report_date').dt.quarter(), ).with_columns( period_to_quarter(cs.numeric().exclude('quarter'), quarter='quarter').over('code', 'pub_date', order_by='report_date').name.suffix('_Q'), ) ``` """ col = pl.col(col) quarter = pl.col(quarter) return pl.when(quarter == 1).then(col).otherwise(col.diff()) def ttm_from_point(col: pl.Expr | str) -> pl.Expr: """时点数据计算TTM 1. 仅`资产负债表`原始表可调用此函数 2. `利润表`和`现金流量表`不能调用 数据要按一年四期排列,不能一年两期 Examples -------- ```python df = df.with_columns( ttm_from_point('total_assets').over('code', 'pub_date', order_by='report_date').name.suffix('_ttm') ) ``` """ col = pl.col(col) return pl.coalesce( col.rolling_mean(4), # 4期平均 (col + col.shift(4)) / 2, # 同比报告期平均 col, # 最新 ) def last_year(col: pl.Expr | str, quarter: pl.Expr | str) -> pl.Expr: """最新年报 Examples -------- ```python df = df.with_columns( quarter=pl.col('report_date').dt.quarter(), ).with_columns( last_year('total_assets').over('code', 'pub_date', order_by='report_date').name.suffix('_ly') ) ``` """ col = pl.col(col) quarter = pl.col(quarter) return pl.when(quarter == 4).then(col).otherwise(None).forward_fill(3) def ttm_from_period(col: pl.Expr | str, quarter: pl.Expr | str) -> pl.Expr: """区间数据计算ttm 1. `利润表`和`现金流量表`原始表可调用此函数 2. `资产负债表`不能调用 数据要按一年四期排列,不能一年两期 Examples -------- ```python df = df.with_columns( quarter=pl.col('report_date').dt.quarter(), ).with_columns( ttm_from_peroid('total_operating_revenue', quarter='quarter').over('code', 'pub_date', order_by='report_date').name.suffix('_TTM') ) ``` """ col = pl.col(col) quarter = pl.col(quarter) # 年报数据 f1 = pl.when(quarter == 4).then(col).otherwise(None) # 当前报告期+上年年报-上年同比 f2 = col + f1.forward_fill(3) - col.shift(4) # 年化计算法=当前报告期*年化系数 f3 = col / quarter * 4 return pl.coalesce(f1, f2, f3) def yoy(col: pl.Expr, period: int = 4) -> pl.Expr: """YOY : (Year On Year)同比增长率 """ return (col - col.shift(period)) / col.shift(period).abs() def qoq(col: pl.Expr, period: int = 1) -> pl.Expr: """QOQ : (Quarter On Quarter) 环比增长率""" return (col - col.shift(period)) / col.shift(period).abs() def join_quote_financial(quote: pl.DataFrame | pl.LazyFrame, financial: pl.DataFrame | pl.LazyFrame, by1: str = 'asset', by2: str = 'date') -> pl.DataFrame | pl.LazyFrame: """合并行情与财务表。请提前对财务表进行ttm等计算。因为之后再按报告期对齐很麻烦 1. 同一天发布多期,需要正确排序最新一期 2. 更新历史上的某一期,不能把他当成最新一期 Parameters ---------- quote: 行情表 financial: 财务表。收盘后公布会显示第二天,一周7天都可能公布。同一天能公布多期 by1 by2 """ quote = quote.sort(by1, by2) financial = financial.sort(by1, by2) return quote.join_asof(financial, left_on=by2, right_on=by2, by=by1, strategy="backward", check_sortedness=False) # ========================================= # 原始报告期数据预整理 # # 因各种数据源设计理念不同,需要使用不同的预处理方法 # ========================================= # ========================================= # 聚宽报告期财务数据 # # STK_BALANCE_SHEET STK_INCOME_STATEMENT STK_CASHFLOW_STATEMENT # 披露最新报表report_type=0时会同时披露基准报表report_type=1,利用此功能实现历史调整 # # 2019年年报20200611疫情晚于一季报公布,但2020一季报20200430资产负债表中基准是用的2019年报 002509.XSHE # https://www.cninfo.com.cn/new/disclosure/detail?orgId=9900015939&announcementId=1207687414&announcementTime=2020-04-30 # ========================================= def sheet_from_joinquant(df: pl.DataFrame | pl.LazyFrame) -> pl.DataFrame | pl.LazyFrame: """报表报告期调整,强制end_date为报告期 利用report_type=1的表进行历史数据调整,可部分实现PIT """ return df.with_columns(report_date=pl.col('end_date')) ================================================ FILE: polars_ta/utils/withs.py ================================================ import re import polars as pl def with_industry(df: pl.DataFrame, industry_name: str, drop_first: bool, keep_col: bool) -> pl.DataFrame: """添加行业哑元变量 Parameters ---------- df industry_name:str 行业列名 drop_first 丢弃第一列 keep_col 是否保留源列 Returns ------- pl.DataFrame """ df = df.with_columns([ # 行业处理,由浮点改成整数 pl.col(industry_name).cast(pl.UInt32), ]) # TODO 没有行业的也过滤,这会不会有问题?已经退市了,但引入了未来数据 df = df.filter( pl.col(industry_name).is_not_null(), ) if keep_col: df = df.with_columns(df.to_dummies(industry_name, drop_first=False)) else: df = df.to_dummies(industry_name, drop_first=False) if drop_first: # drop_first丢弃哪个字段是随机的,非常不友好,只能在行业中性化时动态修改代码 industry_columns = sorted(filter(lambda x: re.search(rf"^{industry_name}_\d+$", x), df.columns)) return df.drop(industry_columns[0]) else: return df ================================================ FILE: polars_ta/wq/__init__.py ================================================ from polars_ta.wq.arithmetic import * # noqa from polars_ta.wq.cross_sectional import * # noqa from polars_ta.wq.half_life import * # noqa from polars_ta.wq.logical import * # noqa from polars_ta.wq.preprocess import * # noqa from polars_ta.wq.time_series import * # noqa from polars_ta.wq.transformational import * # noqa from polars_ta.wq.vector import * # noqa ================================================ FILE: polars_ta/wq/_nb.py ================================================ import numpy as np from numba import jit, float64, boolean from numpy import argmax, argmin, full, vstack, corrcoef, nanprod, nanmean, nanstd from numpy.lib.stride_tricks import sliding_window_view from polars_ta.utils.numba_ import isnan, full_with_window_size, sliding_window_with_min_periods @jit(nopython=True, nogil=True, cache=True) def roll_argmax(x1, window, min_periods, reverse): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1[-min_periods:]).any(): continue if reverse: v1 = v1[::-1] out1[i] = argmax(v1[~np.isnan(v1)]) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def roll_argmin(x1, window, min_periods, reverse): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1[-min_periods:]).any(): continue if reverse: v1 = v1[::-1] out1[i] = argmin(v1[~np.isnan(v1)]) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def roll_prod(x1, window, min_periods): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1).all(): continue out1[i] = nanprod(v1) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _co_kurtosis(a1, a2): t1 = a1 - nanmean(a1) t2 = a2 - nanmean(a2) t3 = nanstd(a1) t4 = nanstd(a2) return nanmean(t1 * (t2 ** 3)) / (t3 * (t4 ** 3)) @jit(nopython=True, nogil=True, cache=True) def roll_co_kurtosis(x1, x2, window, min_periods): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) out1[:] = np.nan for i, (v1, v2) in enumerate(zip(a1, a2)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue out1[i] = _co_kurtosis(v1, v2) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _co_skewness(a1, a2): t1 = a1 - nanmean(a1) t2 = a2 - nanmean(a2) t3 = nanstd(a1) t4 = nanstd(a2) return nanmean(t1 * (t2 ** 2)) / (t3 * (t4 ** 2)) @jit(nopython=True, nogil=True, cache=True) def roll_co_skewness(x1, x2, window, min_periods): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) out1[:] = np.nan for i, (v1, v2) in enumerate(zip(a1, a2)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue out1[i] = _co_skewness(v1, v2) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _moment(a1, k): """centeral moment 中心矩阵""" return nanmean((a1 - nanmean(a1)) ** k) @jit(nopython=True, nogil=True, cache=True) def roll_moment(x1, window, min_periods, k): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1).all(): continue out1[i] = _moment(v1, k) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _partial_corr(a1, a2, a3): """TODO 不知是否正确,需要检查""" c = corrcoef(vstack((a1, a2, a3))) rxy = c[0, 1] rxz = c[0, 2] ryz = c[1, 2] t1 = rxy - rxz * ryz t2 = (1 - rxz ** 2) ** 0.5 t3 = (1 - ryz ** 2) ** 0.5 return t1 / (t2 * t3) @jit(nopython=True, nogil=True, cache=True) def roll_partial_corr(x1, x2, x3, window, min_periods): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) out3 = full_with_window_size(x3, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) a3 = sliding_window_with_min_periods(out3, window, min_periods) out1[:] = np.nan for i, (v1, v2, v3) in enumerate(zip(a1, a2, a3)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue if np.isnan(v3).all(): continue out1[i] = _partial_corr(v1, v2, v3) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _triple_corr(a1, a2, a3): t1 = a1 - nanmean(a1) t2 = a2 - nanmean(a2) t3 = a3 - nanmean(a3) t4 = nanstd(a1) t5 = nanstd(a2) t6 = nanstd(a3) return nanmean(t1 * t2 * t3) / (t4 * t5 * t6) @jit(nopython=True, nogil=True, cache=True) def roll_triple_corr(x1, x2, x3, window, min_periods): out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) out2 = full_with_window_size(x2, np.nan, dtype=np.float64, window_size=window) out3 = full_with_window_size(x3, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) a2 = sliding_window_with_min_periods(out2, window, min_periods) a3 = sliding_window_with_min_periods(out3, window, min_periods) out1[:] = np.nan for i, (v1, v2, v3) in enumerate(zip(a1, a2, a3)): if np.isnan(v1).all(): continue if np.isnan(v2).all(): continue if np.isnan(v3).all(): continue out1[i] = _triple_corr(v1, v2, v3) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, fastmath=True, cache=True) def _cum_prod_by(r, by): out = full(by.shape, 0, dtype=np.float64) out[:] = by for i in range(1, r.shape[0]): if isnan(out[i]): out[i] = r[i] * out[i - 1] return out @jit(nopython=True, nogil=True, fastmath=True, cache=True) def _cum_sum_by(r, by): out = full(by.shape, 0, dtype=np.float64) out[:] = by for i in range(1, r.shape[0]): if isnan(out[i]): out[i] = r[i] + out[i - 1] return out @jit(nopython=True, nogil=True, fastmath=True, cache=True) def _cum_sum_reset(a): last = 0 out = full(a.shape, 0, dtype=np.float64) for i in range(0, a.shape[0]): curr = 0 if isnan(a[i]) else a[i] if curr == 0: out[i] = 0 elif curr > 0: if last <= 0: out[i] = curr else: out[i] = curr + last elif curr < 0: if last >= 0: out[i] = curr else: out[i] = curr + last last = out[i] return out @jit(nopython=True, nogil=True, cache=True) def _sum_split_by(x1, x2, window=10, n=2): out1 = np.full(x1.shape[0], np.nan, dtype=np.float64) out2 = np.full(x1.shape[0], np.nan, dtype=np.float64) if len(x1) < window: return out1, out2 a1 = sliding_window_view(x1, window) a2 = sliding_window_view(x2, window) for i, (v1, v2) in enumerate(zip(a1, a2)): # 排序两次,解决nan的问题 b1 = np.argsort(v2)[:n] b2 = np.argsort(-v2)[:n] out1[i + window - 1] = np.sum(v1[b1]) out2[i + window - 1] = np.sum(v1[b2]) return out1, out2 @jit(float64[:](boolean[:], boolean[:], boolean[:], boolean[:], boolean, boolean), nopython=True, nogil=True, cache=True) def _signals_to_size(is_long_entry: np.ndarray, is_long_exit: np.ndarray, is_short_entry: np.ndarray, is_short_exit: np.ndarray, accumulate: bool = False, action: bool = False) -> np.ndarray: """将4路信号转换成持仓状态。适合按资产分组后的长表,参考于`vectorbt` 在`LongOnly`场景下,`is_short_entry`和`is_short_exit`输入数据值都为`False`即可 Parameters ---------- is_long_entry: np.ndarray 是否多头入场 is_long_exit: np.ndarray 是否多头出场 is_short_entry: np.ndarray 是否空头入场 is_short_exit: np.ndarray 是否空头出场 accumulate: bool 遇到重复信号时是否累计 action: bool 返回持仓状态还是下单操作 Returns ------- np.ndarray 持仓状态 Examples -------- ```python long_entry = np.array([True, True, False, False, False]) long_exit = np.array([False, False, True, False, False]) short_entry = np.array([False, False, True, False, False]) short_exit = np.array([False, False, False, True, False]) amount = signals_to_amount(long_entry, long_exit, short_entry, short_exit, accumulate=True, action=False) ``` """ _amount: float = 0.0 # 持仓状态 _action: float = 0.0 # 下单方向 out = np.zeros(len(is_long_entry), dtype=np.float64) for i in range(len(is_long_entry)): if _amount == 0.0: # 多头信号优先级高于空头信号 if is_long_entry[i]: _amount += 1.0 _action = 1.0 elif is_short_entry[i]: _amount -= 1.0 _action = -1.0 elif _amount > 0.0: if is_long_exit[i]: _amount -= 1.0 _action = -1.0 elif is_long_entry[i] and accumulate: _amount += 1.0 _action = 1.0 else: if is_short_exit[i]: _amount += 1.0 _action = 1.0 elif is_short_entry[i] and accumulate: _amount -= 1.0 _action = -1.0 out[i] = _action if action else _amount return out @jit(nopython=True, nogil=True, cache=True) def _roll_decay_linear(x1, window, min_periods): """ def ts_decay_linear(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _roll_decay_linear, d, minp), return_dtype=Float64) """ weights = np.arange(1., window + 1) out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1).all(): continue mask = ~np.isnan(v1) out1[i] = np.average(v1[mask], weights=weights[mask]) return out1[:x1.shape[0]] @jit(nopython=True, nogil=True, cache=True) def _roll_decay_exp_window(x1, window, min_periods, factor): """ def ts_decay_exp_window(x: Expr, d: int = 30, factor: float = 1.0, min_samples: Optional[int] = None) -> Expr: minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _roll_decay_exp_window, d, minp, factor), return_dtype=Float64) """ weights = factor ** np.arange(window - 1, -1, -1) out1 = full_with_window_size(x1, np.nan, dtype=np.float64, window_size=window) a1 = sliding_window_with_min_periods(out1, window, min_periods) out1[:] = np.nan for i, v1 in enumerate(a1): if np.isnan(v1).all(): continue mask = ~np.isnan(v1) out1[i] = np.average(v1[mask], weights=weights[mask]) return out1[:x1.shape[0]] ================================================ FILE: polars_ta/wq/_slow.py ================================================ """ Algorithm in this file is slow, and has been replaced by numba and other methods 本目录下算法有些慢,已经用numba等其它方法代替 """ import numpy as np from polars import Series, Expr def _arg_max(x: Series): """ Notes ----- TODO 等polars推出rolling_arg_max(reverse=True)这个问题能好转 """ # return x[::-1].arg_max() # return x.reverse().arg_max() # 正确,但太慢 return len(x) - 1 - x.arg_max() # 有多个最大值相同时,靠前的值会被记录下来,导致结果偏大 def ts_arg_max(x: Expr, d: int = 5) -> Expr: # WorldQuant中最大值为今天返回0,为昨天返回1 return x.rolling_map(_arg_max, d) def _arg_min(x: Series): return len(x) - 1 - x.arg_min() def ts_arg_min(x: Expr, d: int = 5) -> Expr: return x.rolling_map(_arg_min, d) def ts_product(x: Expr, d: int = 5) -> Expr: return x.rolling_map(np.nanprod, d) ================================================ FILE: polars_ta/wq/arithmetic.py ================================================ """ 除了使用概率极高的几个函数,其他函数不再对常量做lit """ from polars import Expr, fold, any_horizontal, Float64, Int64, lit from polars import arctan2 as _arctan2 from polars import max_horizontal, sum_horizontal, min_horizontal, mean_horizontal def abs_(x: Expr) -> Expr: """求绝对值 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 1, 2], }).with_columns( out1=abs_(pl.col('a')), out2=abs_(-1), ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i32 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ 1 │ │ -1 ┆ -1 ┆ 1 ┆ 1 │ │ 0 ┆ 0 ┆ 0 ┆ 1 │ │ 1 ┆ 1 ┆ 1 ┆ 1 │ │ 2 ┆ 2 ┆ 2 ┆ 1 │ └──────┴──────┴──────┴──────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.abs() def add(a: Expr, b: Expr, *args) -> Expr: """水平多列相加 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 2, 3, 4, None], 'b': [5, None, 3, 2, None], 'c': [1, 1, None, 1, None], }).with_columns( out=add(pl.col('a'), pl.col('b'), pl.col('c')) ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ c ┆ out │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ 5 ┆ 1 ┆ 6 │ │ 2 ┆ null ┆ 1 ┆ 3 │ │ 3 ┆ 3 ┆ null ┆ 6 │ │ 4 ┆ 2 ┆ 1 ┆ 7 │ │ null ┆ null ┆ null ┆ null │ └──────┴──────┴──────┴──────┘ ``` Notes ----- 全`null`时返回`null` """ # # 全null时返回0 # return sum_horizontal(a, b, *args) _args = [a, b] + list(args) return fold(acc=any_horizontal(_args) - 1, function=lambda acc, x: acc + x.fill_null(0), exprs=_args) def arc_cos(x: Expr) -> Expr: """反余弦""" if not isinstance(x, Expr): x = lit(x) return x.arccos() def arc_sin(x: Expr) -> Expr: """反正弦""" if not isinstance(x, Expr): x = lit(x) return x.arcsin() def arc_tan(x: Expr) -> Expr: """反正切""" if not isinstance(x, Expr): x = lit(x) return x.arctan() def arc_tan2(y: Expr, x: Expr) -> Expr: """反正切二值函数""" if not isinstance(x, Expr): x = lit(x) return _arctan2(y, x) def cbrt(x: Expr) -> Expr: """立方根""" if not isinstance(x, Expr): x = lit(x) return x.cbrt() def ceiling(x: Expr) -> Expr: """向上取整""" if not isinstance(x, Expr): x = lit(x) return x.ceil() def cos(x: Expr) -> Expr: """余弦""" if not isinstance(x, Expr): x = lit(x) return x.cos() def cosh(x: Expr) -> Expr: """双曲余弦""" if not isinstance(x, Expr): x = lit(x) return x.cosh() def cot(x: Expr) -> Expr: """余切""" if not isinstance(x, Expr): x = lit(x) return x.cot() def cube(x: Expr) -> Expr: """立方""" if not isinstance(x, Expr): x = lit(x) return x.pow(3) def degrees(x: Expr) -> Expr: """弧度转角度""" if not isinstance(x, Expr): x = lit(x) return x.degrees() def _densify(x: Expr) -> Expr: raise def div(x: Expr, y: Expr) -> Expr: """x除以y的整数部分 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1.5, 0., 1.5, 2.5], 'b': [None, -1, 0, 1, 2], }).with_columns( out1=div(pl.col('a'), 0), out2=div(pl.col('a'), 1), out3=div(pl.col('a'), pl.col('b')), ) shape: (5, 5) ┌──────┬──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null ┆ null │ │ -1.5 ┆ -1 ┆ null ┆ -2 ┆ 1 │ │ 0.0 ┆ 0 ┆ null ┆ 0 ┆ null │ │ 1.5 ┆ 1 ┆ null ┆ 1 ┆ 1 │ │ 2.5 ┆ 2 ┆ null ┆ 2 ┆ 1 │ └──────┴──────┴──────┴──────┴──────┘ ``` """ return x.floordiv(y).cast(Int64, strict=False) def divide(x: Expr, y: Expr) -> Expr: """除法 x/y Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 1, 2], }).with_columns( out1=divide(pl.col('a'), 0), out2=divide(pl.col('a'), 1), out3=divide(pl.col('a'), pl.col('b')), ) shape: (5, 5) ┌──────┬──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ f64 ┆ f64 ┆ f64 │ ╞══════╪══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null ┆ null │ │ -1 ┆ -1 ┆ -inf ┆ -1.0 ┆ 1.0 │ │ 0 ┆ 0 ┆ NaN ┆ 0.0 ┆ NaN │ │ 1 ┆ 1 ┆ inf ┆ 1.0 ┆ 1.0 │ │ 2 ┆ 2 ┆ inf ┆ 2.0 ┆ 1.0 │ └──────┴──────┴──────┴──────┴──────┘ ``` """ return x / y def exp(x: Expr) -> Expr: """自然指数函数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=expm1(pl.col('a')), ) shape: (5, 2) ┌──────┬──────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════════╡ │ null ┆ null │ │ -1 ┆ 0.367879 │ │ 0 ┆ 1.0 │ │ 1 ┆ 2.718282 │ │ 2 ┆ 7.389056 │ └──────┴──────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.exp() def expm1(x: Expr) -> Expr: """对数收益率 转 简单收益率 convert log return to simple return Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=expm1(pl.col('a')), ) shape: (5, 2) ┌──────┬───────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪═══════════╡ │ null ┆ null │ │ -1 ┆ -0.632121 │ │ 0 ┆ 0.0 │ │ 1 ┆ 1.718282 │ │ 2 ┆ 6.389056 │ └──────┴───────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.exp() - 1 def floor(x: Expr) -> Expr: """向下取整""" if not isinstance(x, Expr): x = lit(x) return x.floor() def fraction(x: Expr) -> Expr: """小数部分 This operator removes the whole number part and returns the remaining fraction part with sign. Examples -------- ```python df = pl.DataFrame({ 'a': [-2.5, -1.2, -1., None, 2., 3.2], }).with_columns( out=fraction(pl.col('a')) ) shape: (6, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ f64 ┆ f64 │ ╞══════╪══════╡ │ -2.5 ┆ -0.5 │ │ -1.2 ┆ -0.2 │ │ -1.0 ┆ -0.0 │ │ null ┆ null │ │ 2.0 ┆ 0.0 │ │ 3.2 ┆ 0.2 │ └──────┴──────┘ ``` Notes ----- 按小学时的定义,负数`-1.2`的整数部分是`-2`,小数部分是`0.8`,而这有所不同 References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#fractionx """ if not isinstance(x, Expr): x = lit(x) return x.sign() * (x.abs() % 1) def inverse(x: Expr) -> Expr: """倒数 1/x Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=inverse(pl.col('a')), ) shape: (5, 2) ┌──────┬──────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════╡ │ null ┆ null │ │ -1 ┆ -1.0 │ │ 0 ┆ inf │ │ 1 ┆ 1.0 │ │ 2 ┆ 0.5 │ └──────┴──────┘ ``` """ return 1 / x def log(x: Expr) -> Expr: """以e为底的对数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=log(pl.col('a')), ) shape: (5, 2) ┌──────┬──────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════════╡ │ null ┆ null │ │ -1 ┆ NaN │ │ 0 ┆ -inf │ │ 1 ┆ 0.0 │ │ 2 ┆ 0.693147 │ └──────┴──────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.log() def log10(x: Expr) -> Expr: """以10为底的对数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=log10(pl.col('a')), ) shape: (5, 2) ┌──────┬─────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪═════════╡ │ null ┆ null │ │ -1 ┆ NaN │ │ 0 ┆ -inf │ │ 1 ┆ 0.0 │ │ 2 ┆ 0.30103 │ └──────┴─────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.log10() def log1p(x: Expr) -> Expr: """简单收益率 转 对数收益率 convert simple return to log return log(x+1) Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=log1p(pl.col('a')), ) shape: (5, 2) ┌──────┬──────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════════╡ │ null ┆ null │ │ -1 ┆ -inf │ │ 0 ┆ 0.0 │ │ 1 ┆ 0.693147 │ │ 2 ┆ 1.098612 │ └──────┴──────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.log1p() def log2(x: Expr) -> Expr: """以2为底的对数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=log2(pl.col('a')), ) shape: (5, 2) ┌──────┬──────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════╡ │ null ┆ null │ │ -1 ┆ NaN │ │ 0 ┆ -inf │ │ 1 ┆ 0.0 │ │ 2 ┆ 1.0 │ └──────┴──────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.log(2) def max_(a: Expr, b: Expr, *args) -> Expr: """水平多列求最大值 Maximum value of all inputs. At least 2 inputs are required.""" return max_horizontal(a, b, *args) def mean(a: Expr, b: Expr, *args) -> Expr: """水平多列求均值 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 0, 2], }).with_columns( out2=mean(pl.col('a'), 2), out3=mean(pl.col('a'), pl.col('b')), ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ f64 ┆ f64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ 2.0 ┆ null │ │ -1 ┆ -1 ┆ 0.5 ┆ -1.0 │ │ 0 ┆ 0 ┆ 1.0 ┆ 0.0 │ │ 1 ┆ 0 ┆ 1.5 ┆ 0.5 │ │ 2 ┆ 2 ┆ 2.0 ┆ 2.0 │ └──────┴──────┴──────┴──────┘ ``` """ return mean_horizontal(a, b, *args) def min_(a: Expr, b: Expr, *args) -> Expr: """水平多列求最小值 Maximum value of all inputs. At least 2 inputs are required.""" return min_horizontal(a, b, *args) def mod(x: Expr, y: Expr) -> Expr: """求余 x%y Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 0, 2], }).with_columns( out2=mod(pl.col('a'), 2), out3=mod(pl.col('a'), pl.col('b')), ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ -1 ┆ 1 ┆ 0 │ │ 0 ┆ 0 ┆ 0 ┆ null │ │ 1 ┆ 0 ┆ 1 ┆ null │ │ 2 ┆ 2 ┆ 0 ┆ 0 │ └──────┴──────┴──────┴──────┘ ``` """ return x % y def multiply(a: Expr, b: Expr, *args) -> Expr: """水平多列相乘 Multiply all inputs. At least 2 inputs are required. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 2, 3, 4, None], 'b': [5, None, 3, 2, None], 'c': [1, 1, None, 1, None], }).with_columns( out=multiply(pl.col('a'), pl.col('b'), pl.col('c')) ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ c ┆ out │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ 5 ┆ 1 ┆ 5 │ │ 2 ┆ null ┆ 1 ┆ 2 │ │ 3 ┆ 3 ┆ null ┆ 9 │ │ 4 ┆ 2 ┆ 1 ┆ 8 │ │ null ┆ null ┆ null ┆ null │ └──────┴──────┴──────┴──────┘ ``` Notes ----- 全`null`时返回`null` """ _args = [a, b] + list(args) # # 全null返回1 # return fold(acc=1, function=lambda acc, x: acc * x.fill_null(1), exprs=_args) return fold(acc=any_horizontal(_args), function=lambda acc, x: acc * x.fill_null(1), exprs=_args) def power(x: Expr, y: Expr) -> Expr: """乘幂 x ** y Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 1, 2], }).with_columns( out2=power(pl.col('a'), 1), out3=power(pl.col('a'), pl.col('b')), ) shape: (5, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ f64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ -1 ┆ -1 ┆ -1.0 │ │ 0 ┆ 0 ┆ 0 ┆ 1.0 │ │ 1 ┆ 1 ┆ 1 ┆ 1.0 │ │ 2 ┆ 2 ┆ 2 ┆ 4.0 │ └──────┴──────┴──────┴──────┘ ``` Notes ----- 负数的非整数幂在实数中未定义 """ if not isinstance(x, Expr): x = lit(x) if isinstance(y, (int, float)): return x.pow(y) return x.pow(y.cast(Float64)) def radians(x: Expr) -> Expr: """角度转弧度""" if not isinstance(x, Expr): x = lit(x) return x.radians() def reverse(x: Expr) -> Expr: """求相反数""" return -x def round_(x: Expr, decimals: int = 0) -> Expr: """四舍五入 Round input to closest integer. Parameters ---------- x decimals Number of decimals to round to. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 3.5, 4.5, -3.5, -4.5], }).with_columns( out1=round_(pl.col('a'), 0), out2=pl.col('a').map_elements(lambda x: round(x, 0), return_dtype=pl.Float64), ) shape: (5, 3) ┌──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 │ ╞══════╪══════╪══════╡ │ null ┆ null ┆ null │ │ 3.5 ┆ 4.0 ┆ 4.0 │ │ 4.5 ┆ 5.0 ┆ 4.0 │ │ -3.5 ┆ -4.0 ┆ -4.0 │ │ -4.5 ┆ -5.0 ┆ -4.0 │ └──────┴──────┴──────┘ ``` Notes ----- 四舍五入,不是四舍六入五取偶(银行家舍入) """ if not isinstance(x, Expr): x = lit(x) return x.round(decimals) def round_down(x: Expr, f: int = 1) -> Expr: """小于输入的f的最大倍数 Round input to greatest multiple of f less than input Parameters ---------- x f Examples -------- ```python df = pl.DataFrame({ 'a': [None, 3.5, 4.5, -3.5, -4.5], }).with_columns( out=round_down(pl.col('a'), 2), ) shape: (5, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ f64 ┆ f64 │ ╞══════╪══════╡ │ null ┆ null │ │ 3.5 ┆ 2.0 │ │ 4.5 ┆ 4.0 │ │ -3.5 ┆ -4.0 │ │ -4.5 ┆ -6.0 │ └──────┴──────┘ ``` """ if f == 1: return x // 1 else: return x // f * f def s_log_1p(x: Expr) -> Expr: """sign(x) * log10(1 + abs(x)) 一种结合符号函数和对数变换的复合函数,常用于‌保留数据符号的同时压缩数值范围‌ Examples -------- ```python df = pl.DataFrame({ 'a': [None, 9, -9, 99, -99], }).with_columns( out1=s_log_1p(pl.col('a')), ) shape: (5, 2) ┌──────┬──────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════╡ │ null ┆ null │ │ 9 ┆ 1.0 │ │ -9 ┆ -1.0 │ │ 99 ┆ 2.0 │ │ -99 ┆ -2.0 │ └──────┴──────┘ ``` Notes ----- 从`wq`示例可以看出,log的底数是10,而不是e References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#s_log_1px """ if not isinstance(x, Expr): x = lit(x) return (x.abs() + 1).log10() * x.sign() def sign(x: Expr) -> Expr: """符号函数""" if not isinstance(x, Expr): x = lit(x) return x.sign() def signed_power(x: Expr, y: Expr) -> Expr: """x的y次幂,符号保留 x raised to the power of y such that final result preserves sign of x. Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], 'b': [None, -1, 0, 1, 2], }).with_columns( out1=signed_power(pl.col('a'), 0), out2=signed_power(pl.col('a'), 1), out3=signed_power(pl.col('a'), 2), out4=signed_power(pl.col('a'), pl.col('b')), ) shape: (5, 6) ┌──────┬──────┬──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 ┆ out3 ┆ out4 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ f64 │ ╞══════╪══════╪══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null ┆ null ┆ null │ │ -1 ┆ -1 ┆ -1 ┆ -1 ┆ -1 ┆ -1.0 │ │ 0 ┆ 0 ┆ 0 ┆ 0 ┆ 0 ┆ 0.0 │ │ 1 ┆ 1 ┆ 1 ┆ 1 ┆ 1 ┆ 1.0 │ │ 2 ┆ 2 ┆ 1 ┆ 2 ┆ 4 ┆ 4.0 │ └──────┴──────┴──────┴──────┴──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#signed_powerx-y """ if not isinstance(x, Expr): x = lit(x) if isinstance(y, (int, float)): if y == 1: return x.abs() * x.sign() elif y == 0: return x.sign() else: return x.abs().pow(y) * x.sign() return x.abs().pow(y.cast(Float64)) * x.sign() def sin(x: Expr) -> Expr: """正弦""" if not isinstance(x, Expr): x = lit(x) return x.sin() def sinh(x: Expr) -> Expr: """双曲正弦""" if not isinstance(x, Expr): x = lit(x) return x.sinh() def softsign(x: Expr) -> Expr: """softsign激活函数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=softsign(pl.col('a')), ) shape: (5, 2) ┌──────┬──────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════════╡ │ null ┆ null │ │ -1 ┆ -0.5 │ │ 0 ┆ 0.0 │ │ 1 ┆ 0.5 │ │ 2 ┆ 0.666667 │ └──────┴──────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x / (1 + x.abs()) def sqrt(x: Expr) -> Expr: """平方根""" if not isinstance(x, Expr): x = lit(x) return x.sqrt() def square(x: Expr) -> Expr: """平方""" if not isinstance(x, Expr): x = lit(x) return x.pow(2) def subtract(x: Expr, y: Expr) -> Expr: """减法 x-y""" return x - y def tan(x: Expr) -> Expr: """正切 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=tan(pl.col('a')), ) shape: (5, 2) ┌──────┬───────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪═══════════╡ │ null ┆ null │ │ -1 ┆ -1.557408 │ │ 0 ┆ 0.0 │ │ 1 ┆ 1.557408 │ │ 2 ┆ -2.18504 │ └──────┴───────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.tan() def tanh(x: Expr) -> Expr: """双曲正切 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2], }).with_columns( out1=tanh(pl.col('a')), ) shape: (5, 2) ┌──────┬───────────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪═══════════╡ │ null ┆ null │ │ -1 ┆ -0.761594 │ │ 0 ┆ 0.0 │ │ 1 ┆ 0.761594 │ │ 2 ┆ 0.964028 │ └──────┴───────────┘ ``` """ if not isinstance(x, Expr): x = lit(x) return x.tanh() def var(a: Expr, b: Expr, *args) -> Expr: """水平多列求方差 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 1, 1, 2], 'b': [None, -1, 0, 1, 2], 'c': [None, -1, 0, 2, None], }).with_columns( out1=var(pl.col('a'), pl.col('b'), pl.col('c')), ) shape: (5, 4) ┌──────┬──────┬──────┬──────────┐ │ a ┆ b ┆ c ┆ out1 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ f64 │ ╞══════╪══════╪══════╪══════════╡ │ null ┆ null ┆ null ┆ 0.0 │ │ -1 ┆ -1 ┆ -1 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0 ┆ 0.666667 │ │ 1 ┆ 1 ┆ 2 ┆ 0.666667 │ │ 2 ┆ 2 ┆ null ┆ 0.0 │ └──────┴──────┴──────┴──────────┘ ``` """ _args = [a, b] + list(args) _mean = mean_horizontal(_args) return sum_horizontal([(expr - _mean) ** 2 for expr in _args]) def std(a: Expr, b: Expr, *args) -> Expr: """水平多列求标准差 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 1, 1, 2], 'b': [None, -1, 0, 1, 2], 'c': [None, -1, 0, 2, None], }).with_columns( out2=std(pl.col('a'), pl.col('b'), pl.col('c')), ) shape: (5, 4) ┌──────┬──────┬──────┬──────────┐ │ a ┆ b ┆ c ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ f64 │ ╞══════╪══════╪══════╪══════════╡ │ null ┆ null ┆ null ┆ 0.0 │ │ -1 ┆ -1 ┆ -1 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0 ┆ 0.816497 │ │ 1 ┆ 1 ┆ 2 ┆ 0.816497 │ │ 2 ┆ 2 ┆ null ┆ 0.0 │ └──────┴──────┴──────┴──────────┘ ``` """ return var(a, b, *args).sqrt() ================================================ FILE: polars_ta/wq/cross_sectional.py ================================================ """ 与`WorldQuant Alpha101`的区别是添加了`cs_`前缀 由于截面与时序的使用方式不同,在自动化工具中如果不在名字上做区分就得手工注册,反而要麻烦些 """ import polars_ols as pls from polars import Expr, when, max_horizontal, UInt16, Int8, Utf8 from polars_ols import OLSKwargs # In the original version, the function names are not prefixed with `cs_`, # here we add it to prevent confusion # 原版函数名都没有加`cs_`, 这里统一加一防止混淆 _ols_kwargs = OLSKwargs(null_policy='drop', solve_method='svd') def cs_one_side(x: Expr, is_long: bool = True) -> Expr: """横截面上,将全部资产上调或下调,使得 Alpha 策略转为纯多头配置(当方向参数设为空头时则转为纯空头配置) Shifts all instruments up or down so that the Alpha becomes long-only or short-only (if side = short), respectively. Examples -------- ```python df = pl.DataFrame({ 'a': [None, -15, -7, 0, 20], 'b': [None, 15, 7, 0, 20], }).with_columns( out1=cs_one_side(pl.col('a'), True), out2=cs_one_side(pl.col('a'), False), out3=cs_one_side(pl.col('b'), True), out4=cs_one_side(pl.col('b'), False), ) shape: (5, 6) ┌──────┬──────┬──────┬──────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 ┆ out3 ┆ out4 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null ┆ null ┆ null │ │ -15 ┆ 15 ┆ 0 ┆ -35 ┆ 15 ┆ -5 │ │ -7 ┆ 7 ┆ 8 ┆ -27 ┆ 7 ┆ -13 │ │ 0 ┆ 0 ┆ 15 ┆ -20 ┆ 0 ┆ -20 │ │ 20 ┆ 20 ┆ 35 ┆ 0 ┆ 20 ┆ 0 │ └──────┴──────┴──────┴──────┴──────┴──────┘ ``` """ if is_long: return when(x.min() < 0).then(x - x.min()).otherwise(x) else: return when(x.max() > 0).then(x - x.max()).otherwise(x) def cs_scale(x: Expr, scale_: float = 1, long_scale: float = 1, short_scale: float = 1) -> Expr: """横截面上,将输入数据进行比例调整 此外,可通过向运算符添加额外参数,将多头头寸和空头头寸分别映射到独立的缩放比例上 Scales input to booksize. We can also scale the long positions and short positions to separate scales by mentioning additional parameters to the operator. Examples -------- ```python df = pl.DataFrame({ 'a': [None, -15, -7, 0, 20], }).with_columns( out1=cs_scale(pl.col('a'), 1), out2=cs_scale(pl.col('a'), 1, 2, 3), ) shape: (5, 3) ┌──────┬───────────┬───────────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪═══════════╪═══════════╡ │ null ┆ null ┆ null │ │ -15 ┆ -0.357143 ┆ -2.045455 │ │ -7 ┆ -0.166667 ┆ -0.954545 │ │ 0 ┆ 0.0 ┆ 0.0 │ │ 20 ┆ 0.47619 ┆ 2.0 │ └──────┴───────────┴───────────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#scale-x-scale1-longscale1-shortscale1 """ if long_scale != 1 or short_scale != 1: L = x.clip(lower_bound=0) # 全正数 S = x.clip(upper_bound=0) # 全负数 L = when(L.sum() == 0).then(0).otherwise(L / L.sum()) S = when(S.sum() == 0).then(0).otherwise(S / S.sum()) # 负数/负数=正数 return L * long_scale - S * short_scale else: return (x / x.abs().sum()).fill_nan(0) * scale_ def cs_scale_down(x: Expr, constant: int = 0) -> Expr: """横截面上,将每日数据按比例缩放至 [0,1] 区间,使得最小值映射为 0,最大值映射为 1,并通过减去常数偏移量调整最终结果 Scales all values in each day proportionately between 0 and 1 such that minimum value maps to 0 and maximum value maps to 1. constant is the offset by which final result is subtracted Examples -------- ```python df = pl.DataFrame({ 'a': [None, 15, 7, 0, 20], }).with_columns( out1=cs_scale_down(pl.col('a'), 0), out2=cs_scale_down(pl.col('a'), 1), ) shape: (5, 3) ┌──────┬──────┬───────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪══════╪═══════╡ │ null ┆ null ┆ null │ │ 15 ┆ 0.75 ┆ -0.25 │ │ 7 ┆ 0.35 ┆ -0.65 │ │ 0 ┆ 0.0 ┆ -1.0 │ │ 20 ┆ 1.0 ┆ 0.0 │ └──────┴──────┴───────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#scale_downxconstant0 """ return ((x - x.min()) / (x.max() - x.min())).fill_nan(0) - constant def cs_truncate(x: Expr, max_percent: float = 0.01) -> Expr: """横截面上,将所有 x 的取值截断至 maxPercent 指定的上限值,其中 maxPercent 需以十进制小数形式表示 Operator truncates all values of x to maxPercent. Here, maxPercent is in decimal notation Examples -------- ```python df = pl.DataFrame({ 'a': [3, 7, 20, 6], }).with_columns( out=cs_truncate(pl.col('a'), 0.5), ) shape: (4, 2) ┌─────┬─────┐ │ a ┆ out │ │ --- ┆ --- │ │ i64 ┆ i64 │ ╞═════╪═════╡ │ 3 ┆ 3 │ │ 7 ┆ 7 │ │ 20 ┆ 18 │ │ 6 ┆ 6 │ └─────┴─────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#truncatexmaxpercent001 """ return x.clip(upper_bound=x.sum() * max_percent) def cs_fill_except_all_null(x: Expr, value: float = 0) -> Expr: """横截面上,全为`null`时,保持`null`,反之`null`填充为`value` Examples -------- ```python df = pl.DataFrame({ 'a': [1, 2, None, 4, None], 'b': [None, None, None, None, None], }).with_columns( A=cs_fill_except_all_null(pl.col('a')), B=cs_fill_except_all_null(pl.col('b')), ) shape: (5, 4) ┌──────┬──────┬─────┬──────┐ │ a ┆ b ┆ A ┆ B │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ null ┆ i64 ┆ i32 │ ╞══════╪══════╪═════╪══════╡ │ 1 ┆ null ┆ 1 ┆ null │ │ 2 ┆ null ┆ 2 ┆ null │ │ null ┆ null ┆ 0 ┆ null │ │ 4 ┆ null ┆ 4 ┆ null │ │ null ┆ null ┆ 0 ┆ null │ └──────┴──────┴─────┴──────┘ ``` Notes ----- 在权重矩阵中使用时。一定要保证所有股票都在,停牌不能被过滤了 """ return when(x.is_not_null().sum() == 0).then(x).otherwise(x.fill_null(value)) def cs_fill_mean(x: Expr) -> Expr: """横截面上,填充`null`为均值""" return x.fill_null(strategy='mean') def cs_fill_max(x: Expr) -> Expr: """横截面上,填充`null`为最大值""" return x.fill_null(strategy='max') def cs_fill_min(x: Expr) -> Expr: """横截面上,填充`null`为最小值""" return x.fill_null(strategy='min') def cs_fill_null(x: Expr, value: float = 0) -> Expr: """横截面上,填充`null`为`value`""" return x.fill_null(value) def cs_regression_neut(y: Expr, x: Expr) -> Expr: """横截面上,一元回归残差""" return pls.compute_least_squares(y, x, add_intercept=True, mode='residuals', ols_kwargs=_ols_kwargs) def cs_regression_proj(y: Expr, x: Expr) -> Expr: """横截面上,一元回归预测""" return pls.compute_least_squares(y, x, add_intercept=True, mode='predictions', ols_kwargs=_ols_kwargs) def cs_rank(x: Expr, pct: bool = True) -> Expr: """横截面排名 Ranks the input among all the instruments and returns an equally distributed number between 0.0 and 1.0. For precise sort, use the rate as 0. Parameters ---------- x pct * True: 排名百分比。范围:[0,1] * False: 排名。范围:[1,+inf) Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 1, 1, 2, 2, 3, 10], }).with_columns( out1=cs_rank(pl.col('a'), True), out2=cs_rank(pl.col('a'), False), ) shape: (8, 3) ┌──────┬──────────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ u32 │ ╞══════╪══════════╪══════╡ │ null ┆ null ┆ null │ │ 1 ┆ 0.0 ┆ 1 │ │ 1 ┆ 0.0 ┆ 1 │ │ 1 ┆ 0.0 ┆ 1 │ │ 2 ┆ 0.333333 ┆ 2 │ │ 2 ┆ 0.333333 ┆ 2 │ │ 3 ┆ 0.666667 ┆ 3 │ │ 10 ┆ 1.0 ┆ 4 │ └──────┴──────────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#rankx-rate2 """ if pct: # (x-x.min)/(x.max-x.min) r = x.rank(method='dense') - 1 return r / max_horizontal(r.max(), 1) else: return x.rank(method='dense') def cs_rank_if(condition: Expr, x: Expr, pct: bool = True) -> Expr: """横截面筛选排名。可实现动态票池 Parameters ---------- condition:Expr 条件 x:Expr 因子 pct:bool 排名百分比。范围:[0,1] Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 1, 1, 2, 2, 3, 10], 'b': [1, 2, 3, 4, 5, 6, None, 8], }).with_columns( out1=cs_rank_if(True, pl.col('a'), True), # 与cs_rank等价 out2=cs_rank_if(pl.col('b') > 3, -pl.col('a'), False), ) shape: (8, 4) ┌──────┬──────┬──────────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ f64 ┆ u32 │ ╞══════╪══════╪══════════╪══════╡ │ null ┆ 1 ┆ null ┆ null │ │ 1 ┆ 2 ┆ 0.0 ┆ null │ │ 1 ┆ 3 ┆ 0.0 ┆ null │ │ 1 ┆ 4 ┆ 0.0 ┆ 3 │ │ 2 ┆ 5 ┆ 0.333333 ┆ 2 │ │ 2 ┆ 6 ┆ 0.333333 ┆ 2 │ │ 3 ┆ null ┆ 0.666667 ┆ null │ │ 10 ┆ 8 ┆ 1.0 ┆ 1 │ └──────┴──────┴──────────┴──────┘ ``` Notes ----- 已经产生了新的`None`,尽量避免之后再进行`ts_`时序计算。或按需调整`over_null` 或配合`cs_fill_null`等将`null`填充 """ return cs_rank(when(condition).then(x).otherwise(None), pct) def _cs_qcut_rank(x: Expr, q: int = 10) -> Expr: """横截面上等频分箱 Parameters ---------- x q 按频率分成`q`份 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 1, 1, 2, 2, 3, 10], }).with_columns( out1=cs_qcut(pl.col('a'), 10), out2=cs_qcut_rank(pl.col('a'), 10), out3=pl.col('a').map_batches(lambda x: pd.qcut(x, 10, labels=False, duplicates='drop')), ) shape: (8, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ u16 ┆ u16 ┆ f64 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ NaN │ │ 1 ┆ 0 ┆ 0 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0 ┆ 0.0 │ │ 2 ┆ 4 ┆ 3 ┆ 1.0 │ │ 2 ┆ 4 ┆ 3 ┆ 1.0 │ │ 3 ┆ 8 ┆ 6 ┆ 4.0 │ │ 10 ┆ 9 ┆ 10 ┆ 5.0 │ └──────┴──────┴──────┴──────┘ ``` Notes ----- 使用`rank`来实现`qcut`的效果 """ r = x.rank(method='dense') - 1 return (r * q / max_horizontal(r.max(), 1)).cast(UInt16) def cs_qcut(x: Expr, q: int = 10) -> Expr: """横截面上等频分箱 Convert float values into indexes for user-specified buckets. Bucket is useful for creating group values, which can be passed to group operators as input. Parameters ---------- x q 按频率分成`q`份 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 1, 1, 2, 2, 3, 10], }).with_columns( out1=cs_qcut(pl.col('a'), 10), out2=pl.col('a').map_batches(lambda x: pd.qcut(x, 10, labels=False, duplicates='drop')), ) shape: (8, 3) ┌──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ u16 ┆ f64 │ ╞══════╪══════╪══════╡ │ null ┆ null ┆ NaN │ │ 1 ┆ 0 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0.0 │ │ 1 ┆ 0 ┆ 0.0 │ │ 2 ┆ 4 ┆ 1.0 │ │ 2 ┆ 4 ┆ 1.0 │ │ 3 ┆ 8 ┆ 4.0 │ │ 10 ┆ 9 ┆ 5.0 │ └──────┴──────┴──────┘ ``` Warnings -------- 目前与`pd.qcut`结果不同,等官方改进 """ # 实测直接to_physical()无法用于over,相当于with pl.StringCache(): # return x.qcut(q, allow_duplicates=True).to_physical() return x.qcut(q, allow_duplicates=True, labels=[f'{i}' for i in range(q)]).cast(Utf8).cast(UInt16) def cs_top_bottom(x: Expr, k: int = 10) -> Expr: """横截面上,排名。前K标记成-1,后K标记成1 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 2, 2, 3, 5, 5, 5, 10], }).with_columns( out1=pl.col('a').rank(method='min'), out2=pl.col('a').rank(method='dense'), out3=cs_top_bottom(pl.col('a'), 2), ) shape: (10, 4) ┌──────┬──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ u32 ┆ u32 ┆ i8 │ ╞══════╪══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ 1 ┆ 1 ┆ 1 ┆ -1 │ │ 2 ┆ 2 ┆ 2 ┆ -1 │ │ 2 ┆ 2 ┆ 2 ┆ -1 │ │ 2 ┆ 2 ┆ 2 ┆ -1 │ │ 3 ┆ 5 ┆ 3 ┆ 0 │ │ 5 ┆ 6 ┆ 4 ┆ 1 │ │ 5 ┆ 6 ┆ 4 ┆ 1 │ │ 5 ┆ 6 ┆ 4 ┆ 1 │ │ 10 ┆ 9 ┆ 5 ┆ 1 │ └──────┴──────┴──────┴──────┘ """ # 值越小排第一,用来做空 a = x.rank(method='dense') b = a.max() - a return (b < k).cast(Int8) - (a <= k).cast(Int8) ================================================ FILE: polars_ta/wq/half_life.py ================================================ import numpy as np from polars import Expr from polars_ta.utils.numba_ import get_exponent_weights def ts_mean_hl(x: Expr, d: int, half_life: int): """滚动均值。带半衰期""" return x.fill_null(np.nan).rolling_mean(d, weights=get_exponent_weights(d, half_life)).fill_nan(None) def ts_sum_hl(x: Expr, d: int, half_life: int): """滚动求和。带半衰期""" return x.fill_null(np.nan).rolling_sum(d, weights=get_exponent_weights(d, half_life)).fill_nan(None) def ts_std_hl(x: Expr, d: int, half_life: int): """滚动标准差。带半衰期""" return x.fill_null(np.nan).rolling_std(d, weights=get_exponent_weights(d, half_life)).fill_nan(None) def ts_var_hl(x: Expr, d: int, half_life: int): """滚动方差。带半衰期""" return x.fill_null(np.nan).rolling_var(d, weights=get_exponent_weights(d, half_life)).fill_nan(None) # TODO 混动时序回归,带半衰期 ================================================ FILE: polars_ta/wq/logical.py ================================================ from polars import Expr, all_horizontal, any_horizontal, Boolean from polars import when def and_(a: Expr, *args) -> Expr: """Logical AND operator, returns true if both operands are true and returns false otherwise""" if len(args) == 0: return a return all_horizontal(a, *args) def equal(input1: Expr, input2: Expr) -> Expr: """Returns true if both inputs are same and returns false otherwise""" return input1 == input2 def if_else(condition: Expr, true_value: Expr, false_value: Expr = None) -> Expr: """条件判断 Notes ----- 如果`true_value`或`false_value`使用了`None`,导致数据有`null`, 后面如再有`ts_`时序算子,一定要留意当前`over_null`值 """ return when(condition).then(true_value).otherwise(false_value) def is_finite(input1: Expr) -> Expr: """If (input NaN or input == INF) return false, else return true""" return input1.is_finite() def is_nan(input1: Expr) -> Expr: """If (input == NaN) return true else return false""" return input1.is_nan() def is_null(input1: Expr) -> Expr: """If (input == null) return true else return false""" return input1.is_null() def is_not_finite(input1: Expr) -> Expr: """If (input NAN or input == INF) return true else return false""" return input1.is_infinite() def is_not_nan(input1: Expr) -> Expr: """If (input != NaN) return true else return false""" return input1.is_not_nan() def is_not_null(input1: Expr) -> Expr: """If (input != null) return true else return false""" return input1.is_not_null() def less(input1: Expr, input2: Expr) -> Expr: """If input1 < input2 return true, else return false""" return input1 < input2 def negate(input1: Expr) -> Expr: """The result is true if the converted operand is false; the result is false if the converted operand is true""" return not_(input1) def not_(input1: Expr) -> Expr: """The result is true if the converted operand is false; the result is false if the converted operand is true""" return ~input1.cast(Boolean) def or_(a: Expr, *args) -> Expr: """Logical OR operator returns true if either or both inputs are true and returns false otherwise""" if len(args) == 0: return a return any_horizontal(a, *args) def xor(a: Expr, b: Expr) -> Expr: """Logical XOR operator returns true if exactly one of the inputs is true and returns false otherwise""" return a.xor(b) ================================================ FILE: polars_ta/wq/preprocess.py ================================================ """ 1. 补空值 → 去极值 → 标准化 → 中性化 → 标准化(可选二次标准化) 2. 补空值 → 去极值 → 中性化 → 标准化 # 对数市值。去极值 MC_LOG = cs_quantile(log1p(market_cap), 0.01, 0.99) # 对数市值。标准化。供其他因子市值中性化时使用 MC_NORM = cs_zscore(MC_LOG) # 对数市值。行业中性化。直接作为因子使用 MC_NEUT = cs_zscore(cs_resid(MC_NORM, CS_SW_L1, ONE)) """ import polars_ols as pls from polars import Expr, when from polars_ols.least_squares import OLSKwargs # ====================== # standardize def cs_zscore(x: Expr, ddof: int = 0) -> Expr: """横截面zscore标准化""" return (x - x.mean()) / x.std(ddof=ddof) def cs_minmax(x: Expr) -> Expr: """横截面minmax标准化""" a = x.min() b = x.max() # 这个版本在b-a为整数时,得到的结果不好看 # return (x - a) / (b - a + TA_EPSILON) return when(a != b).then((x - a) / (b - a)).otherwise(0) def cs_robust_scale(x: Expr) -> Expr: """横截面robust scale标准化""" return (x - x.median()) / (x.quantile(0.75) - x.quantile(0.25)) # ====================== # winsorize def cs_quantile(x: Expr, low_limit: float = 0.025, up_limit: float = 0.975) -> Expr: """横截面分位数去极值""" a = x.quantile(low_limit) b = x.quantile(up_limit) return x.clip(lower_bound=a, upper_bound=b) def cs_3sigma(x: Expr, n: float = 3.) -> Expr: """横截面3倍sigma去极值""" # fill_nan will seriously reduce speed. So it's more appropriate for users to handle it themselves # fill_nan(None) 严重拖慢速度,所以还是由用户自己处理更合适 a = x.mean() b = n * x.std(ddof=0) return x.clip(lower_bound=a - b, upper_bound=a + b) def cs_mad(x: Expr, n: float = 3., k: float = 1.4826) -> Expr: """横截面MAD去极值 References ---------- https://en.wikipedia.org/wiki/Median_absolute_deviation """ a = x.median() b = (n * k) * (x - a).abs().median() return x.clip(lower_bound=a - b, upper_bound=a + b) # ====================== # neutralize def cs_demean(x: Expr) -> Expr: """横截面去均值化 Notes ----- Slower than multivariate regression. We need to groupby date and industry here, while multivariate regression only needs to add industry dummy variables and then groupby date Notes ----- 速度没有多元回归快,因为这里需要按日期行业groupby, 而多元回归只要添加行业哑变量,然后按日期groupby即可 """ return x - x.mean() # ====================== # neutralize _ols_kwargs = OLSKwargs(null_policy='drop', solve_method='svd') def cs_resid(y: Expr, *more_x: Expr) -> Expr: """横截面多元回归取残差""" return pls.compute_least_squares(y, *more_x, mode='residuals', ols_kwargs=_ols_kwargs) def cs_zscore_resid(y: Expr, *more_x: Expr) -> Expr: """横截面标准化、中性化""" return cs_resid(cs_zscore(y), *more_x) def cs_resid_zscore(y: Expr, *more_x: Expr) -> Expr: """横截面中性化、标准化""" return cs_resid(cs_zscore(y), *more_x) def cs_mad_zscore(y: Expr) -> Expr: """横截面MAD去极值、标准化""" return cs_zscore(cs_mad(y)) def cs_mad_zscore_resid(y: Expr, *more_x: Expr) -> Expr: """横截面MAD去极值、标准化、中性化""" return cs_resid(cs_zscore(cs_mad(y)), *more_x) def cs_mad_zscore_resid_zscore(y: Expr, *more_x: Expr) -> Expr: """横截面去MAD极值、标准化、中性化、二次标准化""" return cs_zscore(cs_resid(cs_zscore(cs_mad(y)), *more_x)) def cs_quantile_zscore(y: Expr, low_limit: float = 0.025, up_limit: float = 0.975) -> Expr: """横截面分位数去极值、标准化""" return cs_zscore(cs_quantile(y, low_limit, up_limit)) # ========================== def cs_resid_w(w: Expr, y: Expr, *more_x: Expr) -> Expr: """横截面加权多元回归取残差 Barra中权重采用流通市值的平方根 """ return pls.compute_least_squares(y, *more_x, sample_weights=w, mode='residuals', ols_kwargs=_ols_kwargs) ================================================ FILE: polars_ta/wq/time_series.py ================================================ import itertools from typing import Optional import more_itertools import numpy as np import polars_ols as pls from polars import Expr, UInt16, struct, when, Struct, Field, Float64, Boolean, UInt32, all_horizontal, any_horizontal from polars import rolling_corr, rolling_cov from polars_ols import RollingKwargs import polars_ta from polars_ta.utils.numba_ import batches_i1_o1, batches_i2_o1, batches_i2_o2, struct_to_numpy from polars_ta.wq._nb import roll_argmax, roll_argmin, roll_co_kurtosis, roll_co_skewness, roll_moment, roll_partial_corr, roll_triple_corr, _cum_prod_by, _cum_sum_by, _signals_to_size, \ _cum_sum_reset, _sum_split_by, roll_prod def ts_arg_max(x: Expr, d: int = 5, reverse: bool = True, min_samples: Optional[int] = None) -> Expr: """最大值相对位置 最近的一天记为第 0 天,最远的一天为第 d-1 天 Returns the relative index of the max value in the time series for the past d days. If the current day has the max value for the past d days, it returns 0. If previous day has the max value for the past d days, it returns 1. Parameters ---------- x d reverse 反向 min_samples See Also -------- ts_arg_min Examples -------- ```python df = pl.DataFrame({ 'a': [0., 1., 0.,np.nan, 1., 0.], }).with_columns( out3=ts_arg_max(pl.col('a'), 3), out2=ts_arg_max(pl.col('a'), 3, min_samples=2), out1=ts_arg_max(pl.col('a'), 3, min_samples=1), ) shape: (6, 4) ┌─────┬──────┬──────┬──────┐ │ a ┆ out3 ┆ out2 ┆ out1 │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ u16 ┆ u16 ┆ u16 │ ╞═════╪══════╪══════╪══════╡ │ 0.0 ┆ null ┆ null ┆ 0 │ │ 1.0 ┆ null ┆ 0 ┆ 0 │ │ 0.0 ┆ 1 ┆ 1 ┆ 1 │ │ NaN ┆ null ┆ null ┆ null │ │ 1.0 ┆ null ┆ null ┆ 0 │ │ 0.0 ┆ null ┆ 1 ┆ 1 │ └─────┴──────┴──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#ts_arg_maxx-d """ minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_argmax, d, minp, reverse, dtype=UInt16), return_dtype=UInt16) def ts_arg_min(x: Expr, d: int = 5, reverse: bool = True, min_samples: Optional[int] = None) -> Expr: """最小值相对位置 最近的一天记为第 0 天,最远的一天为第 d-1 天 Parameters ---------- x d reverse 反向 min_samples See Also -------- ts_arg_max Examples -------- ```python df = pl.DataFrame({ 'a': [0., 1., 0.,np.nan, 1., 0.], }).with_columns( out3=ts_arg_min(pl.col('a'), 3), out2=ts_arg_min(pl.col('a'), 3, min_samples=2), out1=ts_arg_min(pl.col('a'), 3, min_samples=1), ) shape: (6, 4) ┌─────┬──────┬──────┬──────┐ │ a ┆ out3 ┆ out2 ┆ out1 │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ u16 ┆ u16 ┆ u16 │ ╞═════╪══════╪══════╪══════╡ │ 0.0 ┆ null ┆ null ┆ 0 │ │ 1.0 ┆ null ┆ 1 ┆ 1 │ │ 0.0 ┆ 0 ┆ 0 ┆ 0 │ │ NaN ┆ null ┆ null ┆ null │ │ 1.0 ┆ null ┆ null ┆ 1 │ │ 0.0 ┆ null ┆ 0 ┆ 0 │ └─────┴──────┴──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#ts_arg_minx-d """ minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_argmin, d, minp, reverse, dtype=UInt16), return_dtype=UInt16) def ts_co_kurtosis(x: Expr, y: Expr, d: int = 5, ddof: int = 0, min_samples: Optional[int] = None) -> Expr: """计算两个序列在滚动窗口内联合分布的协峰度""" minp = min_samples or polars_ta.MIN_SAMPLES or d return struct(f0=x, f1=y).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2), roll_co_kurtosis, d, minp), return_dtype=Float64) def ts_co_skewness(x: Expr, y: Expr, d: int = 5, ddof: int = 0, min_samples: Optional[int] = None) -> Expr: """计算两个序列在滚动窗口内联合分布的协偏度""" minp = min_samples or polars_ta.MIN_SAMPLES or d return struct(f0=x, f1=y).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2), roll_co_skewness, d, minp), return_dtype=Float64) def ts_corr(x: Expr, y: Expr, d: int = 5, ddof: int = 1, min_samples: Optional[int] = None) -> Expr: """时序滚动相关系数 rolling correlation between two columns Parameters ---------- x y d ddof 自由度 min_samples Notes ----- x、y不区分先后 """ minp = min_samples or polars_ta.MIN_SAMPLES return rolling_corr(x, y, window_size=d, ddof=ddof, min_samples=minp) def ts_count(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """时序滚动计数 Parameters ---------- x d min_samples Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2, 3], 'b': [None, True, True, True, False, False], }).with_columns( out1=ts_count(pl.col('a'), 3), out2=ts_count(pl.col('b'), 3), ) shape: (6, 4) ┌──────┬───────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ u32 ┆ u32 │ ╞══════╪═══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ true ┆ null ┆ null │ │ 0 ┆ true ┆ null ┆ null │ │ 1 ┆ true ┆ 2 ┆ 3 │ │ 2 ┆ false ┆ 2 ┆ 2 │ │ 3 ┆ false ┆ 3 ┆ 1 │ └──────┴───────┴──────┴──────┘ ``` """ minp = min_samples or polars_ta.MIN_SAMPLES return x.cast(Boolean).cast(UInt32).rolling_sum(d, min_samples=minp) def ts_count_eq(x: Expr, d: int = 30, n: int = 10, min_samples: Optional[int] = None) -> Expr: """D天内最近连续出现N次 Parameters ---------- x d: int 窗口大小 n: int 连续出现次数 """ minp = min_samples or polars_ta.MIN_SAMPLES xx = x.cast(Boolean).cast(UInt32) return (xx.rolling_sum(n) == n) & (xx.rolling_sum(d, min_samples=minp) == n) def ts_count_ge(x: Expr, d: int = 30, n: int = 10, min_samples: Optional[int] = None) -> Expr: """D天内最近连续出现至少N次 Parameters ---------- x d: int 窗口大小 n: int 至少连续出现次数 """ minp = min_samples or polars_ta.MIN_SAMPLES xx = x.cast(Boolean).cast(UInt32) return (xx.rolling_sum(n) == n) & (xx.rolling_sum(d, min_samples=minp) >= n) def ts_count_nans(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动统计nan出现次数 Parameters ---------- x d min_samples Examples -------- ```python df = pl.DataFrame({ 'a': [None, float('nan'), -1, 0, 1, 2, 3], }).with_columns( out1=ts_count_nans(pl.col('a'), 3), ) shape: (7, 2) ┌──────┬──────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ f64 ┆ u32 │ ╞══════╪══════╡ │ null ┆ null │ │ NaN ┆ null │ │ -1.0 ┆ null │ │ 0.0 ┆ 1 │ │ 1.0 ┆ 0 │ │ 2.0 ┆ 0 │ │ 3.0 ┆ 0 │ └──────┴──────┘ ``` """ minp = min_samples or polars_ta.MIN_SAMPLES return x.is_nan().cast(UInt32).rolling_sum(d, min_samples=minp) def ts_count_nulls(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动统计null出现次数 Parameters ---------- x d min_samples Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 0, 1, 2, 3], 'b': [None, True, True, True, False, False], }).with_columns( out1=ts_count_nulls(pl.col('a'), 3), out2=ts_count_nulls(pl.col('b'), 3), ) shape: (6, 4) ┌──────┬───────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ u32 ┆ u32 │ ╞══════╪═══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ true ┆ null ┆ null │ │ 0 ┆ true ┆ 1 ┆ 1 │ │ 1 ┆ true ┆ 0 ┆ 0 │ │ 2 ┆ false ┆ 0 ┆ 0 │ │ 3 ┆ false ┆ 0 ┆ 0 │ └──────┴───────┴──────┴──────┘ ``` """ minp = min_samples or polars_ta.MIN_SAMPLES return x.is_null().cast(UInt32).rolling_sum(d, min_samples=minp) def ts_covariance(x: Expr, y: Expr, d: int = 5, ddof: int = 1, min_samples: Optional[int] = None) -> Expr: """时序滚动协方差 rolling covariance between two columns Parameters ---------- x y d ddof 自由度 min_samples Notes ----- x、y不区分先后 """ minp = min_samples or polars_ta.MIN_SAMPLES return rolling_cov(x, y, window_size=d, ddof=ddof, min_samples=minp) def ts_cum_count(x: Expr) -> Expr: """时序累计计数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, None, -1, 0, 1, 2], 'b': [None, None, True, True, True, False], }).with_columns( out1=ts_cum_count(pl.col('a')), out2=ts_cum_count(pl.col('b')), ) shape: (6, 4) ┌──────┬───────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ u32 ┆ u32 │ ╞══════╪═══════╪══════╪══════╡ │ null ┆ null ┆ 0 ┆ 0 │ │ null ┆ null ┆ 0 ┆ 0 │ │ -1 ┆ true ┆ 1 ┆ 1 │ │ 0 ┆ true ┆ 2 ┆ 2 │ │ 1 ┆ true ┆ 3 ┆ 3 │ │ 2 ┆ false ┆ 4 ┆ 4 │ └──────┴───────┴──────┴──────┘ ``` """ return x.cum_count() def ts_cum_max(x: Expr) -> Expr: """时序累计最大值 Examples -------- ```python df = pl.DataFrame({ 'a': [None, None, -1, 0, 2, 1], 'b': [None, None, True, False, False, True], }).with_columns( out1=ts_cum_max(pl.col('a')), out2=ts_cum_max(pl.col('b')), ) shape: (6, 4) ┌──────┬───────┬──────┬──────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ i64 ┆ bool │ ╞══════╪═══════╪══════╪══════╡ │ null ┆ null ┆ null ┆ null │ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ true ┆ -1 ┆ true │ │ 0 ┆ false ┆ 0 ┆ true │ │ 2 ┆ false ┆ 2 ┆ true │ │ 1 ┆ true ┆ 2 ┆ true │ └──────┴───────┴──────┴──────┘ ``` """ return x.cum_max() def ts_cum_min(x: Expr) -> Expr: """时序累计最小值 Examples -------- ```python df = pl.DataFrame({ 'a': [None, None, -1, 0, -2, 1], 'b': [None, None, True, False, False, True], }).with_columns( out1=ts_cum_min(pl.col('a')), out2=ts_cum_min(pl.col('b')), ) shape: (6, 4) ┌──────┬───────┬──────┬───────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ i64 ┆ bool │ ╞══════╪═══════╪══════╪═══════╡ │ null ┆ null ┆ null ┆ null │ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ true ┆ -1 ┆ true │ │ 0 ┆ false ┆ -1 ┆ false │ │ -2 ┆ false ┆ -2 ┆ false │ │ 1 ┆ true ┆ -2 ┆ false │ └──────┴───────┴──────┴───────┘ ``` """ return x.cum_min() def ts_cum_prod(x: Expr) -> Expr: """时序累乘""" return x.cum_prod() def ts_cum_sum(x: Expr) -> Expr: """时序累加""" return x.cum_sum() def ts_cum_sum_reset(x: Expr) -> Expr: """时序累加。遇到0、nan、相反符号时重置 Examples -------- ```python df = pl.DataFrame({ 'a': [1, 0, 1, 2, None, 3, -2, -3], }).with_columns( A=ts_cum_sum_reset(pl.col('a')) ) shape: (8, 2) ┌──────┬──────┐ │ a ┆ A │ │ --- ┆ --- │ │ i64 ┆ f64 │ ╞══════╪══════╡ │ 1 ┆ 1.0 │ │ 0 ┆ 0.0 │ │ 1 ┆ 1.0 │ │ 2 ┆ 3.0 │ │ null ┆ 0.0 │ │ 3 ┆ 3.0 │ │ -2 ┆ -2.0 │ │ -3 ┆ -5.0 │ └──────┴──────┘ ``` """ return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), _cum_sum_reset), return_dtype=Float64) def ts_decay_exp_window(x: Expr, d: int = 30, factor: float = 1.0, min_samples: Optional[int] = None) -> Expr: """指数衰减移动平均 Examples -------- ```python from polars_ta.wq.time_series import ts_decay_linear, ts_decay_exp_window df = pl.DataFrame({ 'a': [None, 6, 5, 4, 5, 30], }).with_columns( out1=ts_decay_linear(pl.col('a'), 5), out2=ts_decay_exp_window(pl.col('a'), 5, 0.5), ) shape: (6, 3) ┌──────┬──────┬───────────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪══════╪═══════════╡ │ null ┆ null ┆ null │ │ 6 ┆ null ┆ null │ │ 5 ┆ null ┆ null │ │ 4 ┆ null ┆ null │ │ 5 ┆ null ┆ null │ │ 30 ┆ 13.2 ┆ 17.806452 │ └──────┴──────┴───────────┘ ``` Parameters ---------- x d factor 衰减系数 min_samples References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#ts_decay_exp_windowx-d-factor-10-nan-true """ minp = min_samples or polars_ta.MIN_SAMPLES weights = np.repeat(factor, d) ** np.arange(d - 1, -1, -1) # print(weights) # pyo3_runtime.PanicException: weights not yet supported on array with null values return x.fill_null(np.nan).rolling_mean(d, weights=weights, min_samples=minp).fill_nan(None) def ts_decay_linear(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """线性衰减移动平均 Examples -------- ```python from polars_ta.talib import WMA as ts_WMA from polars_ta.wq.time_series import ts_decay_linear df = pl.DataFrame({ 'a': [None, 6, 5, 4, 5, 30], }).with_columns( out1=ts_decay_linear(pl.col('a'), 5), out2=ts_WMA(pl.col('a'), 5), ) shape: (6, 3) ┌──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪══════╪══════╡ │ null ┆ null ┆ null │ │ 6 ┆ null ┆ null │ │ 5 ┆ null ┆ null │ │ 4 ┆ null ┆ null │ │ 5 ┆ null ┆ null │ │ 30 ┆ 13.2 ┆ 13.2 │ └──────┴──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#ts_decay_linearx-d-dense-false """ minp = min_samples or polars_ta.MIN_SAMPLES weights = np.arange(1, d + 1) # print(weights) # pyo3_runtime.PanicException: weights not yet supported on array with null values # null换成NaN就不报错了,再换回来 return x.fill_null(np.nan).rolling_mean(d, weights=weights, min_samples=minp).fill_nan(None) def ts_delay(x: Expr, d: int = 1, fill_value: float = None) -> Expr: """时序数据移动 shift x Parameters ---------- x d 向前或向后的移动天数 fill_value 填充。可用None、常量或Expr """ return x.shift(d, fill_value=fill_value) def ts_delta(x: Expr, d: int = 1) -> Expr: """时序差分""" return x.diff(d) def ts_fill_null(x: Expr, limit: int = None) -> Expr: """用上一个非空值填充空值 Parameters ---------- x limit 最大填充次数 Examples -------- ```python df = pl.DataFrame({ 'a': [None, -1, 1, None, None], 'b': [None, True, False, None, None], }).with_columns( out1=ts_fill_null(pl.col('a')), out2=ts_fill_null(pl.col('b'), 1), ) shape: (5, 4) ┌──────┬───────┬──────┬───────┐ │ a ┆ b ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ bool ┆ i64 ┆ bool │ ╞══════╪═══════╪══════╪═══════╡ │ null ┆ null ┆ null ┆ null │ │ -1 ┆ true ┆ -1 ┆ true │ │ 1 ┆ false ┆ 1 ┆ false │ │ null ┆ null ┆ 1 ┆ false │ │ null ┆ null ┆ 1 ┆ null │ └──────┴───────┴──────┴───────┘ ``` """ return x.forward_fill(limit) def ts_ir(x: Expr, d: int = 1, min_samples: Optional[int] = None) -> Expr: """时序滚动信息系数 rolling information ratio""" return ts_mean(x, d, min_samples) / ts_std_dev(x, d, 0, min_samples) def ts_kurtosis(x: Expr, d: int = 5, bias: bool = False, min_samples: Optional[int] = None) -> Expr: """时序滚动峰度 kurtosis of x for the last d days Parameters ---------- x d bias 有偏 min_samples Notes ----- `bias=False`时与`pandas`结果一样 Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 3, 4, 999], }).with_columns( out1=pl.col('a').map_batches(lambda x: pl.Series(pd.Series(x).rolling(4).kurt())), out2=ts_kurtosis(pl.col('a'), 4), ) shape: (6, 3) ┌──────┬──────────┬──────────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 │ ╞══════╪══════════╪══════════╡ │ null ┆ null ┆ null │ │ 1 ┆ null ┆ null │ │ 2 ┆ null ┆ null │ │ 3 ┆ null ┆ null │ │ 4 ┆ -1.2 ┆ -1.2 │ │ 999 ┆ 3.999946 ┆ 3.999946 │ └──────┴──────────┴──────────┘ ``` """ minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_kurtosis(d, min_samples=minp, bias=bias) def ts_l2_norm(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """欧几里得范数 Euclidean norm """ minp = min_samples or polars_ta.MIN_SAMPLES return x.pow(2).rolling_sum(d, min_samples=minp).sqrt() def ts_log_diff(x: Expr, d: int = 1) -> Expr: """求对数,然后时序滚动差分 log(current value of input or x[t] ) - log(previous value of input or x[t-1]). """ return x.log().diff(d) def ts_max(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """时序滚动最大值""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_max(d, min_samples=minp) def ts_max_diff(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """窗口内最大值与当前值的差异‌ x - ts_max(x, d)""" return x - ts_max(x, d, min_samples) def ts_mean(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """简单移动平均""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_mean(d, min_samples=minp) def ts_median(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动中位数""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_median(d, min_samples=minp) def ts_min(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """时序滚动最小值""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_min(d, min_samples=minp) def ts_min_diff(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """窗口内最小值与当前值的差异‌ x - ts_min(x, d)""" return x - ts_min(x, d, min_samples) def ts_min_max_cps(x: Expr, d: int, f: float = 2.0, min_samples: Optional[int] = None) -> Expr: """计算时间窗口内最小值与最大值的总和减去当前值的加权结果 (ts_min(x, d) + ts_max(x, d)) - f * x""" return (ts_min(x, d, min_samples) + ts_max(x, d, min_samples)) - f * x def ts_min_max_diff(x: Expr, d: int, f: float = 0.5, min_samples: Optional[int] = None) -> Expr: """计算当前值 x 与基于时间窗口内最小值、最大值的加权组合的差值 x - f * (ts_min(x, d) + ts_max(x, d))""" return x - f * (ts_min(x, d, min_samples) + ts_max(x, d, min_samples)) def ts_moment(x: Expr, d: int, k: int = 0, min_samples: Optional[int] = None) -> Expr: """滚动k阶中心距 Returns K-th central moment of x for the past d days. Parameters ---------- x d k min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_moment, d, minp, k), return_dtype=Float64) def ts_partial_corr(x: Expr, y: Expr, z: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """滚动偏相关 Returns partial correlation of x, y, z for the past d days. """ minp = min_samples or polars_ta.MIN_SAMPLES or d return struct(f0=x, f1=y, f2=z).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3), roll_partial_corr, d, minp), return_dtype=Float64) def ts_percentage(x: Expr, d: int, percentage: float = 0.5, min_samples: Optional[int] = None) -> Expr: """滚动百分位数 Returns percentile value of x for the past d days. Parameters ---------- x d percentage min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_quantile(percentage, window_size=d, min_samples=minp) def ts_product(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动乘""" minp = min_samples or polars_ta.MIN_SAMPLES or d return x.map_batches(lambda x1: batches_i1_o1(x1.to_numpy(), roll_prod, d, minp), return_dtype=Float64) def ts_rank(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动排名""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_rank(d, min_samples=minp) / x.is_not_null().cast(UInt32).rolling_sum(d, min_samples=minp) def ts_realized_volatility(close: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """已实现波动率""" minp = min_samples or polars_ta.MIN_SAMPLES or d return ts_log_diff(close, 1).rolling_std(d, ddof=0, min_samples=minp) def ts_returns(x: Expr, d: int = 1) -> Expr: """简单收益率""" return x.pct_change(d) def ts_scale(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动缩放。相当于ts_minmax Returns (x – ts_min(x, d)) / (ts_max(x, d) – ts_min(x, d)) + constant """ a = ts_min(x, d, min_samples) b = ts_max(x, d, min_samples) # return (x - a) / (b - a + TA_EPSILON) return when(a != b).then((x - a) / (b - a)).otherwise(0) def ts_shifts_v1(*args) -> Expr: """时序上按顺序进行平移,然后逻辑与 比如今天反包,昨天阴跌,前天涨停。只需要写`ts_shifts_v1(反包,阴跌,涨停)`。使用此函数能一定程度上简化代码 Parameters ---------- args pl.col按照顺序排列 Examples -------- ```python df = pl.DataFrame({ 'a': [None, False, False, True, True, True], 'b': [None, False, True, True, True, True], 'c': [None, True, True, True, True, True], }).with_columns( out1=ts_shifts_v1(pl.col('a'), pl.col('b'), pl.col('c')), out2=pl.col('a').shift(0) & pl.col('b').shift(1) & pl.col('c').shift(2), ) shape: (6, 5) ┌──────┬───────┬───────┬───────┬───────┐ │ c ┆ b ┆ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ bool ┆ bool ┆ bool ┆ bool ┆ bool │ ╞══════╪═══════╪═══════╪═══════╪═══════╡ │ null ┆ null ┆ null ┆ null ┆ null │ │ true ┆ false ┆ false ┆ false ┆ false │ │ true ┆ true ┆ false ┆ false ┆ false │ │ true ┆ true ┆ true ┆ true ┆ true │ │ true ┆ true ┆ true ┆ true ┆ true │ │ true ┆ true ┆ true ┆ true ┆ true │ └──────┴───────┴───────┴───────┴───────┘ ``` """ return all_horizontal(arg.shift(i) for i, arg in enumerate(args)) def ts_shifts_v2(*args) -> Expr: """时序上按顺序进行平移,然后逻辑与 遇到连续条件时可简化参数。如连续3天涨停后连续2天跌停,可用`ts_shifts_v2(跌停,2,涨停,3)` 另一种实现方法是:`ts_delay((ts_count(涨停,3)==3),2)&(ts_count(跌停,2)==2)` Parameters ---------- args pl.col, repeat。两个参数循环 Examples -------- ```python df = pl.DataFrame({ 'a': [None, False, False, True, True, True], 'b': [None, False, True, True, True, True], 'c': [None, True, True, True, True, True], }).with_columns( out1=ts_shifts_v1(pl.col('a'), pl.col('b'), pl.col('b')), out2=ts_shifts_v2(pl.col('a'), 1, pl.col('b'), 2), out3=pl.col('a').shift(0) & pl.col('b').shift(1) & pl.col('b').shift(2), ) shape: (6, 6) ┌───────┬───────┬──────┬───────┬───────┬───────┐ │ a ┆ b ┆ c ┆ out1 ┆ out2 ┆ out3 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ bool ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool │ ╞═══════╪═══════╪══════╪═══════╪═══════╪═══════╡ │ null ┆ null ┆ null ┆ null ┆ null ┆ null │ │ false ┆ false ┆ true ┆ false ┆ false ┆ false │ │ false ┆ true ┆ true ┆ false ┆ false ┆ false │ │ true ┆ true ┆ true ┆ false ┆ false ┆ false │ │ true ┆ true ┆ true ┆ true ┆ true ┆ true │ │ true ┆ true ┆ true ┆ true ┆ true ┆ true │ └───────┴───────┴──────┴───────┴───────┴───────┘ ``` """ return ts_shifts_v1(*itertools.chain.from_iterable([item] * count for item, count in more_itertools.chunked(args, 2))) def ts_shifts_v3(*args) -> Expr: """时序上按顺序进行平移,然后逻辑或 如:涨停后连续下跌1~3天。这个案例会导致涨停可能出现在动态的一天。`ts_shifts_v3(下跌,1,3,涨停,1,1)` 它本质上是`ts_shifts_v2(下跌,1,涨停,1)|ts_shifts_v2(下跌,2,涨停,1)|ts_shifts_v2(下跌,3,涨停,1)|` Parameters ---------- args pl.col, [start, end]。三个参数循环。start/end都是闭区间 Examples -------- ```python df = pl.DataFrame({ 'a': [None, False, False, True, True, True], 'b': [None, False, True, True, True, True], 'c': [None, True, True, True, True, True], }).with_columns( out0=ts_shifts_v3(pl.col('a'), 1, 3, pl.col('b'), 2, 2), out1=ts_shifts_v2(pl.col('a'), 1, pl.col('b'), 2), out2=ts_shifts_v2(pl.col('a'), 2, pl.col('b'), 2), out3=ts_shifts_v2(pl.col('a'), 3, pl.col('b'), 2) ).with_columns( out4=pl.col('out1') | pl.col('out2') | pl.col('out3') ) shape: (6, 8) ┌───────┬───────┬──────┬───────┬───────┬───────┬───────┬───────┐ │ a ┆ b ┆ c ┆ out0 ┆ out1 ┆ out2 ┆ out3 ┆ out4 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ bool ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool │ ╞═══════╪═══════╪══════╪═══════╪═══════╪═══════╪═══════╪═══════╡ │ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ null │ │ false ┆ false ┆ true ┆ false ┆ false ┆ false ┆ false ┆ false │ │ false ┆ true ┆ true ┆ false ┆ false ┆ false ┆ false ┆ false │ │ true ┆ true ┆ true ┆ false ┆ false ┆ false ┆ false ┆ false │ │ true ┆ true ┆ true ┆ true ┆ true ┆ false ┆ false ┆ true │ │ true ┆ true ┆ true ┆ true ┆ true ┆ true ┆ false ┆ true │ └───────┴───────┴──────┴───────┴───────┴───────┴───────┴───────┘ ``` """ exprs = [a for a, b, c in more_itertools.chunked(args, 3)] ranges = [range(b, c + 1) for a, b, c in more_itertools.chunked(args, 3)] # 参数整理成col,repeat模式 outputs = [itertools.chain.from_iterable(zip(exprs, d)) for d in itertools.product(*ranges)] return any_horizontal(ts_shifts_v2(*_) for _ in outputs) def ts_skewness(x: Expr, d: int = 5, bias: bool = False, min_samples: Optional[int] = None) -> Expr: """时序滚动偏度 Return skewness of x for the past d days Parameters ---------- x d bias 有偏 min_samples Notes ----- `bias=False`时与`pandas`结果一样 """ minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_skew(d, min_samples=minp, bias=bias) def ts_std_dev(x: Expr, d: int = 5, ddof: int = 0, min_samples: Optional[int] = None) -> Expr: """时序滚动标准差 Parameters ---------- x d ddof 自由度 min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_std(d, ddof=ddof, min_samples=minp) def ts_sum(x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """时序滚动求和""" minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_sum(d, min_samples=minp) def ts_sum_split_by(x: Expr, by: Expr, d: int = 30, k: int = 10) -> Expr: """切割论求和。在d窗口范围内以by为依据进行从小到大排序。取最大的N个和最小的N个对应位置的x的和 Parameters ---------- x by d 窗口大小 k 最大最小的k个 Returns ------- Expr * top_k * bottom_k Examples -------- ```python df = pl.DataFrame({ 'a': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], 'b': [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], }).with_columns( A=ts_sum_split_by(pl.col('a'), pl.col('b'), 8, 3) ) shape: (10, 3) ┌─────┬─────┬───────────────┐ │ a ┆ b ┆ A │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ struct[2] │ ╞═════╪═════╪═══════════════╡ │ 10 ┆ 10 ┆ {null,null} │ │ 20 ┆ 9 ┆ {null,null} │ │ 30 ┆ 8 ┆ {null,null} │ │ 40 ┆ 7 ┆ {null,null} │ │ 50 ┆ 6 ┆ {null,null} │ │ 60 ┆ 5 ┆ {null,null} │ │ 70 ┆ 4 ┆ {null,null} │ │ 80 ┆ 3 ┆ {210.0,60.0} │ │ 90 ┆ 2 ┆ {240.0,90.0} │ │ 100 ┆ 1 ┆ {270.0,120.0} │ └─────┴─────┴───────────────┘ ``` """ dtype = Struct([Field(f"column_{i}", Float64) for i in range(2)]) return struct(f0=x, f1=by).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, 2), _sum_split_by, d, k), return_dtype=dtype) def ts_triple_corr(x: Expr, y: Expr, z: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动三重相关系数 Returns triple correlation of x, y, z for the past d days. """ minp = min_samples or polars_ta.MIN_SAMPLES or d return struct(f0=x, f1=y, f2=z).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 3), roll_triple_corr, d, minp), return_dtype=Float64) def ts_weighted_decay(x: Expr, k: float = 0.5, min_samples: Optional[int] = None) -> Expr: """时序滚动加权衰减求和 Instead of replacing today’s value with yesterday’s as in ts_delay(x, 1), it assigns weighted average of today’s and yesterday’s values with weight on today’s value being k and yesterday’s being (1-k). Parameters ---------- x k 衰减系数 min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES return x.rolling_sum(2, weights=[1 - k, k], min_samples=minp) def ts_zscore(x: Expr, d: int = 5, min_samples: Optional[int] = None) -> Expr: """时序滚动zscore""" return (x - ts_mean(x, d, min_samples)) / ts_std_dev(x, d, 0, min_samples) def ts_cum_prod_by(r: Expr, v: Expr) -> Expr: """带设置的累乘 可用于市值累乘日收益率得到新市值的需求 Parameters ---------- r 收益率。不能出现`null`, `null`需要提前用`1`代替 `CLOSE/ts_delay(CLOSE, 1)` v 市值。非空时分配指定市值资产 * 如果非`null`,直接返回`v` * 如果`null`,返回`V[-1]*r` Returns ------- Expr V。累乘后成新的市值 Examples -------- ```python df = pl.DataFrame({ 'r': [1, 2, 3, 4, 5, 6], 'v': [None, None, 6, None, None, 12], }).with_columns( V=ts_cum_prod_by(pl.col('r'), pl.col('v')) ) shape: (6, 3) ┌─────┬──────┬───────┐ │ r ┆ v ┆ V │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ f64 │ ╞═════╪══════╪═══════╡ │ 1 ┆ null ┆ null │ │ 2 ┆ null ┆ null │ │ 3 ┆ 6 ┆ 6.0 │ │ 4 ┆ null ┆ 24.0 │ │ 5 ┆ null ┆ 120.0 │ │ 6 ┆ 12 ┆ 12.0 │ └─────┴──────┴───────┘ ``` """ return struct(f0=r, f1=v).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2), _cum_prod_by), return_dtype=Float64) def ts_cum_sum_by(r: Expr, v: Expr) -> Expr: """带设置的累加 可用于市值累加日收益得到新市值的需求 Parameters ---------- r 收益。不能出现`null`, `null`需要提前用`0`代替 `CLOSE-ts_delay(CLOSE, 1)` v 市值。非空时分配指定市值资产 * 如果非`null`,直接返回`v` * 如果`null`,返回`V[-1]+r` Examples -------- ```python df = pl.DataFrame({ 'r': [1, 2, 3, 4, 5, 6], 'v': [None, None, 6, None, None, 12], }).with_columns( V=ts_cum_sum_by(pl.col('r'), pl.col('v')) ) shape: (6, 3) ┌─────┬──────┬──────┐ │ r ┆ v ┆ V │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ f64 │ ╞═════╪══════╪══════╡ │ 1 ┆ null ┆ null │ │ 2 ┆ null ┆ null │ │ 3 ┆ 6 ┆ 6.0 │ │ 4 ┆ null ┆ 10.0 │ │ 5 ┆ null ┆ 15.0 │ │ 6 ┆ 12 ┆ 12.0 │ └─────┴──────┴──────┘ ``` """ return struct(f0=r, f1=v).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, 2), _cum_sum_by), return_dtype=Float64) def ts_regression_resid(y: Expr, x: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动回归取残差""" minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, x, mode='residuals', add_intercept=True, rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)) def ts_regression_pred(y: Expr, x: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动回归取y的预测值 """ minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, x, mode='predictions', add_intercept=True, rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)) def ts_regression_intercept(y: Expr, x: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动回归取截距 """ minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, x, mode='coefficients', add_intercept=True, rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)).struct[1] def ts_regression_slope(y: Expr, x: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动回归取斜率""" minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, x, mode='coefficients', add_intercept=True, rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)).struct[0] def ts_resid(y: Expr, *more_x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """多元时序滚动回归取残差 Parameters ---------- y *more_x 多个x d min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, *more_x, mode='residuals', rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)) def ts_pred(y: Expr, *more_x: Expr, d: int = 30, min_samples: Optional[int] = None) -> Expr: """多元时序滚动回归预测 Parameters ---------- y *more_x 多个x d min_samples """ minp = min_samples or polars_ta.MIN_SAMPLES or d return pls.compute_rolling_least_squares(y, *more_x, mode='predictions', rolling_kwargs=RollingKwargs(window_size=d, min_periods=minp)) def ts_weighted_mean(x: Expr, w: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动加权平均""" minp = min_samples or polars_ta.MIN_SAMPLES return (x * w).rolling_sum(d, min_samples=minp) / w.rolling_sum(d, min_samples=minp) def ts_weighted_sum(x: Expr, w: Expr, d: int, min_samples: Optional[int] = None) -> Expr: """时序滚动加权求和""" minp = min_samples or polars_ta.MIN_SAMPLES return (x * w).rolling_sum(d, min_samples=minp) def ts_signals_to_size(long_entry: Expr, long_exit: Expr, short_entry: Expr, short_exit: Expr, accumulate: bool = False, action: bool = False) -> Expr: """多空信号转持仓。参考于`vectorbt` Parameters ---------- long_entry 多头入场 long_exit 多头出场 short_entry 空头入场 short_exit 空头出场 accumulate 遇到重复信号时是否累计 action 返回持仓状态还是下单操作 """ return struct(f0=long_entry, f1=long_exit, f2=short_entry, f3=short_exit).map_batches( lambda xx: batches_i2_o1(struct_to_numpy(xx, 4, dtype=float), _signals_to_size, accumulate, action), return_dtype=Float64) ================================================ FILE: polars_ta/wq/transformational.py ================================================ from polars import Expr, when, Boolean, Int32, Float32, lit def cut(x: Expr, b: float, *more_bins) -> Expr: """分箱 Parameters ---------- x b *more_bins Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 1, 1, 2, 2, 3, 10], }).with_columns( out1=cut(pl.col('a'), 2, 5, 20), ) shape: (8, 2) ┌──────┬──────┐ │ a ┆ out1 │ │ --- ┆ --- │ │ i64 ┆ u32 │ ╞══════╪══════╡ │ null ┆ null │ │ 1 ┆ 0 │ │ 1 ┆ 0 │ │ 1 ┆ 0 │ │ 2 ┆ 0 │ │ 2 ┆ 0 │ │ 3 ┆ 1 │ │ 10 ┆ 2 │ └──────┴──────┘ ``` """ return x.cut([b, *more_bins]).to_physical() def clamp(x: Expr, lower: float = 0, upper: float = 0, inverse: bool = False, mask: float = None) -> Expr: """Limits input value between lower and upper bound in inverse = false mode (which is default). Alternatively, when inverse = true, values between bounds are replaced with mask, while values outside bounds are left as is. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 3, 4, 5, 6], }).with_columns( out1=clamp(pl.col('a'), 2, 5, False), out2=clamp(pl.col('a'), 2, 5, True), ) shape: (7, 3) ┌──────┬──────┬──────┐ │ a ┆ out1 ┆ out2 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╡ │ null ┆ null ┆ null │ │ 1 ┆ 2 ┆ 1 │ │ 2 ┆ 2 ┆ null │ │ 3 ┆ 3 ┆ null │ │ 4 ┆ 4 ┆ null │ │ 5 ┆ 5 ┆ null │ │ 6 ┆ 5 ┆ 6 │ └──────┴──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#clampx-lower-0-upper-0-inverse-false-mask """ if inverse: cond = (x >= lower) & (x <= upper) return when(~cond).then(x).otherwise(mask) else: return x.clip(lower, upper) # def filter_(x: Expr, h: str = "1, 2, 3, 4", t: str = "0.5") -> Expr: # """Used to filter the value and allows to create filters like linear or exponential decay.""" # raise def _keep(x: Expr, f: float, period: int = 5) -> Expr: """This operator outputs value x when f changes and continues to do that for “period” days after f stopped changing. After “period” days since last change of f, NaN is output.""" raise def left_tail(x: Expr, maximum: float = 0) -> Expr: """NaN everything greater than maximum, maximum should be constant. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 3, 4, 5], }).with_columns( out=left_tail(pl.col('a'), 3), ) shape: (6, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ i64 ┆ i64 │ ╞══════╪══════╡ │ null ┆ null │ │ 1 ┆ 1 │ │ 2 ┆ 2 │ │ 3 ┆ 3 │ │ 4 ┆ null │ │ 5 ┆ null │ └──────┴──────┘ ``` See Also -------- tail References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#left_tail """ return when(x <= maximum).then(x).otherwise(None) # def pasteurize(x: Expr) -> Expr: # """Set to NaN if x is INF or if the underlying instrument is not in the Alpha universe""" # # TODO: 不在票池中的的功能无法表示 # # TODO: 与purify好像没啥区别 # return when(x.is_finite()).then(x).otherwise(None) def purify(x: Expr) -> Expr: """Clear infinities (+inf, -inf) by replacing with null. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1., 2., float('nan'), float('inf'), float('-inf')], }).with_columns( out=purify(pl.col('a')), ) shape: (6, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ f64 ┆ f64 │ ╞══════╪══════╡ │ null ┆ null │ │ 1.0 ┆ 1.0 │ │ 2.0 ┆ 2.0 │ │ NaN ┆ null │ │ inf ┆ null │ │ -inf ┆ null │ └──────┴──────┘ ``` """ return when(x.is_finite()).then(x).otherwise(None) def fill_nan(x: Expr) -> Expr: """填充`nan`为`null`""" return x.fill_nan(None) def fill_null(x: Expr, value=0) -> Expr: """填充`null`为`value`""" return x.fill_null(value) def right_tail(x: Expr, minimum: float = 0) -> Expr: """NaN everything less than minimum, minimum should be constant. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 3, 4, 5], }).with_columns( out=right_tail(pl.col('a'), 3), ) shape: (6, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ i64 ┆ i64 │ ╞══════╪══════╡ │ null ┆ null │ │ 1 ┆ null │ │ 2 ┆ null │ │ 3 ┆ 3 │ │ 4 ┆ 4 │ │ 5 ┆ 5 │ └──────┴──────┘ ``` References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#right_tail """ return when(x >= minimum).then(x).otherwise(None) def sigmoid(x: Expr) -> Expr: """sigmoid激活函数""" if not isinstance(x, Expr): x = lit(x) return 1 / (1 + (-x).exp()) def logit(x: Expr) -> Expr: """对数几率,拉伸中间部分""" if not isinstance(x, Expr): x = lit(x) return (x / (1 + x)).log() def tail(x: Expr, lower: float = 0, upper: float = 0, newval: float = 0) -> Expr: """If (x > lower AND x < upper) return newval, else return x. Lower, upper, newval should be constants. Examples -------- ```python df = pl.DataFrame({ 'a': [None, 1, 2, 3, 4, 5, 6], }).with_columns( out=tail(pl.col('a'), 2, 5, 1), ) shape: (7, 2) ┌──────┬──────┐ │ a ┆ out │ │ --- ┆ --- │ │ i64 ┆ i64 │ ╞══════╪══════╡ │ null ┆ null │ │ 1 ┆ 1 │ │ 2 ┆ 2 │ │ 3 ┆ 1 │ │ 4 ┆ 1 │ │ 5 ┆ 5 │ │ 6 ┆ 6 │ └──────┴──────┘ ``` See Also -------- clamp References ---------- https://platform.worldquantbrain.com/learn/operators/detailed-operator-descriptions#tail """ cond = (x > lower) & (x < upper) return when(~cond | x.is_null()).then(x).otherwise(newval) def int_(a: Expr) -> Expr: """bool转int""" if not isinstance(a, Expr): a = lit(a) return a.cast(Int32) def bool_(a: Expr) -> Expr: """int转成bool""" if not isinstance(a, Expr): a = lit(a) return a.cast(Boolean) def float_(a: Expr) -> Expr: """int转成float""" if not isinstance(a, Expr): a = lit(a) return a.cast(Float32) def nop(x: Expr) -> Expr: """空操作""" return x def lit_(a: Expr) -> Expr: """将常量封装成lit""" if not isinstance(a, Expr): return lit(a) return a ================================================ FILE: polars_ta/wq/vector.py ================================================ """ 本文件中算子都是对整列计算得到一个值,使用时可能会广播到整列 可以用在横截面,也可以用在时序。但时序使用会引入未来数据,这时一般用途是统计 """ from polars import Expr def vec_avg(x: Expr) -> Expr: """Taking mean of the vector field x""" return x.mean() def vec_choose(x: Expr, nth: int) -> Expr: """Choosing kth item(indexed at 0) from each vector field x""" return x.gather(nth) def vec_count(x: Expr) -> Expr: """Number of elements in vector field x""" return x.count() def vec_ir(x: Expr) -> Expr: """Information Ratio (Mean / Standard Deviation) of vector field x""" return x.mean() / x.std(ddof=0) def vec_kurtosis(x: Expr) -> Expr: """Kurtosis of vector field x""" return x.kurtosis() def vec_l2_norm(x: Expr) -> Expr: """Euclidean norm""" return x.pow(2).sum().sqrt() def vec_max(x: Expr) -> Expr: """Maximum value form vector field x""" return x.max() def vec_min(x: Expr) -> Expr: """Minimum value form vector field x""" return x.min() def vec_norm(x: Expr) -> Expr: """Sum of all absolute values of vector field x""" return x.abs().sum() def vec_percentage(x: Expr, percentage: float = 0.5) -> Expr: """Percentile of vector field x""" return x.quantile(percentage) def vec_powersum(x: Expr, constant: float = 2) -> Expr: """Sum of power of vector field x""" return (x ** constant).sum() def vec_range(x: Expr) -> Expr: """Difference between maximum and minimum element in vector field x""" return x.max() - x.min() def vec_skewness(x: Expr) -> Expr: """Skewness of vector field x""" return x.skew() def vec_stddev(x: Expr) -> Expr: """Standard Deviation of vector field x""" return x.std(ddof=0) def vec_sum(x: Expr) -> Expr: """Sum of vector field x""" return x.sum() def vec_median(x: Expr) -> Expr: return x.median() ================================================ FILE: prompt.txt ================================================ 你是一个专业的量化因子开发助手,帮助创建、改进因子。 你可以: - 编写和优化因子代码 - 提供因子开发示例 - 建议和改进因子逻辑 【重要限制】 1. 只回答与因子开发、编码和优化相关的问题 2. 无论输入何种语言,都用中文回复 3. 只使用`内置算子`。优先使用`基础因子` 4. 不能使用命名参数 5. 很多函数直接支持将bool当成int用来计算,可以省去if_else和int_ ## 基础因子 - 价格因子: OPEN, HIGH, LOW, CLOSE, VWAP - 成交量因子: VOLUME, AMOUNT, TURNOVER - 市值因子: MARKET_CAP, PB, PE ## 内置算子 ### polars_ta.wq.arithmetic - abs_(x) : 求绝对值 - cbrt(x) : 立方根 - ceiling(x) : 向上取整 - cos(x) : 余弦 - cube(x) : 立方 - exp(x) : 自然指数函数 - floor(x) : 向下取整 - fraction(x) : 小数部分 - log(x) : 以e为底的对数 - log10(x) : 以10为底的对数 - log1p(x) : 简单收益率 转 对数收益率 - max_(a,b,*args) : 水平多列求最大值 - min_(a,b,*args) : 水平多列求最小值 - mod(x,y) : 求余 - power(x,y) : 乘幂 - round_(x,decimals:int=0) : 四舍五入 - round_down(x,f:int=1) : 小于输入的f的最大倍数 - sign(x) : 符号函数 - signed_power(x,y) : x的y次幂,符号保留 - sin(x) : 正弦 - sqrt(x) : 平方根 - square(x) : 平方 - std(a,b,*args) : 水平多列求标准差 - tan(x) : 正切 - var(a,b,*args) : 水平多列求方差 ### polars_ta.wq.time_series - ts_arg_max(x,d:int=5,reverse:bool=True) : 最大值相对位置 - ts_arg_min(x,d:int=5,reverse:bool=True) : 最小值相对位置 - ts_corr(x,y,d:int=5,ddof:int=1) : 时序滚动相关系数 - ts_count(x,d:int=30) : 时序滚动计数 - ts_covariance(x,y,d:int=5,ddof:int=1) : 时序滚动协方差 - ts_decay_exp_window(x,d:int=30,factor:float=1.0) : 指数衰减移动平均 - ts_decay_linear(x,d:int=30) : 线性衰减移动平均 - ts_delay(x,d:int=1,fill_value:float=None) : 时序数据移动 - ts_delta(x,d:int=1) : 时序差分 - ts_fill_null(x,limit:int=None) : 用上一个非空值填充空值 - ts_ir(x,d:int=1) : 时序滚动信息系数 - ts_kurtosis(x,d:int=5,bias:bool=False) : 时序滚动峰度 - ts_l2_norm(x,d:int=5) : 欧几里得范数 - ts_log_diff(x,d:int=1) : 求对数,然后时序滚动差分 - ts_max(x,d:int=30) : 时序滚动最大值 - ts_mean(x,d:int=5) : 简单移动平均 - ts_median(x,d:int=5) : 时序滚动中位数 - ts_min(x,d:int=30) : 时序滚动最小值 - ts_moment(x,d:int,k:int=0) : 滚动k阶中心距 - ts_percentage(x,d:int,percentage:float=0.5) : 滚动百分位数 - ts_pred(y,*more_x,d:int=30) : 多元时序滚动回归预测 - ts_product(x,d:int=5) : 时序滚动乘 - ts_rank(x,d:int=5) : 时序滚动排名 - ts_realized_volatility(close,d:int=5) : 已实现波动率 - ts_regression_intercept(y,x,d:int) : 时序滚动回归取截距 - ts_regression_pred(y,x,d:int) : 时序滚动回归取y的预测值 - ts_regression_resid(y,x,d:int) : 时序滚动回归取残差 - ts_regression_slope(y,x,d:int) : 时序滚动回归取斜率 - ts_resid(y,*more_x,d:int=30) : 多元时序滚动回归取残差 - ts_returns(x,d:int=1) : 简单收益率 - ts_scale(x,d:int=5) : 时序滚动缩放。相当于ts_minmax - ts_skewness(x,d:int=5,bias:bool=False) : 时序滚动偏度 - ts_std_dev(x,d:int=5,ddof:int=0) : 时序滚动标准差 - ts_sum(x,d:int=30) : 时序滚动求和 - ts_weighted_decay(x,k:float=0.5) : 时序滚动加权衰减求和 - ts_weighted_mean(x,w,d:int) : 时序滚动加权平均 - ts_weighted_sum(x,w,d:int) : 时序滚动加权求和 - ts_zscore(x,d:int=5) : 时序滚动zscore ### polars_ta.wq.cross_sectional - cs_fill_null(x,value:float=0) : 横截面上,填充`null`为`value` - cs_qcut(x,q:int=10) : 横截面上等频分箱 - cs_rank(x,pct:bool=True) : 横截面排名 - cs_rank_if(condition,x,pct:bool=True) : 横截面筛选排名。可实现动态票池 - cs_scale(x,scale_:float=1,long_scale:float=1,short_scale:float=1) : 横截面上,将输入数据进行比例调整 ### polars_ta.wq.preprocess - cs_3sigma(x,n:float=3.0) : 横截面3倍sigma去极值 - cs_demean(x) : 横截面去均值化 - cs_mad(x,n:float=3.0,k:float=1.4826) : 横截面MAD去极值 - cs_minmax(x) : 横截面minmax标准化 - cs_quantile(x,low_limit:float=0.025,up_limit:float=0.975) : 横截面分位数去极值 - cs_quantile_zscore(y,low_limit:float=0.025,up_limit:float=0.975) : 横截面分位数去极值、标准化 - cs_resid(y,*more_x) : 横截面多元回归取残差 - cs_resid_zscore(y,*more_x) : 横截面中性化、标准化 - cs_zscore(x,ddof:int=0) : 横截面zscore标准化 ### polars_ta.wq.logical - if_else(condition,true_value,false_value=None) : 条件判断 ### polars_ta.wq.transformational - bool_(a) : int转成bool - cut(x,b:float,*more_bins) : 分箱 - float_(a) : int转成float - int_(a) : bool转int - sigmoid(x) : sigmoid激活函数 ## 因子示例 ```python alpha_004=-ts_rank(cs_rank(LOW),9) alpha_006=-ts_corr(OPEN,VOLUME,10) alpha_040=-cs_rank(ts_std_dev(HIGH,10))*ts_corr(HIGH,VOLUME,10) alpha_041=power(HIGH*LOW,0.5)-VWAP alpha_042=cs_rank(VWAP-CLOSE)/cs_rank(VWAP+CLOSE) alpha_053=-ts_delta((CLOSE-LOW-(HIGH-CLOSE))/(CLOSE-LOW),9) alpha_057=-((CLOSE-VWAP)/ts_decay_linear(cs_rank(ts_arg_max(CLOSE,30)),2)) alpha_101=(CLOSE-OPEN)/(HIGH-LOW+0.001) ``` 如果理解了上述要求,不用思考,直接回复:“老大,请吩咐” ================================================ FILE: pyproject.toml ================================================ [project] name = "polars_ta" authors = [ { name = "wukan", email = "wu-kan@163.com" }, ] description = "polars expressions" readme = "README.md" requires-python = ">=3.8" keywords = ["polars", "expression", "talib"] license = { file = "LICENSE" } classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python" ] dependencies = [ "polars>=1.28.0", "polars-ols>=0.3.0", "numpy", "numba", "pandas", "more_itertools", ] dynamic = ["version"] [project.optional-dependencies] talib = [ "TA-Lib", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.version] path = "polars_ta/_version.py" [tool.hatch.build.targets.wheel] packages = ["polars_ta"] include-package-data = true [tool.hatch.build.targets.sdist] include = ["polars_ta*"] ================================================ FILE: requirements-docs.txt ================================================ mkdocs mkdocstrings[python] mkdocs-material mkdocs-llmstxt ================================================ FILE: requirements.txt ================================================ polars>=1.26.0 polars-ols>=0.3.0 numpy numba pandas more_itertools ================================================ FILE: setup.py ================================================ from setuptools import setup setup() ================================================ FILE: tests/numba_test.py ================================================ import time import numpy as np import polars as pl from numba import jit from polars_ta.utils.numba_ import nb_roll_sum, batches_i1_o1, roll_sum, roll_cov, roll_split_i2_o2 from polars_ta.wq.time_series import ts_co_kurtosis @jit(nopython=True, nogil=True, fastmath=True, cache=True) def nb_sum(x): return np.sum(x) df = pl.DataFrame({'A': range(100000), 'B': range(100000)}) a = df.with_columns([ pl.col('A').rolling_sum(10).alias('a1'), pl.col('A').rolling_map(lambda x: x.sum(), 10).alias('a2'), pl.col('A').rolling_map(lambda x: nb_sum(x.to_numpy()), 10).alias('a3'), roll_sum(pl.col('A'), 10).alias('a4'), pl.col('A').map_batches(lambda x: batches_i1_o1(x.to_numpy(), nb_roll_sum, 10)).alias('a5'), pl.rolling_cov(pl.col('A'), pl.col('B'), window_size=10).alias('a6'), roll_cov(pl.col('A'), pl.col('B'), 10).alias('a7'), ts_co_kurtosis(pl.col('A'), pl.col('B'), 10).alias('a8'), roll_split_i2_o2(pl.col('A'), pl.col('B'), 10, 2).alias('a10'), ]) print(a) t1 = time.perf_counter() for i in range(10): a = df.with_columns([ pl.col('A').rolling_sum(10).alias('a1'), ]) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10): a = df.with_columns([ pl.col('A').rolling_map(lambda x: x.sum(), 10).alias('a2'), ]) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10): a = df.with_columns([ pl.col('A').rolling_map(lambda x: nb_sum(x.to_numpy()), 10).alias('a3'), ]) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10): a = df.with_columns([ pl.col('A').map_batches(lambda x: pl.Series(nb_roll_sum(x.to_numpy(), 10), nan_to_null=True)).alias('a4'), ]) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10): a = df.with_columns([ pl.col('A').map_batches(lambda x: batches_i1_o1(x.to_numpy(), nb_roll_sum, 10)).alias('a5'), ]) t2 = time.perf_counter() print(t2 - t1) ================================================ FILE: tests/pit_test.py ================================================ import time from pathlib import Path import pandas as pd import polars as pl from polars_ta.utils.pit import pit_prepare, ttm_from_point, LOOKBACK_DATE, pit_frist, ttm_from_period, sheet_from_joinquant PATH_STEP0_INPUT1 = r'F:\data\jqresearch\get_STK_BALANCE_SHEET' PATH_STEP0_INPUT2 = r'F:\data\jqresearch\get_STK_INCOME_STATEMENT' PATH_STEP0_INPUT3 = r'F:\data\jqresearch\get_STK_CASHFLOW_STATEMENT' def load_parquet(folder): paths = list(Path(folder).glob('*.parquet')) # due to the data is not complete when writing, the data type can only be read from pandas then convert to polars # 写入时由于部分数据为空,导致入写类型不同,读取就存在问题,只能用pandas读回来 dfs = pd.concat([pd.read_parquet(p) for p in paths]) return pl.from_pandas(dfs, nan_to_null=True) def balance(df1): # https://www.joinquant.com/help/api/help#Stock:合并资产负债表 # 002509.XSHE 600528.XSHG 000528.XSHE # 天广中贸 疫情期 2019年报晚于2020一季报公布 df1 = sheet_from_joinquant(df1) df1 = pit_prepare(df1, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) df2 = df1.select( "code", "report_date", "pub_date", LOOKBACK_DATE, "report_type", "total_assets", ttm_from_point('total_assets').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), "equities_parent_company_owners", ttm_from_point('equities_parent_company_owners').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), ) df3 = pit_frist(df2, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) return df3 def income(df1): # https://www.joinquant.com/help/api/help#Stock:合并利润表 df1 = sheet_from_joinquant(df1) df1 = df1.with_columns(quarter=pl.col('report_date').dt.quarter()) df1 = pit_prepare(df1, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) df2 = df1.select( "code", "report_date", "pub_date", LOOKBACK_DATE, "report_type", "total_operating_revenue", ttm_from_period("total_operating_revenue", quarter='quarter').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), "net_profit", ttm_from_period('net_profit', quarter='quarter').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), ) df3 = pit_frist(df2, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) return df3 def cashflow(df1): # https://www.joinquant.com/help/api/help#Stock:合并现金流量表 df1 = sheet_from_joinquant(df1) df1 = df1.with_columns(quarter=pl.col('report_date').dt.quarter()) df1 = pit_prepare(df1, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) df2 = df1.select( "code", "report_date", "pub_date", LOOKBACK_DATE, "report_type", "net_operate_cash_flow", ttm_from_period("net_operate_cash_flow", quarter='quarter').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), "financial_cost", ttm_from_period('financial_cost', quarter='quarter').over('code', LOOKBACK_DATE, order_by='report_date').name.suffix('_ttm'), ) df3 = pit_frist(df2, by1='code', by2='report_date', by3='pub_date', by4=LOOKBACK_DATE) return df3 if __name__ == '__main__': df1 = load_parquet(PATH_STEP0_INPUT1) df2 = load_parquet(PATH_STEP0_INPUT2) df3 = load_parquet(PATH_STEP0_INPUT3) t1 = time.perf_counter() balance(df1) income(df2) cashflow(df3) t2 = time.perf_counter() print(t2 - t1) ================================================ FILE: tests/ta/test_momentum.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = 0 + np.random.rand(100) self.low_np = 0 - np.random.rand(100) self.close_np = np.arange(100, dtype=float)+ np.random.rand(100) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) def test_WILLR(self): from polars_ta.ta.momentum import WILLR result1 = talib.WILLR(self.high_np, self.low_np, self.close_np, timeperiod=5) result2 = self.df_pl.select(WILLR(pl.col("high"), pl.col("low"), pl.col("close"), timeperiod=5)) # !!! 注意,与talib版的区别 result3 = result2['high'].to_numpy() * (-100) print() print(result1) print(result3) # assert np.allclose(result1, result3, equal_nan=True) def test_STOCHF_fastd(self): from polars_ta.ta.momentum import STOCHF _, result1 = talib.STOCHF(self.high_np, self.low_np, self.close_np, fastk_period=5, fastd_period=3, fastd_matype=0) result2 = self.df_pl.select(STOCHF(pl.col("high"), pl.col("low"), pl.col("close"), fastk_period=5, fastd_period=3).struct[1]) # !!! 注意,与talib版的区别 result3 = result2['fastd'].to_numpy() * 100 print() print(result1) print(result3) # assert np.allclose(result1, result3, equal_nan=True) def test_MACD(self): talib.set_compatibility(1) from polars_ta.ta.momentum import MACD fastperiod = 3 slowperiod = 5 result1, _, _ = talib.MACD(self.close_np, fastperiod=fastperiod, slowperiod=slowperiod, signalperiod=1) result2 = self.df_pl.select(MACD(pl.col("close"), fastperiod=fastperiod, slowperiod=slowperiod).struct[0]) result3 = result2['macd'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1[slowperiod:], result3[slowperiod:], equal_nan=True) def test_TRIX(self): talib.set_compatibility(1) from polars_ta.ta.momentum import TRIX result1 = talib.TRIX(self.close_np, timeperiod=30) result2 = self.df_pl.select(TRIX(pl.col("close"), timeperiod=30)) result3 = result2['close'].to_numpy() * 100 # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) def test_AROON(self): timeperiod = 10 from polars_ta.ta.momentum import AROON _, result1 = talib.AROON(self.high_np, self.low_np, timeperiod=timeperiod) result2 = self.df_pl.select(AROON(pl.col("high"), pl.col("low"), timeperiod=timeperiod).alias('high').struct[1]) result3 = result2['aroonup'].to_numpy() * 100 print() print(result1) print(result3) # assert np.allclose(result1[timeperiod:], result3[timeperiod:], equal_nan=True) def test_RSI(self): timeperiod = 10 from polars_ta.ta.momentum import RSI result1 = talib.RSI(self.close_np, timeperiod=timeperiod) result2 = self.df_pl.select(RSI(pl.col("close"), timeperiod=timeperiod)) result3 = result2['close'].to_numpy() * 100 # print() # print(result1) # print(result3) assert np.allclose(result1[timeperiod:], result3[timeperiod:], equal_nan=True) ================================================ FILE: tests/ta/test_operators.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.random.rand(100) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) def test_MAX(self): from polars_ta.ta.operators import MAX result1 = talib.MAX(self.close_np, timeperiod=10) result2 = self.df_pl.select(MAX(pl.col("close"), timeperiod=10)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_MIN(self): from polars_ta.ta.operators import MIN result1 = talib.MIN(self.close_np, timeperiod=10) result2 = self.df_pl.select(MIN(pl.col("close"), timeperiod=10)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_MAXINDEX(self): from polars_ta.ta.operators import MAXINDEX result1 = talib.MAXINDEX(self.close_np, timeperiod=5) result2 = self.df_pl.select(MAXINDEX(pl.col("close"), timeperiod=5)) result3 = result2['close'].to_numpy() assert np.allclose(result1[5:], result3[5:], equal_nan=True) ================================================ FILE: tests/ta/test_overlap.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) def test_SMA(self): from polars_ta.ta.overlap import SMA result1 = talib.SMA(self.close_np, timeperiod=3) result2 = self.df_pl.select(SMA(pl.col("close"), d=3)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_WMA(self): from polars_ta.ta.overlap import WMA result1 = talib.WMA(self.close_np, timeperiod=15) result2 = self.df_pl.select(WMA(pl.col("close"), d=15)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_EMA(self): from polars_ta.ta.overlap import EMA # !!! 此处非常重要,有部分函数受此影响 # https://github.com/TA-Lib/ta-lib-python/blob/master/talib/_ta_lib.pxd#L28 talib.set_compatibility(1) result1 = talib.EMA(self.close_np, timeperiod=5) result2 = self.df_pl.select(EMA(pl.col("close"), timeperiod=5)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_DEMA(self): from polars_ta.ta.overlap import DEMA # !!! 此处非常重要,有部分函数受此影响 talib.set_compatibility(1) result1 = talib.DEMA(self.close_np, timeperiod=3) result2 = self.df_pl.select(DEMA(pl.col("close"), timeperiod=3)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_TEMA(self): from polars_ta.ta.overlap import TEMA # !!! 此处非常重要,有部分函数受此影响 talib.set_compatibility(1) result1 = talib.TEMA(self.close_np, timeperiod=3) result2 = self.df_pl.select(TEMA(pl.col("close"), timeperiod=3)) result3 = result2['close'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) def test_TRIMA(self): from polars_ta.ta.overlap import TRIMA result1 = talib.TRIMA(self.close_np, timeperiod=6) result2 = self.df_pl.select(TRIMA(pl.col("close"), timeperiod=6)) result3 = result2['close'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) # def test_KAMA(self): # # !!! 此处非常重要,有部分函数受此影响 # talib.set_compatibility(1) # # from polars_ta.talib.overlap import KAMA # # result1 = talib.KAMA(self.close_np, timeperiod=5) # result2 = self.df_pl.select(KAMA(pl.col("close"), timeperiod=5)) # result3 = result2['close'].to_numpy() # # print() # # print(result1) # # print(result3) # # assert np.allclose(result1, result3, equal_nan=True) def test_BBANDS(self): from polars_ta.ta.overlap import BBANDS result1, _, _ = talib.BBANDS(self.close_np, timeperiod=3) result2 = self.df_pl.select(BBANDS(pl.col("close"), timeperiod=3).struct[0]) result3 = result2['upperband'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_MIDPOINT(self): from polars_ta.ta.overlap import MIDPOINT result1 = talib.MIDPOINT(self.close_np, timeperiod=3) result2 = self.df_pl.select(MIDPOINT(pl.col("close"), timeperiod=3)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_MIDPRICE(self): from polars_ta.ta.overlap import MIDPRICE result1 = talib.MIDPRICE(self.high_np, self.low_np, timeperiod=3) result2 = self.df_pl.select(MIDPRICE(pl.col("high"), pl.col("low"), timeperiod=3)) result3 = result2['high'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) ================================================ FILE: tests/ta/test_statistic.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) self.df_pd = self.df_pl.to_pandas() def test_STDDEV(self): from polars_ta.ta.statistic import STDDEV result1 = talib.STDDEV(self.close_np, timeperiod=3) result2 = self.df_pl.select(STDDEV(pl.col("close"), timeperiod=3)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_VAR(self): from polars_ta.ta.statistic import VAR result1 = talib.VAR(self.close_np, timeperiod=5) result2 = self.df_pl.select(VAR(pl.col("close"), timeperiod=5)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_CORREL(self): from polars_ta.ta.statistic import CORREL result1 = talib.CORREL(self.close_np, self.high_np, timeperiod=5) result2 = self.df_pl.select(CORREL(pl.col("close"), pl.col('high'), timeperiod=5)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) ================================================ FILE: tests/ta/test_volatility.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) def test_TRANGE(self): from polars_ta.ta.volatility import TRANGE result1 = talib.TRANGE(self.high_np, self.low_np, self.close_np) result2 = self.df_pl.select(TRANGE(pl.col("high"), pl.col("low"), pl.col("close"))) result3 = result2['high'].to_numpy() # print() # print(result1) # print(result3) # value at position 0 is `x` rather than `nan` in talib # pl版第一个位置计算的值为max(x,nan,nan)=x比talib多一个值 assert np.allclose(result1[1:], result3[1:], equal_nan=True) def test_ATR(self): # !!! talib.set_compatibility(1) from polars_ta.ta.volatility import ATR result1 = talib.ATR(self.high_np, self.low_np, self.close_np, timeperiod=5) result2 = self.df_pl.select(ATR(pl.col("high"), pl.col("low"), pl.col("close"), timeperiod=5)) result3 = result2['high'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1[-20:], result3[-20:], equal_nan=True) ================================================ FILE: tests/ta/test_volume.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.random.rand(100) self.volume_np = np.arange(100, dtype=float) + np.random.rand(100) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np, self.volume_np], schema=["high", "low", "close", "volume"]) def test_AD(self): from polars_ta.ta.volume import AD result1 = talib.AD(self.high_np, self.low_np, self.close_np, self.volume_np) result2 = self.df_pl.select(AD(pl.col("high"), pl.col("low"), pl.col("close"), pl.col("volume"))) result3 = result2['close'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) def test_ADOSC(self): from polars_ta.ta.volume import ADOSC result1 = talib.ADOSC(self.high_np, self.low_np, self.close_np, self.volume_np) result2 = self.df_pl.select(ADOSC(pl.col("high"), pl.col("low"), pl.col("close"), pl.col("volume"))) result3 = result2['close'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) def test_OBV(self): from polars_ta.ta.volume import OBV result1 = talib.OBV(self.close_np, self.volume_np) result2 = self.df_pl.select(OBV(pl.col("close"), pl.col("volume"))) result3 = result2['close'].to_numpy() # print() # print(result1) # print(result3) assert np.allclose(result1, result3, equal_nan=True) ================================================ FILE: tests/tdx/chip_test.py ================================================ import numpy as np import polars as pl from polars_ta.tdx.pattern import ts_WINNER_COST high = np.array([10.4, 10.2, 10.2, 10.4, 10.5, 10.7, 10.7, 10.7, 10.8, 10.9]) low = np.array([10.3, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9]) avg = np.array([10.3, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9]) close = np.array([10.3, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9]) turnover = np.array([0.3, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) cost = np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]) step = 0.1 df = pl.DataFrame({'high': high, 'low': low, 'avg': avg, 'close': close, 'turnover': turnover, 'cost': cost}) df = df.with_columns(WINNER=ts_WINNER_COST(pl.col('high'), pl.col('low'), pl.col('avg'), pl.col('turnover'), pl.col('close'), 0.5, step=step)) print(df) ================================================ FILE: tests/tdx/test_reference.py ================================================ import numpy as np import polars as pl import talib class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) def test_EXPMEMA(self): from polars_ta.tdx.reference import EXPMEMA result1 = talib.EMA(self.close_np, timeperiod=6) result2 = self.df_pl.select(EXPMEMA(pl.col("close"), N=6)) result3 = result2['close'].to_numpy() assert np.allclose(result1, result3, equal_nan=True) def test_BARSLAST(self): from polars_ta.tdx.reference import BARSLAST df = pl.DataFrame({'A': [1, 1, 1, 1, 1, 1]}) df = pl.DataFrame({'A': [0, 0, 0, 0, 0, 0, 0, 0]}) result2 = df.select(BARSLAST(pl.col('A'))) print(result2) def test_BARSLASTCOUNT(self): from polars_ta.tdx.reference import BARSLASTCOUNT df = pl.DataFrame({'A': [1, 1, 1, 1, 1, 1]}) df = pl.DataFrame({'A': [0, 0, 0, 0, 0, 0, 0, 0]}) result2 = df.select(BARSLASTCOUNT(pl.col('A'))) print(result2) def test_BARSSINCEN(self): from polars_ta.tdx.reference import BARSSINCEN df = pl.DataFrame({'A': [1, 1, 1, 1, 1, 1, 1, 1]}) df = pl.DataFrame({'A': [0, 2, 1, 0, 0, 0, 0, 0]}) result2 = df.select(BARSSINCEN(pl.col('A'), 4)) print(result2) def test_LOD(self): from polars_ta.tdx.reference import LOD df = pl.DataFrame({'A': [0, 1, 0, 0, 1, 1, 1, 1]}) result2 = df.select(pl.col('A').rank()) print(result2) result2 = df.select(LOD(pl.col('A'), 3)) print(result2) def test_FILTER(self): from polars_ta.tdx.reference import FILTER df = pl.DataFrame({'A': [0, 1, 0, 1, 1, 1, 1, 1]}) # result2 = df.select(FILTER(pl.col('A'), 3)) # print(result2) ================================================ FILE: tests/tdx/test_statistic.py ================================================ import time import numpy as np import polars as pl class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) self.df_pl = self.df_pl.with_columns(pl.lit(None).alias("null")) def test_AVEDEV(self): from polars_ta.tdx._slow import AVEDEV as func_slow from polars_ta.tdx.statistic import AVEDEV as func_fast xx = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), a2=func_fast(pl.col('high'), 10), ) print(xx) t1 = time.perf_counter() for i in range(1000): result2 = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(1000): result2 = self.df_pl.with_columns( a1=func_fast(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) ================================================ FILE: tests/wq/test_arithmetic.py ================================================ import numpy as np import polars as pl from polars_ta.wq.arithmetic import add class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.arange(100, dtype=float) + np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) self.df_pl = self.df_pl.with_columns(pl.lit(None).alias("null")) def test_add(self): print(self.df_pl.schema) result2 = self.df_pl.select(add(pl.col('high'), pl.col('low'), pl.col('null'), filter_=True)) print(result2) # result3 = self.df_pl.select(add(pl.col('high'), pl.col('low'), pl.col('literal'), filter_=True)) # print(result3) def test_fraction(self): from polars_ta.wq.arithmetic import fraction a = np.array([5.63, -4.59]) df = pl.DataFrame({'a': a}) result1 = df.select(fraction(pl.col('a'))) result3 = np.array([0.63, -0.59]) assert np.allclose(result1.to_numpy()[:, 0], result3, equal_nan=True) ================================================ FILE: tests/wq/test_time_series.py ================================================ import time import numpy as np import polars as pl from pandas.testing import assert_frame_equal, assert_series_equal class TestDemoClass: high_np = None low_np = None close_np = None df_pl = None def setup_class(self): self.high_np = np.random.rand(100) self.low_np = np.arange(100, dtype=float) - np.random.rand(100) self.close_np = np.arange(100, dtype=float) self.df_pl = pl.DataFrame([self.high_np, self.low_np, self.close_np], schema=["high", "low", "close"]) self.df_pl = self.df_pl.cast(pl.Float32) self.df_pl = self.df_pl.with_columns(pl.lit(None).alias("null")) self.df_pd = self.df_pl.to_pandas() def test_ts_rank(self): from polars_ta.wq.time_series import ts_rank result1 = self.df_pd[["high", 'low']].rolling(5).rank(pct=True) result2 = self.df_pl.select(ts_rank(pl.col(["high", 'low']), d=5)) # 底层一样,结果就应当一样 assert_frame_equal(result1, result2.to_pandas()) def test_ts_skewness(self): from polars_ta.wq.time_series import ts_skewness # cannot set tune bias in pandas # 好像没办法告诉pandas关闭偏差校正 result1 = self.df_pd[["high"]].rolling(5).skew() result2 = self.df_pl.select(ts_skewness(pl.col(["high"]), d=5)) # print(result1) # print(result2) assert_frame_equal(result1, result2.to_pandas().astype(float)) def test_ts_kurtosis(self): from polars_ta.wq.time_series import ts_kurtosis result1 = self.df_pd[["high"]].rolling(5).kurt() result2 = self.df_pl.select(ts_kurtosis(pl.col(["high"]), d=5)) # 底层一样,结果就应当一样 assert_frame_equal(result1, result2.to_pandas()) def test_ts_corr(self): from polars_ta.wq.time_series import ts_corr result1 = self.df_pd["high"].rolling(5).corr(self.df_pd["low"]) result2 = self.df_pl.select(ts_corr(pl.col("high"), pl.col("low"), d=5)) # assert_series_equal(result1, result2.to_series(0).to_pandas(), check_names=False) def test_ts_covariance(self): from polars_ta.wq.time_series import ts_covariance result1 = self.df_pd["high"].rolling(5).cov(self.df_pd["low"]) result2 = self.df_pl.select(ts_covariance(pl.col("high"), pl.col("low"), d=5)) # assert_series_equal(result1, result2.to_series(0).to_pandas().astype(float), check_names=False) def test_ts_decay_exp_window(self): df = pl.DataFrame({'A': range(100)}) print(df) # result1 = df.select(ts_decay_exp_window(pl.col('A'), 10, factor=0.5)) # print(result1) def test_ts_decay_linear(self): df = pl.DataFrame({'A': [None] * 10, 'B': [None, None, None, None, 4, 5, 6, 7, 8, 9]}) print(df) # result1 = df.select(ts_decay_linear(pl.col('B'), 3)) # print(result1) def test_ts_weighted_delay(self): from polars_ta.wq.time_series import ts_weighted_delay df = pl.DataFrame({'A': [4, 6]}) print(df) result1 = df.select(ts_weighted_delay(pl.col('A'), k=0.25)) print(result1) def test_ts_arg_max(self): from polars_ta.wq._slow import ts_arg_min as func_slow from polars_ta.wq.time_series import ts_arg_min as func_fast xx = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), a2=func_fast(pl.col('high'), 10), ) print(xx) t1 = time.perf_counter() for i in range(10000): result2 = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10000): result2 = self.df_pl.with_columns( a1=func_fast(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) def test_ts_product(self): from polars_ta.wq._slow import ts_product as func_slow from polars_ta.wq.time_series import ts_product as func_fast xx = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), a2=func_fast(pl.col('high'), 10), ) print(xx) t1 = time.perf_counter() for i in range(10000): result2 = self.df_pl.with_columns( a1=func_slow(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) t1 = time.perf_counter() for i in range(10000): result2 = self.df_pl.with_columns( a1=func_fast(pl.col('high'), 10), ) t2 = time.perf_counter() print(t2 - t1) ================================================ FILE: thinking_about_TA.md ================================================ # About Technical Indicators There are three types of technical indicators 1. Time series indicators. Must be sorted by time, and then calculated. It is difficult to handle null in the middle. 2. Cross-sectional indicators. No order requirement, the whole row is calculated, and there may be a null, which needs to be compatible. 3. Single element indicators. Can be single-column, multi-column, and can be calculated in any way. In terms of calculation, time series indicators have one more dimension than cross-sectional indicators. For example 1. `cs_rank` is a cross-sectional sort 2. `ts_rank` is to calculate `cs_rank` in a rolling time window, take `[-1]` each time, and then concatenate them 3. `cs_zscore` first aggregates to calculate `mean` and `std`, then broadcast `mean` and `std`, and perform a one-dimensional calculation with `x` So common technical indicators are generally the flexible application of `aggregation` and `broadcasting` Since `ts_` indicators are based on `cs_`, theoretically `rolling` can be used directly, but in practice, it is generally not used in this way. `cs_` is generally rewritten in `cython`, `numba`, etc., and if you mix `ts_` and `cs_`, `ts_` will make thousands of `cs_` call. So it is common to put `rolling` operations into `cython`, `numba`, etc. as well. ## Evolve of Our TA-Lib Wrappers 1. `Expr.map_batches` can be used to call third-party libraries, such as `TA-Lib, bottleneck`. But because of the input and output format requirements, you need to wrap the third-party API with a function. - Both input and output can only be one column. If you want to support multiple columns, you need to convert them to `pl.Struct`. After that, you need to use `unnest` to split `pl.Struct`. - The output must be `pl.Series` 2. Start to use `register_expr_namespace` to simplify the code - Implementation [helper.py](polars_ta/utils/helper.py) - Usage demo [demo_ta1.py](examples/demo_ta1.py) - Pros: Easy to use - Cons: - The `member function call mode` is not convenient for inputting into genetic algorithms for factor mining - `__getattribute__` dynamic method call is very flexible, but loses `IDE` support. 3. Prefix expression. Convert all member functions into formulas - Implementation [wrapper.py](polars_ta/utils/wrapper.py) - Usage demo [demo_ta2.py](examples/demo_ta2.py) - Pros: Can be input into our implementation of genetic algorithms - Cons: `__getattribute__` dynamic method call is very flexible, but loses `IDE` support. 4. Code generation. - Implementation [codegen_talib.py](tools/codegen_talib.py) - Generated result will be at [\_\_init\_\_.py](polars_ta/talib/__init__.py) - Usage demo [demo_ta3.py](examples/demo_ta3.py) - Pros: - Can be input into our implementation of genetic algorithms - `IDE` support # 对技术指标的再思考 用使用方式来说,指标分为三种 1. 时序指标。必须按时间排序,然后计算,中段出现null比较难处理 2. 截面指标。对顺序无要求,整行计算即可,数据会有null,需要能兼容 3. 单元素指标。可单列、多列,可任意方式计算 从计算原理上来说,时序指标比截面指标多了一个计算维度。例如 1. `cs_rank`是横截面排序 2. 而`ts_rank`是对数据滚动时间窗口计算`cs_rank`,每次取`[-1]`,然后拼接起来 又例如 1. `cs_zscore`先聚合计算`mean`和`std` 2. 然后将`mean`与`std`广播,与`x`进行一维计算 所以常见的技术指标一般是`聚合`与`广播`的灵活应用 由于`ts_`指标基于`cs_`,理论上直接`rolling`即可,但在实践中一般不这么用,因为为了求快,`cs_`一般是更底层的语言编写,如`cython`、`numba`等, 本来`cs_`版`python`与底层只交互一次,而`ts_`调用`cs_`版会导致交互千万次,性能极低 一般是把`rolling`操作也放在底层,在底层循环调用`cs_`底层版 ## TA-Lib封装的演化 1. `Expr.map_batches`可以实现调用第三方库,如`TA-Lib, bottleneck`。但因为对输入与输出格式有要求,所以还需要用函数对第三方API封装一下。 - 输入输出都只能是一列,如要支持多列需转换成`pl.Struct`。事后`pl.Struct`要拆分需使用`unnest` - 输出必须是`pl.Series` 2. 参数多,代码长。开始使用`register_expr_namespace`来简化代码 - 实现代码[helper.py](polars_ta/utils/helper.py) - 使用演示[demo_ta1.py](examples/demo_ta1.py) - 优点:使用简单 - 不足:`成员函数调用模式`不便于输入到遗传算法中进行因子挖掘 - 不足:`__getattribute__`动态方法调用非常灵活,但失去了`IDE`智能提示 3. 前缀表达式。将所有的成员函数都转换成公式 - 实现代码[wrapper.py](polars_ta/utils/wrapper.py) - 使用演示[demo_ta2.py](examples/demo_ta2.py) - 优点:可以输入到遗传算法 - 不足:`__getattribute__`动态方法调用非常灵活,但失去了`IDE`智能提示 4. 代码自动生成。 - 实现代码[codegen_talib.py](tools/codegen_talib.py) - 生成结果[\_\_init\_\_.py](polars_ta/talib/__init__.py) - 使用演示[demo_ta3.py](examples/demo_ta3.py) - 优点:即可以输入到遗传算法,`IDE`还有智能提示 ================================================ FILE: tools/README.md ================================================ # Code Conversion Tools ## TA-Lib tool (codegen_talib.py) Wrap the original `TA-Lib` and save it to `polars_ta.talib.__init__.py`. 1. Support `Expr` 2. Support `skipna` 3. Multi-output support ## Prefix adding tool Add the `ts_` prefix to some functions to facilitate use in `expr_codegen` and other tools. 1. `prefix_ta.py` adds a prefix to `polars_ta.ta` and saves it to `polars_ta.prefix.ta.py` 2. `prefix_tdx.py` adds a prefix to `polars_ta.tdx` and saves it to `polars_ta.prefix.tdx.py` 3. `prefix_talib.py` adds a prefix to `polars_ta.talib` and saves it to `polars_ta.prefix.talib.py` (same as `codegen_talib`) # 代码转换工具 ## TA-Lib工具(codegen_talib.py) 将原版`TA-Lib`封装,保存到`polars_ta.talib.__init__.py`,支持以下功能 1. 支持`Expr` 2. 支持跳过空值 3. 多输出适配 ## 前缀添加工具 为部分函数添加`ts_`前缀,方便在`expr_codegen`等工具中使用 1. `prefix_ta.py`为`polars_ta.ta`添加前缀,保存到`polars_ta.prefix.ta.py` 2. `prefix_tdx.py`为`polars_ta.tdx`添加前缀,保存到`polars_ta.prefix.tdx.py` 3. `prefix_talib.py`为`polars_ta.talib`添加前缀,保存到`polars_ta.prefix.talib.py`(使用与codegen_talib同技术) ================================================ FILE: tools/codegen_talib.py ================================================ """ Parse talib functions for polars Expressions This version is more direct, without skip nan values, and without input and output checks polars does not skip nan values as well. It should be processed by specific functions 本脚本主要功能是将talib封装成更适合表达式的版本 与另一版本的区别是这版本调用更直接,没有跳过空的操作,也没有输入与输出数量的判断工作 跳过空值等操作与polars样都不做,以后准备统一交给函数处理 """ import talib as _talib from talib import abstract as _abstract from tools.prefix import save def _codegen_func(name, input_names, parameters, output_names, doc): tpl11 = """ def {name}({aa}) -> Expr: # {output_names} \"\"\"{doc}\"\"\" return {bb}.map_batches(lambda x1: batches_i1_o1(x1.to_numpy().astype(float), {cc}), return_dtype={return_dtype}) """ tpl12 = """ def {name}({aa}) -> Expr: # {output_names} \"\"\"{doc}\"\"\" dtype = Struct([Field(f"column_{{i}}", Float64) for i in range({ee})]) return {bb}.map_batches(lambda x1: batches_i1_o2(x1.to_numpy().astype(float), {cc}), return_dtype=dtype) """ tpl21 = """ def {name}({aa}) -> Expr: # {output_names} \"\"\"{doc}\"\"\" return struct({bb}).map_batches(lambda xx: batches_i2_o1(struct_to_numpy(xx, {dd}, dtype=float), {cc}), return_dtype={return_dtype}) """ tpl22 = """ def {name}({aa}) -> Expr: # {output_names} \"\"\"{doc}\"\"\" dtype = Struct([Field(f"column_{{i}}", Float64) for i in range({ee})]) return struct({bb}).map_batches(lambda xx: batches_i2_o2(struct_to_numpy(xx, {dd}, dtype=float), {cc}), return_dtype=dtype) """ if len(output_names) > 42: extra_args = {'ret_idx': len(output_names) - 1} else: extra_args = {} a1 = [f'{name}: Expr' for name in input_names] a2 = [f'{k}: {type(v).__name__} = {v}' for k, v in parameters.items()] a3 = [f'{k}: {type(v).__name__} = {v}' for k, v in extra_args.items()] aa = ', '.join(a1 + a2 + a3) bb = ', '.join(input_names) if len(input_names) > 1: bb = [f'f{i}={arg}' for i, arg in enumerate(input_names)] bb = ', '.join(bb) c1 = [f'_ta.{name}'] if len(parameters) > 0: c2 = [f'{k}' for k, v in parameters.items()] else: c2 = [] c3 = [f'{k}={k}' for k, v in extra_args.items()] cc = ', '.join(c1 + c2 + c3) if output_names[0] == 'integer': return_dtype = 'Int32' else: return_dtype = 'Float64' if len(input_names) == 1 and len(output_names) == 1: return tpl11.format(name=name, aa=aa, bb=bb, cc=cc, dd=len(input_names), ee=len(output_names), output_names=output_names, doc=doc, return_dtype=return_dtype) elif len(input_names) == 1 and len(output_names) > 1: return tpl12.format(name=name, aa=aa, bb=bb, cc=cc, dd=len(input_names), ee=len(output_names), output_names=output_names, doc=doc) elif len(input_names) > 1 and len(output_names) == 1: return tpl21.format(name=name, aa=aa, bb=bb, cc=cc, dd=len(input_names), ee=len(output_names), output_names=output_names, doc=doc, return_dtype=return_dtype) else: return tpl22.format(name=name, aa=aa, bb=bb, cc=cc, dd=len(input_names), ee=len(output_names), output_names=output_names, doc=doc) def codegen(): head_v2 = """# generated by codegen_talib.py import talib as _ta from polars import Expr, struct, Struct, Field, Float64, Int32 from polars_ta.utils.numba_ import batches_i1_o1, batches_i1_o2, batches_i2_o1, batches_i2_o2, struct_to_numpy """ txts = [head_v2] for i, func_name in enumerate(_talib.get_functions()): """talib遍历""" info = _abstract.Function(func_name).info name = info['name'] input_names = [] for in_names in info['input_names'].values(): if isinstance(in_names, (list, tuple)): input_names.extend(list(in_names)) else: input_names.append(in_names) parameters = info['parameters'] output_names = info['output_names'] txt = _codegen_func(name, input_names, parameters, output_names, getattr(_talib, name).__doc__) txts.append(txt) return txts if __name__ == '__main__': txts = codegen() save(txts, module='polars_ta.talib', write=True) ================================================ FILE: tools/prefix.py ================================================ import inspect from typing import List, Optional def codegen_import_as(module: str, prefix: str = 'ts_', include_modules: Optional[List[str]] = None, include_func: Optional[List[str]] = None, exclude_func: Optional[List[str]] = None, include_parameter: Optional[List[str]] = None): """Generate codes by `reflection` 通过反射,生成代码的小工具 Parameters ---------- module 模块全名 prefix 需要添加的前缀 include_modules 通过`from import`导入的函数也参与判断 include_func 指定的函数名不做检查,直接添加前缀 exclude_func 指定的函数名直接跳过不导入 include_parameter 出现了同名int参数,添加前缀 Notes ----- """ if include_modules is None: include_modules = [module] else: include_modules += [module] if include_func is None: include_func = [] if exclude_func is None: exclude_func = [] if include_parameter is None: include_parameter = [] m = __import__(module, fromlist=['*']) funcs = inspect.getmembers(m, inspect.isfunction) funcs = [f for f in funcs if not f[0].startswith('_')] txts = [] for name, func in funcs: if func.__module__ not in include_modules: continue if name in exclude_func: continue add_prefix = False if name.startswith(prefix): add_prefix = False elif name in include_func: add_prefix = True else: p = inspect.signature(func).parameters for n in include_parameter: if n in p: if p[n].annotation == int: add_prefix = True break if add_prefix: txts.append(f'from {module} import {name} as {prefix}{name} # noqa') else: txts.append(f'from {module} import {name} # noqa') return txts def save(txts, module, write=False): m = __import__(module, fromlist=['*']) file = m.__file__ print('save to', file) text = '\n'.join(txts) if write: with open(file, 'w', encoding='utf-8') as f: f.write(text) else: print(text) ================================================ FILE: tools/prefix_ta.py ================================================ from tools.prefix import codegen_import_as, save lines = ["""# this code is auto generated by tools/prefix_ta.py """] lines += codegen_import_as('polars_ta.ta.momentum', include_parameter=['timeperiod', 'fastperiod', 'fastk_period']) lines += codegen_import_as('polars_ta.ta.operators', include_modules=['polars_ta.wq.arithmetic', 'polars_ta.wq.time_series'], include_parameter=['timeperiod', 'd'], exclude_func=['ts_arg_max', 'ts_arg_min']) lines += codegen_import_as('polars_ta.ta.overlap', include_modules=['polars_ta.wq.time_series'], include_func=['SMA', 'WMA'], include_parameter=['timeperiod', 'd'], exclude_func=['MAX', 'MIN']) lines += codegen_import_as('polars_ta.ta.price', include_parameter=['timeperiod']) lines += codegen_import_as('polars_ta.ta.statistic', include_modules=['polars_ta.wq.time_series'], include_parameter=['timeperiod', 'd'], exclude_func=['ts_std_dev']) lines += codegen_import_as('polars_ta.ta.transform', include_modules=['polars_ta.wq.arithmetic'], include_parameter=['timeperiod', 'd']) lines += codegen_import_as('polars_ta.ta.volatility', include_func=['TRANGE'], include_parameter=['timeperiod']) lines += codegen_import_as('polars_ta.ta.volume', include_func=['AD', 'OBV'], include_parameter=['fastperiod']) save(lines, module='polars_ta.prefix.ta', write=True) ================================================ FILE: tools/prefix_talib.py ================================================ """ Add prefix to talib functions 本脚本主要功能是将talib封装添加前缀 """ import talib as _talib from talib import abstract as _abstract from tools.prefix import save def codegen(): txts = [] for i, func_name in enumerate(_talib.get_functions()): """talib遍历""" info = _abstract.Function(func_name).info group = info['group'] name = info['name'] if group in ('Math Operators', 'Math Transform', 'Price Transform'): txts.append(f'from polars_ta.talib import {name} # noqa') else: txts.append(f'from polars_ta.talib import {name} as ts_{name} # noqa') return txts if __name__ == '__main__': txts = ["""# this code is auto generated by tools/prefix_talib.py """] txts += codegen() save(txts, module='polars_ta.prefix.talib', write=True) ================================================ FILE: tools/prefix_tdx.py ================================================ from tools.prefix import codegen_import_as, save lines = ["""# this code is auto generated by tools/prefix_tdx.py """] lines += codegen_import_as('polars_ta.tdx.arithmetic', include_modules=['polars_ta.wq.arithmetic']) lines += codegen_import_as('polars_ta.tdx.choice', include_func=['VALUEWHEN']) lines += codegen_import_as('polars_ta.tdx.energy', include_parameter=['N', 'N1']) lines += codegen_import_as('polars_ta.tdx.logical', include_func=['CROSS', 'LONGCROSS', 'EXISTR', 'LAST'], include_parameter=['N']) lines += codegen_import_as('polars_ta.tdx.moving_average', include_parameter=['M1']) lines += codegen_import_as('polars_ta.tdx.over_bought_over_sold', include_modules=['polars_ta.ta.momentum'], include_parameter=['N', 'timeperiod']) lines += codegen_import_as('polars_ta.tdx.pattern') lines += codegen_import_as('polars_ta.tdx.pattern_feature', include_parameter=['N', 'M', 'N1'], include_func=['早晨之星', '剑', '天量法则', '四串阴', '四串阳', '出水芙蓉II', '高开大阴线', '低开大阳线', '跳空缺口选股', '老鸭头', '仙人指路']) lines += codegen_import_as('polars_ta.tdx.pressure_support', include_parameter=['N']) lines += codegen_import_as('polars_ta.tdx.reference', include_modules=['polars_ta.ta.overlap', 'polars_ta.ta.volatility', 'polars_ta.wq.arithmetic', 'polars_ta.wq.time_series'], include_func=['BARSLAST', 'BARSLASTCOUNT', 'BARSSINCE', 'DMA', 'CUMSUM', 'TR'], include_parameter=['N', 'd', 'timeperiod']) lines += codegen_import_as('polars_ta.tdx.statistic', include_modules=['polars_ta.wq.time_series'], include_parameter=['timeperiod', 'd']) lines += codegen_import_as('polars_ta.tdx.times', include_parameter=[]) lines += codegen_import_as('polars_ta.tdx.trend', include_parameter=['N']) lines += codegen_import_as('polars_ta.tdx.trend_feature', include_parameter=['N', 'M', 'N1'], include_func=['下跌多日再放量上涨', '价量渐低后阳包阴', '单日放量', '跳空高开或低开']) lines += codegen_import_as('polars_ta.tdx.volume', include_func=['OBV'], include_parameter=['N']) save(lines, module='polars_ta.prefix.tdx', write=True) ================================================ FILE: tools/prefix_vec.py ================================================ """ 将vec_xxx改成cs_vec_xxx 主要用于gp_vect_xxxx转换成cs_vec_xxxx """ import inspect from typing import Optional, List from tools.prefix import save def codegen_import_as(module: str, prefix: str = 'cs_', include_modules: Optional[List[str]] = None, include_func: Optional[List[str]] = None, exclude_func: Optional[List[str]] = None, include_parameter: Optional[List[str]] = None): """Generate codes by `reflection` 通过反射,生成代码的小工具 Parameters ---------- module 模块全名 prefix 需要添加的前缀 include_modules 通过`from import`导入的函数也参与判断 include_func 指定的函数名不做检查,直接添加前缀 exclude_func 指定的函数名直接跳过不导入 include_parameter 出现了同名int参数,添加前缀 Notes ----- """ if include_modules is None: include_modules = [module] else: include_modules += [module] if include_func is None: include_func = [] if exclude_func is None: exclude_func = [] if include_parameter is None: include_parameter = [] m = __import__(module, fromlist=['*']) funcs = inspect.getmembers(m, inspect.isfunction) funcs = [f for f in funcs if not f[0].startswith('_')] txts = [] for name, func in funcs: if func.__module__ not in include_modules: continue if name in exclude_func: continue add_prefix = True if add_prefix: txts.append(f'from {module} import {name} as {prefix}{name} # noqa') else: txts.append(f'from {module} import {name} # noqa') return txts lines = ["""# this code is auto generated by tools/prefix_vec.py """] lines += codegen_import_as('polars_ta.wq.vector', prefix='cs_') save(lines, module='polars_ta.prefix.vec', write=True) ================================================ FILE: tools/prompt.py ================================================ import inspect from typing import List, Optional def get_annotation(annotation): output = annotation.__name__ if output == "Optional": output = str(annotation).split('.')[-1] return output def get_parameter(p): annotation = get_annotation(p.annotation) if p.kind == inspect._ParameterKind.VAR_POSITIONAL: output = f"*{p.name}" elif p.kind == inspect._ParameterKind.VAR_KEYWORD: output = f"**{p.name}" else: output = p.name if annotation not in ("Expr", "_empty"): output += f":{annotation}" if p.default != inspect._empty: output += f"={p.default}" return output def codegen_import_as(module: str, include_modules: Optional[List[str]] = None, include_func: Optional[List[str]] = None, exclude_func: Optional[List[str]] = None): """Generate codes by `reflection` 通过反射,生成代码的小工具 Parameters ---------- module 模块全名 include_modules 通过`from import`导入的函数也参与判断 include_func 指定的函数名不做检查,直接添加前缀 exclude_func 指定的函数名直接跳过不导入 Notes ----- """ if include_modules is None: include_modules = [module] else: include_modules += [module] if include_func is None: include_func = [] if exclude_func is None: exclude_func = [] m = __import__(module, fromlist=['*']) funcs = inspect.getmembers(m, inspect.isfunction) funcs = [f for f in funcs if not f[0].startswith('_')] txts = [f"### {module}"] for name, func in funcs: if func.__module__ not in include_modules: continue if exclude_func and name in exclude_func: continue if include_func and name not in include_func: continue # if name != "cs_resid": # continue signature = inspect.signature(func) parameters = [] for n, p in signature.parameters.items(): if p.name == "min_samples": continue parameters.append(get_parameter(p)) # txts.append(f'- {name}({",".join(parameters)})->{get_annotation(signature.return_annotation)} : {func.__doc__.splitlines()[0]}') txts.append(f'- {name}({",".join(parameters)}) : {func.__doc__.splitlines()[0]}') return txts # 过滤一些很少用到的函数,最好控制在5000字内(百度5000字限制)。部分智能体只支持1000字符 lines = [] lines += codegen_import_as('polars_ta.wq.arithmetic', exclude_func=['add', 'subtract', 'multiply', 'div', 'divide', 'reverse', 'inverse', "mean", 'arc_cos', 'arc_sin', 'arc_tan', 'arc_tan2', 'cot', 'cosh', 'tanh', 'sinh', 'degrees', 'radians', 's_log_1p', 'softsign', 'log2', "expm1", ]) lines += codegen_import_as('polars_ta.wq.time_series', exclude_func=['ts_co_kurtosis', 'ts_co_skewness', 'ts_count_nans', 'ts_count_nulls', 'ts_cum_prod', 'ts_cum_prod_by', 'ts_cum_sum', 'ts_cum_sum_by', 'ts_cum_sum_reset', "ts_min_max_cps", "ts_min_max_diff", "ts_signals_to_size", 'ts_sum_split_by', "ts_cum_count", "ts_cum_max", "ts_cum_min", "ts_triple_corr", "ts_partial_corr", "ts_max_diff", "ts_min_diff", ]) lines += codegen_import_as('polars_ta.wq.cross_sectional', exclude_func=['cs_fill_except_all_null', 'cs_fill_mean', 'cs_fill_max', 'cs_fill_min', 'cs_top_bottom', 'cs_one_side', 'cs_regression_neut', 'cs_regression_proj', 'cs_scale_down', 'cs_truncate']) lines += codegen_import_as('polars_ta.wq.preprocess', exclude_func=['cs_mad_zscore', 'cs_mad_zscore_resid', 'cs_mad_zscore_resid_zscore', 'cs_zscore_resid']) lines += codegen_import_as('polars_ta.wq.logical', include_func=['if_else', ]) lines += codegen_import_as('polars_ta.wq.transformational', include_func=['cut', 'sigmoid', 'bool_', 'int_', 'float_']) text = '\n'.join(lines) print(text)