PandasTA 源码解析(三)

.\pandas-ta\pandas_ta\custom.py

# 设置文件编码为 UTF-8
# -*- coding: utf-8 -*-

# 导入必要的模块
import importlib  # 动态导入模块的工具
import os  # 提供与操作系统交互的功能
import sys  # 提供与 Python 解释器交互的功能
import types  # 提供对 Python 类型和类的支持

# 从 os.path 模块中导入指定函数,避免命名冲突
from os.path import abspath, join, exists, basename, splitext
# 从 glob 模块中导入指定函数,用于文件匹配
from glob import glob

# 导入 pandas_ta 模块,并指定 AnalysisIndicators 类
import pandas_ta
from pandas_ta import AnalysisIndicators


def bind(function_name, function, method):
    """
    辅助函数,将自定义指标模块中定义的函数和类方法绑定到活动的 pandas_ta 实例。

    Args:
        function_name (str): 在 pandas_ta 中指标的名称
        function (fcn): 指标函数
        method (fcn): 与传递的函数对应的类方法
    """
    # 将指标函数绑定到 pandas_ta 实例
    setattr(pandas_ta, function_name, function)
    # 将类方法绑定到 AnalysisIndicators 类
    setattr(AnalysisIndicators, function_name, method)


def create_dir(path, create_categories=True, verbose=True):
    """
    辅助函数,为使用自定义指标设置合适的文件夹结构。每当想要设置新的自定义指标文件夹时,只需调用此函数一次。

    Args:
        path (str): 指标树的完整路径
        create_categories (bool): 如果为 True,则创建类别子文件夹
        verbose (bool): 如果为 True,则打印结果的详细输出
    """

    # 确保传递的目录存在/可读
    if not exists(path):
        os.makedirs(path)
        # 如果 verbose 为 True,则打印已创建主目录的消息
        if verbose:
            print(f"[i] Created main directory '{path}'.")

    # 列出目录的内容
    # dirs = glob(abspath(join(path, '*')))

    # 可选地添加任何缺少的类别子目录
    if create_categories:
        for sd in [*pandas_ta.Category]:
            d = abspath(join(path, sd))
            if not exists(d):
                os.makedirs(d)
                # 如果 verbose 为 True,则打印已创建空子目录的消息
                if verbose:
                    dirname = basename(d)
                    print(f"[i] Created an empty sub-directory '{dirname}'.")


def get_module_functions(module):
    """
    辅助函数,以字典形式获取导入模块的函数。

    Args:
        module: Python 模块

    Returns:
        dict: 模块函数映射
        {
            "func1_name": func1,
            "func2_name": func2,...
        }
    """
    module_functions = {}

    for name, item in vars(module).items():
        if isinstance(item, types.FunctionType):
            module_functions[name] = item

    return module_functions


def import_dir(path, verbose=True):
    # 确保传递的目录存在/可读
    if not exists(path):
        print(f"[X] Unable to read the directory '{path}'.")
        return

    # 列出目录的内容
    dirs = glob(abspath(join(path, "*")))

    # 遍历整个目录,导入找到的所有模块
    # 对每个目录进行遍历
    for d in dirs:
        # 获取目录的基本名称
        dirname = basename(d)

        # 仅在目录是有效的 pandas_ta 类别时才进行处理
        if dirname not in [*pandas_ta.Category]:
            # 如果启用了详细输出,则打印消息跳过非有效 pandas_ta 类别的子目录
            if verbose:
                print(f"[i] Skipping the sub-directory '{dirname}' since it's not a valid pandas_ta category.")
            continue

        # 对该类别(目录)中找到的每个模块进行处理
        for module in glob(abspath(join(path, dirname, "*.py"))):
            # 获取模块的名称(不带扩展名)
            module_name = splitext(basename(module))[0]

            # 确保提供的路径被包含在我们的 Python 路径中
            if d not in sys.path:
                sys.path.append(d)

            # (重新)加载指标模块
            module_functions = load_indicator_module(module_name)

            # 确定要绑定到 pandas_ta 的哪些模块函数
            fcn_callable = module_functions.get(module_name, None)
            fcn_method_callable = module_functions.get(f"{module_name}_method", None)

            # 如果找不到可调用的函数,则打印错误消息并继续下一个模块
            if fcn_callable == None:
                print(f"[X] Unable to find a function named '{module_name}' in the module '{module_name}.py'.")
                continue
            # 如果找不到可调用的方法函数,则打印错误消息并继续下一个模块
            if fcn_method_callable == None:
                missing_method = f"{module_name}_method"
                print(f"[X] Unable to find a method function named '{missing_method}' in the module '{module_name}.py'.")
                continue

            # 如果模块名称尚未在相应类别中,则将其添加到类别中
            if module_name not in pandas_ta.Category[dirname]:
                pandas_ta.Category[dirname].append(module_name)

            # 将函数绑定到 pandas_ta
            bind(module_name, fcn_callable, fcn_method_callable)
            # 如果启用了详细输出,则打印成功导入自定义指标的消息
            if verbose:
                print(f"[i] Successfully imported the custom indicator '{module}' into category '{dirname}'.")
# 将 import_dir 函数的文档字符串赋值给 import_dir.__doc__,用于说明该函数的作用和用法
import_dir.__doc__ = \
"""
Import a directory of custom indicators into pandas_ta

Args:
    path (str): Full path to your indicator tree  # 参数:指定自定义指标所在目录的完整路径
    verbose (bool): If True verbose output of results  # 参数:如果为 True,则输出详细的结果信息

This method allows you to experiment and develop your own technical analysis
indicators in a separate local directory of your choice but use them seamlessly
together with the existing pandas_ta functions just like if they were part of
pandas_ta.

If you at some late point would like to push them into the pandas_ta library
you can do so very easily by following the step by step instruction here
https://github.com/twopirllc/pandas-ta/issues/355.

A brief example of usage:

1. Loading the 'ta' module:
>>> import pandas as pd
>>> import pandas_ta as ta

2. Create an empty directory on your machine where you want to work with your
indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with
sub-folders for all available indicator categories, e.g.:

>>> import os
>>> from os.path import abspath, join, expanduser
>>> from pandas_ta.custom import create_dir, import_dir
>>> ta_dir = abspath(join(expanduser("~"), "my_indicators"))
>>> create_dir(ta_dir)

3. You can now create your own custom indicator e.g. by copying existing
ones from pandas_ta core module and modifying them.

IMPORTANT: Each custom indicator should have a unique name and have both
a) a function named exactly as the module, e.g. 'ni' if the module is ni.py
b) a matching method used by AnalysisIndicators named as the module but
   ending with '_method'. E.g. 'ni_method'

In essence these modules should look exactly like the standard indicators
available in categories under the pandas_ta-folder. The only difference will
be an addition of a matching class method.

For an example of the correct structure, look at the example ni.py in the
examples folder.

The ni.py indicator is a trend indicator so therefore we drop it into the
sub-folder named trend. Thus we have a folder structure like this:

~/my_indicators/
│
├── candles/
.
.
└── trend/
.      └── ni.py
.
└── volume/

4. We can now dynamically load all our custom indicators located in our
designated indicators directory like this:

>>> import_dir(ta_dir)

If your custom indicator(s) loaded succesfully then it should behave exactly
like all other native indicators in pandas_ta, including help functions.
"""


def load_indicator_module(name):
    """
     Helper function to (re)load an indicator module.

    Returns:
        dict: module functions mapping
        {
            "func1_name": func1,
            "func2_name": func2,...
        }

    """
    # 加载指标模块
    try:
        module = importlib.import_module(name)
    except Exception as ex:  # 捕获异常,如果加载模块出错则打印错误信息并退出程序
        print(f"[X] An error occurred when attempting to load module {name}: {ex}")
        sys.exit(1)

    # 刷新之前加载的模块,以便重新加载
    module = importlib.reload(module)
    # 返回模块函数的字典映射,包括模块中定义的所有函数
    return get_module_functions(module)

# `.\pandas-ta\pandas_ta\cycles\ebsw.py`

```py
# -*- coding: utf-8 -*-
# 从 numpy 模块导入 cos 函数并命名为 npCos
from numpy import cos as npCos
# 从 numpy 模块导入 exp 函数并命名为 npExp
from numpy import exp as npExp
# 从 numpy 模块导入 nan 常量并命名为 npNaN
from numpy import nan as npNaN
# 从 numpy 模块导入 pi 常量并命名为 npPi
from numpy import pi as npPi
# 从 numpy 模块导入 sin 函数并命名为 npSin
from numpy import sin as npSin
# 从 numpy 模块导入 sqrt 函数并命名为 npSqrt
from numpy import sqrt as npSqrt
# 从 pandas 模块中导入 Series 类
from pandas import Series
# 从 pandas_ta.utils 模块中导入 get_offset 和 verify_series 函数
from pandas_ta.utils import get_offset, verify_series


# 定义函数 ebsw,用于计算 Even Better SineWave (EBSW) 指标
def ebsw(close, length=None, bars=None, offset=None, **kwargs):
    """Indicator: Even Better SineWave (EBSW)"""
    # 校验参数
    # 如果 length 存在且大于38,则转换为整数,否则默认为40
    length = int(length) if length and length > 38 else 40
    # 如果 bars 存在且大于0,则转换为整数,否则默认为10
    bars = int(bars) if bars and bars > 0 else 10
    # 验证 close 是否为有效 Series 对象
    close = verify_series(close, length)
    # 获取偏移量
    offset = get_offset(offset)

    # 如果 close 为空,则返回 None
    if close is None: return

    # 初始化变量
    alpha1 = HP = 0  # alpha 和 HighPass
    a1 = b1 = c1 = c2 = c3 = 0
    Filt = Pwr = Wave = 0

    lastClose = lastHP = 0
    FilterHist = [0, 0]  # 过滤器历史记录

    # 计算结果
    m = close.size
    result = [npNaN for _ in range(0, length - 1)] + [0]
    for i in range(length, m):
        # 使用周期为 length 的高通滤波器过滤短于 Duration 输入的周期成分
        alpha1 = (1 - npSin(360 / length)) / npCos(360 / length)
        HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP

        # 使用超级平滑滤波器平滑数据(方程 3-3)
        a1 = npExp(-npSqrt(2) * npPi / bars)
        b1 = 2 * a1 * npCos(npSqrt(2) * 180 / bars)
        c2 = b1
        c3 = -1 * a1 * a1
        c1 = 1 - c2 - c3
        Filt = c1 * (HP + lastHP) / 2 + c2 * FilterHist[1] + c3 * FilterHist[0]

        # 计算波动和功率的3根均线
        Wave = (Filt + FilterHist[1] + FilterHist[0]) / 3
        Pwr = (Filt * Filt + FilterHist[1] * FilterHist[1] + FilterHist[0] * FilterHist[0]) / 3

        # 将平均波动归一化到平均功率的平方根
        Wave = Wave / npSqrt(Pwr)

        # 更新存储和结果
        FilterHist.append(Filt)  # 添加新的 Filt 值
        FilterHist.pop(0)  # 移除列表中的第一个元素(最早的)-> 更新/修剪
        lastHP = HP
        lastClose = close[i]
        result.append(Wave)

    # 创建结果 Series 对象
    ebsw = Series(result, index=close.index)

    # 如果有偏移量,则进行偏移
    if offset != 0:
        ebsw = ebsw.shift(offset)

    # 处理填充值
    if "fillna" in kwargs:
        ebsw.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        ebsw.fillna(method=kwargs["fill_method"], inplace=True)

    # 设置指标名称和类别
    ebsw.name = f"EBSW_{length}_{bars}"
    ebsw.category = "cycles"

    return ebsw


# 设置函数 ebsw 的文档字符串
ebsw.__doc__ = \
"""Even Better SineWave (EBSW) *beta*

This indicator measures market cycles and uses a low pass filter to remove noise.
Its output is bound signal between -1 and 1 and the maximum length of a detected
trend is limited by its length input.

Written by rengel8 for Pandas TA based on a publication at 'prorealcode.com' and
a book by J.F.Ehlers.
"""
# 这个实现在逻辑上似乎有限制。最好实现与prorealcode中的版本完全相同,并比较行为。

来源:
    https://www.prorealcode.com/prorealtime-indicators/even-better-sinewave/
    J.F.Ehlers的《Cycle Analytics for Traders》,2014

计算:
    参考'sources'或实现

参数:
    close (pd.Series): 'close'的系列
    length (int): 最大周期/趋势周期。值在40-48之间的效果如预期,最小值为39。默认值:40。
    bars (int): 低通滤波的周期。默认值:10
    drift (int): 差异周期。默认值:1
    offset (int): 结果的偏移周期数。默认值:0

关键字参数:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): 填充方法的类型

返回:
    pd.Series: 生成的新特征。

.\pandas-ta\pandas_ta\cycles\__init__.py

# 设置文件编码格式为 UTF-8
# 从当前目录下的 ebsw 模块中导入 ebsw 函数
from .ebsw import ebsw

.\pandas-ta\pandas_ta\momentum\ao.py

# -*- coding: utf-8 -*-
# 从pandas_ta.overlap模块导入简单移动平均函数sma
from pandas_ta.overlap import sma
# 从pandas_ta.utils模块导入get_offset和verify_series函数
from pandas_ta.utils import get_offset, verify_series


def ao(high, low, fast=None, slow=None, offset=None, **kwargs):
    """Indicator: Awesome Oscillator (AO)"""
    # 验证参数
    # 如果fast存在且大于0,则转换为整数,否则默认为5
    fast = int(fast) if fast and fast > 0 else 5
    # 如果slow存在且大于0,则转换为整数,否则默认为34
    slow = int(slow) if slow and slow > 0 else 34
    # 如果slow小于fast,则交换它们的值
    if slow < fast:
        fast, slow = slow, fast
    # 计算_length为fast和slow中的最大值
    _length = max(fast, slow)
    # 验证high和low的Series长度为_length
    high = verify_series(high, _length)
    low = verify_series(low, _length)
    # 获取offset的偏移量
    offset = get_offset(offset)

    # 如果high或low为None,则返回空
    if high is None or low is None: return

    # 计算结果
    # 计算中间价,即(high + low) / 2
    median_price = 0.5 * (high + low)
    # 计算fast期间的简单移动平均
    fast_sma = sma(median_price, fast)
    # 计算slow期间的简单移动平均
    slow_sma = sma(median_price, slow)
    # 计算AO指标,即fast期间的SMA减去slow期间的SMA
    ao = fast_sma - slow_sma

    # 偏移结果
    if offset != 0:
        # 将结果向前偏移offset个周期
        ao = ao.shift(offset)

    # 处理填充
    # 如果kwargs中有"fillna"参数,则用指定值填充空值
    if "fillna" in kwargs:
        ao.fillna(kwargs["fillna"], inplace=True)
    # 如果kwargs中有"fill_method"参数,则使用指定的填充方法
    if "fill_method" in kwargs:
        ao.fillna(method=kwargs["fill_method"], inplace=True)

    # 命名和分类
    # 设置AO指标的名称,格式为"AO_{fast}_{slow}"
    ao.name = f"AO_{fast}_{slow}"
    # 设置AO指标的类别为动量
    ao.category = "momentum"

    # 返回AO指标
    return ao


# 更新函数文档字符串
ao.__doc__ = \
"""Awesome Oscillator (AO)

The Awesome Oscillator is an indicator used to measure a security's momentum.
AO is generally used to affirm trends or to anticipate possible reversals.

Sources:
    https://www.tradingview.com/wiki/Awesome_Oscillator_(AO)
    https://www.ifcm.co.uk/ntx-indicators/awesome-oscillator

Calculation:
    Default Inputs:
        fast=5, slow=34
    SMA = Simple Moving Average
    median = (high + low) / 2
    AO = SMA(median, fast) - SMA(median, slow)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    fast (int): The short period. Default: 5
    slow (int): The long period. Default: 34
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""

.\pandas-ta\pandas_ta\momentum\apo.py

# 设置文件编码为 UTF-8
# 导入所需模块
from pandas_ta import Imports
# 导入移动平均函数
from pandas_ta.overlap import ma
# 导入辅助函数
from pandas_ta.utils import get_offset, tal_ma, verify_series


def apo(close, fast=None, slow=None, mamode=None, talib=None, offset=None, **kwargs):
    """Indicator: Absolute Price Oscillator (APO)"""
    # 验证参数有效性,如果未指定则使用默认值
    fast = int(fast) if fast and fast > 0 else 12
    slow = int(slow) if slow and slow > 0 else 26
    # 如果慢周期小于快周期,交换它们
    if slow < fast:
        fast, slow = slow, fast
    # 验证并准备输入序列
    close = verify_series(close, max(fast, slow))
    # 确定移动平均的模式,默认为简单移动平均
    mamode = mamode if isinstance(mamode, str) else "sma"
    # 获取偏移量
    offset = get_offset(offset)
    # 确定是否使用 TA-Lib 库进行计算,默认为 True
    mode_tal = bool(talib) if isinstance(talib, bool) else True

    # 如果输入序列为空,则返回 None
    if close is None: return

    # 计算结果
    if Imports["talib"] and mode_tal:
        # 如果 TA-Lib 可用且需要使用它,则调用 TA-Lib 库计算 APO
        from talib import APO
        # 使用 TA-Lib 计算 APO
        apo = APO(close, fast, slow, tal_ma(mamode))
    else:
        # 否则使用自定义移动平均函数计算 APO
        # 计算快速和慢速移动平均线
        fastma = ma(mamode, close, length=fast)
        slowma = ma(mamode, close, length=slow)
        # 计算 APO
        apo = fastma - slowma

    # 根据偏移量调整结果
    if offset != 0:
        apo = apo.shift(offset)

    # 处理填充值
    if "fillna" in kwargs:
        apo.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        apo.fillna(method=kwargs["fill_method"], inplace=True)

    # 设置指标名称和分类
    apo.name = f"APO_{fast}_{slow}"
    apo.category = "momentum"

    # 返回计算结果
    return apo


# 设置 APO 函数的文档字符串
apo.__doc__ = \
"""Absolute Price Oscillator (APO)

The Absolute Price Oscillator is an indicator used to measure a security's
momentum.  It is simply the difference of two Exponential Moving Averages
(EMA) of two different periods. Note: APO and MACD lines are equivalent.

Sources:
    https://www.tradingtechnologies.com/xtrader-help/x-study/technical-indicator-definitions/absolute-price-oscillator-apo/

Calculation:
    Default Inputs:
        fast=12, slow=26
    SMA = Simple Moving Average
    APO = SMA(close, fast) - SMA(close, slow)

Args:
    close (pd.Series): Series of 'close's
    fast (int): The short period. Default: 12
    slow (int): The long period. Default: 26
    mamode (str): See ```help(ta.ma)```py. Default: 'sma'
    talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib
        version. Default: True
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""

.\pandas-ta\pandas_ta\momentum\bias.py

# -*- coding: utf-8 -*-
# 导入 pandas_ta 库中的 ma 函数
from pandas_ta.overlap import ma
# 导入 pandas_ta 库中的 get_offset 和 verify_series 函数
from pandas_ta.utils import get_offset, verify_series


# 定义 Bias 指标函数,接受 close、length、mamode、offset 等参数
def bias(close, length=None, mamode=None, offset=None, **kwargs):
    """Indicator: Bias (BIAS)"""
    # 验证参数合法性
    # 如果未指定 length 或 length 小于等于 0,则默认为 26
    length = int(length) if length and length > 0 else 26
    # 如果未指定 mamode 或 mamode 不是字符串,则默认为 "sma"
    mamode = mamode if isinstance(mamode, str) else "sma"
    # 验证 close 是否为 Series,并指定长度为 length
    close = verify_series(close, length)
    # 获取 offset
    offset = get_offset(offset)

    # 如果 close 为 None,则返回 None
    if close is None: return

    # 计算结果
    # 计算移动平均线,参数为 mamode、close 和 length
    bma = ma(mamode, close, length=length, **kwargs)
    # 计算 Bias,即 (close / bma) - 1
    bias = (close / bma) - 1

    # 偏移
    # 如果 offset 不为 0,则对 Bias 进行偏移
    if offset != 0:
        bias = bias.shift(offset)

    # 处理填充
    # 如果 kwargs 中包含 "fillna",则使用该值填充 NaN
    if "fillna" in kwargs:
        bias.fillna(kwargs["fillna"], inplace=True)
    # 如果 kwargs 中包含 "fill_method",则使用指定的填充方法
    if "fill_method" in kwargs:
        bias.fillna(method=kwargs["fill_method"], inplace=True)

    # 设置指标名称和类别
    # 指标名称为 "BIAS_移动平均线名称",类别为 "momentum"
    bias.name = f"BIAS_{bma.name}"
    bias.category = "momentum"

    # 返回 Bias
    return bias


# 设置 Bias 函数的文档字符串
bias.__doc__ = \
"""Bias (BIAS)

Rate of change between the source and a moving average.

Sources:
    Few internet resources on definitive definition.
    Request by Github user homily, issue #46

Calculation:
    Default Inputs:
        length=26, MA='sma'

    BIAS = (close - MA(close, length)) / MA(close, length)
         = (close / MA(close, length)) - 1

Args:
    close (pd.Series): Series of 'close's
    length (int): The period. Default: 26
    mamode (str): See ```help(ta.ma)```py. Default: 'sma'
    drift (int): The short period. Default: 1
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""

.\pandas-ta\pandas_ta\momentum\bop.py

# -*- coding: utf-8 -*-
# 从 pandas_ta 库中导入必要的模块
from pandas_ta import Imports
# 从 pandas_ta.utils 模块中导入 get_offset, non_zero_range, verify_series 函数
from pandas_ta.utils import get_offset, non_zero_range, verify_series


def bop(open_, high, low, close, scalar=None, talib=None, offset=None, **kwargs):
    """Indicator: Balance of Power (BOP)"""
    # 验证参数
    open_ = verify_series(open_)
    high = verify_series(high)
    low = verify_series(low)
    close = verify_series(close)
    scalar = float(scalar) if scalar else 1
    offset = get_offset(offset)
    mode_tal = bool(talib) if isinstance(talib, bool) else True

    # 计算结果
    if Imports["talib"] and mode_tal:
        from talib import BOP
        bop = BOP(open_, high, low, close)
    else:
        high_low_range = non_zero_range(high, low)
        close_open_range = non_zero_range(close, open_)
        bop = scalar * close_open_range / high_low_range

    # 偏移
    if offset != 0:
        bop = bop.shift(offset)

    # 处理填充
    if "fillna" in kwargs:
        bop.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        bop.fillna(method=kwargs["fill_method"], inplace=True)

    # 命名和分类
    bop.name = f"BOP"
    bop.category = "momentum"

    return bop


# 设置函数文档字符串
bop.__doc__ = \
"""Balance of Power (BOP)

Balance of Power measure the market strength of buyers against sellers.

Sources:
    http://www.worden.com/TeleChartHelp/Content/Indicators/Balance_of_Power.htm

Calculation:
    BOP = scalar * (close - open) / (high - low)

Args:
    open (pd.Series): Series of 'open's
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    scalar (float): How much to magnify. Default: 1
    talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib
        version. Default: True
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""

.\pandas-ta\pandas_ta\momentum\brar.py

# 设置文件编码为 UTF-8
# 导入 DataFrame 类
from pandas import DataFrame
# 从 pandas_ta.utils 中导入函数 get_drift, get_offset, non_zero_range, verify_series
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series

# 定义函数 brar,计算 BRAR 指标
def brar(open_, high, low, close, length=None, scalar=None, drift=None, offset=None, **kwargs):
    """Indicator: BRAR (BRAR)"""
    # 验证参数
    # 如果 length 存在且大于 0,则将其转换为整数,否则默认为 26
    length = int(length) if length and length > 0 else 26
    # 如果 scalar 存在,则将其转换为浮点数,否则默认为 100
    scalar = float(scalar) if scalar else 100
    # 计算 high 与 open 的非零范围
    high_open_range = non_zero_range(high, open_)
    # 计算 open 与 low 的非零范围
    open_low_range = non_zero_range(open_, low)
    # 验证输入的数据列,并截取长度为 length
    open_ = verify_series(open_, length)
    high = verify_series(high, length)
    low = verify_series(low, length)
    close = verify_series(close, length)
    # 获取漂移值
    drift = get_drift(drift)
    # 获取偏移值
    offset = get_offset(offset)

    # 如果任何输入数据为空,则返回空
    if open_ is None or high is None or low is None or close is None: return

    # 计算结果
    # 计算 high_close_yesterday,即 high 与 close 的差值
    hcy = non_zero_range(high, close.shift(drift))
    # 计算 close_yesterday_low,即 close 与 low 的差值
    cyl = non_zero_range(close.shift(drift), low)
    # 将负值替换为零
    hcy[hcy < 0] = 0
    cyl[cyl < 0] = 0

    # 计算 AR 和 BR 指标
    # AR = scalar * HO 的长度为 length 的滚动和 / OL 的长度为 length 的滚动和
    ar = scalar * high_open_range.rolling(length).sum()
    ar /= open_low_range.rolling(length).sum()
    # BR = scalar * HCY 的长度为 length 的滚动和 / CYL 的长度为 length 的滚动和
    br = scalar * hcy.rolling(length).sum()
    br /= cyl.rolling(length).sum()

    # 偏移
    if offset != 0:
        ar = ar.shift(offset)
        br = ar.shift(offset)

    # 处理填充值
    if "fillna" in kwargs:
        ar.fillna(kwargs["fillna"], inplace=True)
        br.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        ar.fillna(method=kwargs["fill_method"], inplace=True)
        br.fillna(method=kwargs["fill_method"], inplace=True)

    # 设置指标名称和类别
    _props = f"_{length}"
    ar.name = f"AR{_props}"
    br.name = f"BR{_props}"
    ar.category = br.category = "momentum"

    # 准备返回的 DataFrame
    brardf = DataFrame({ar.name: ar, br.name: br})
    brardf.name = f"BRAR{_props}"
    brardf.category = "momentum"

    return brardf

# 设置 brar 函数的文档字符串
brar.__doc__ = \
"""BRAR (BRAR)

BR and AR

Sources:
    No internet resources on definitive definition.
    Request by Github user homily, issue #46

Calculation:
    Default Inputs:
        length=26, scalar=100
    SUM = Sum

    HO_Diff = high - open
    OL_Diff = open - low
    HCY = high - close[-1]
    CYL = close[-1] - low
    HCY[HCY < 0] = 0
    CYL[CYL < 0] = 0
    AR = scalar * SUM(HO, length) / SUM(OL, length)
    BR = scalar * SUM(HCY, length) / SUM(CYL, length)

Args:
    open_ (pd.Series): Series of 'open's
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    length (int): The period. Default: 26
    scalar (float): How much to magnify. Default: 100
    drift (int): The difference period. Default: 1
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.DataFrame: ar, br columns.
"""

.\pandas-ta\pandas_ta\momentum\cci.py

# -*- coding: utf-8 -*-
# 导入必要的库
from pandas_ta import Imports
from pandas_ta.overlap import hlc3, sma
from pandas_ta.statistics.mad import mad
from pandas_ta.utils import get_offset, verify_series

# 定义计算 CCI 指标的函数
def cci(high, low, close, length=None, c=None, talib=None, offset=None, **kwargs):
    """Indicator: Commodity Channel Index (CCI)"""
    # 验证参数
    length = int(length) if length and length > 0 else 14
    c = float(c) if c and c > 0 else 0.015
    high = verify_series(high, length)
    low = verify_series(low, length)
    close = verify_series(close, length)
    offset = get_offset(offset)
    mode_tal = bool(talib) if isinstance(talib, bool) else True

    if high is None or low is None or close is None: return

    # 计算结果
    if Imports["talib"] and mode_tal:
        from talib import CCI
        cci = CCI(high, low, close, length)
    else:
        typical_price = hlc3(high=high, low=low, close=close)
        mean_typical_price = sma(typical_price, length=length)
        mad_typical_price = mad(typical_price, length=length)

        cci = typical_price - mean_typical_price
        cci /= c * mad_typical_price

    # 偏移结果
    if offset != 0:
        cci = cci.shift(offset)

    # 处理填充
    if "fillna" in kwargs:
        cci.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        cci.fillna(method=kwargs["fill_method"], inplace=True)

    # 命名和分类
    cci.name = f"CCI_{length}_{c}"
    cci.category = "momentum"

    return cci

# 设置函数文档
cci.__doc__ = \
"""Commodity Channel Index (CCI)

Commodity Channel Index is a momentum oscillator used to primarily identify
overbought and oversold levels relative to a mean.

Sources:
    https://www.tradingview.com/wiki/Commodity_Channel_Index_(CCI)

Calculation:
    Default Inputs:
        length=14, c=0.015
    SMA = Simple Moving Average
    MAD = Mean Absolute Deviation
    tp = typical_price = hlc3 = (high + low + close) / 3
    mean_tp = SMA(tp, length)
    mad_tp = MAD(tp, length)
    CCI = (tp - mean_tp) / (c * mad_tp)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    length (int): It's period. Default: 14
    c (float): Scaling Constant. Default: 0.015
    talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib
        version. Default: True
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""

.\pandas-ta\pandas_ta\momentum\cfo.py

# -*- coding: utf-8 -*-
# 从 pandas_ta 库中导入 overlap 模块中的 linreg 函数
from pandas_ta.overlap import linreg
# 从 pandas_ta 库中导入 utils 模块中的 get_drift, get_offset, verify_series 函数
from pandas_ta.utils import get_drift, get_offset, verify_series


# 定义 Chande Forcast Oscillator (CFO) 函数
def cfo(close, length=None, scalar=None, drift=None, offset=None, **kwargs):
    """Indicator: Chande Forcast Oscillator (CFO)"""
    # 验证参数
    length = int(length) if length and length > 0 else 9
    scalar = float(scalar) if scalar else 100
    # 验证 close 参数,确保其为有效的 pd.Series 对象,并应用 length 长度验证
    close = verify_series(close, length)
    # 获取 drift 参数的值
    drift = get_drift(drift)
    # 获取 offset 参数的值
    offset = get_offset(offset)

    # 如果 close 为 None,则返回 None
    if close is None: return

    # 计算 Series 的线性回归
    cfo = scalar * (close - linreg(close, length=length, tsf=True))
    cfo /= close

    # 偏移
    if offset != 0:
        cfo = cfo.shift(offset)

    # 处理填充
    if "fillna" in kwargs:
        cfo.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        cfo.fillna(method=kwargs["fill_method"], inplace=True)

    # 命名和分类
    cfo.name = f"CFO_{length}"
    cfo.category = "momentum"

    return cfo


# 设置 CFO 函数的文档字符串
cfo.__doc__ = \
"""Chande Forcast Oscillator (CFO)

The Forecast Oscillator calculates the percentage difference between the actual
price and the Time Series Forecast (the endpoint of a linear regression line).

Sources:
    https://www.fmlabs.com/reference/default.htm?url=ForecastOscillator.htm

Calculation:
    Default Inputs:
        length=9, drift=1, scalar=100
    LINREG = Linear Regression

    CFO = scalar * (close - LINERREG(length, tdf=True)) / close

Args:
    close (pd.Series): Series of 'close's
    length (int): The period. Default: 9
    scalar (float): How much to magnify. Default: 100
    drift (int): The short period. Default: 1
    offset (int): How many periods to offset the result. Default: 0

Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method

Returns:
    pd.Series: New feature generated.
"""
posted @ 2024-04-15 13:37  绝不原创的飞龙  阅读(56)  评论(0编辑  收藏  举报