PandasTA 原始碼解析(十七)

绝不原创的飞龙發表於2024-04-15

.\pandas-ta\pandas_ta\volume\adosc.py

# -*- coding: utf-8 -*-
# 匯入 ad 模組
from .ad import ad
# 從 pandas_ta 庫中匯入 Imports 模組
from pandas_ta import Imports
# 從 pandas_ta.overlap 模組中匯入 ema 函式
from pandas_ta.overlap import ema
# 從 pandas_ta.utils 模組中匯入 get_offset 和 verify_series 函式
from pandas_ta.utils import get_offset, verify_series

# 定義 adosc 函式,用於計算累積/分佈振盪器指標
def adosc(high, low, close, volume, open_=None, fast=None, slow=None, talib=None, offset=None, **kwargs):
    """Indicator: Accumulation/Distribution Oscillator"""
    # 驗證引數有效性
    # 如果 fast 引數存在且大於 0,則將其轉換為整數型別,否則設為預設值 3
    fast = int(fast) if fast and fast > 0 else 3
    # 如果 slow 引數存在且大於 0,則將其轉換為整數型別,否則設為預設值 10
    slow = int(slow) if slow and slow > 0 else 10
    # 計算 _length,即 fast 和 slow 中的較大值
    _length = max(fast, slow)
    # 驗證 high、low、close、volume 系列資料的長度,使其與 _length 相同
    high = verify_series(high, _length)
    low = verify_series(low, _length)
    close = verify_series(close, _length)
    volume = verify_series(volume, _length)
    # 獲取偏移量,根據 offset 引數
    offset = get_offset(offset)
    # 如果 kwargs 中存在 "length" 鍵,則將其移除
    if "length" in kwargs: kwargs.pop("length")
    # 如果 talib 引數為布林型別且為真,則將 mode_tal 設為 True,否則設為 False
    mode_tal = bool(talib) if isinstance(talib, bool) else True

    # 如果 high、low、close、volume 中存在空值,則返回空值
    if high is None or low is None or close is None or volume is None: return

    # 計算結果
    if Imports["talib"] and mode_tal:
        # 如果匯入了 TA Lib 並且 mode_tal 為 True,則使用 TA Lib 計算 adosc
        from talib import ADOSC
        adosc = ADOSC(high, low, close, volume, fast, slow)
    else:
        # 否則,使用自定義的 ad 函式計算 ad_,然後分別計算其快速和慢速移動平均線
        ad_ = ad(high=high, low=low, close=close, volume=volume, open_=open_)
        fast_ad = ema(close=ad_, length=fast, **kwargs)
        slow_ad = ema(close=ad_, length=slow, **kwargs)
        adosc = fast_ad - slow_ad

    # 根據偏移量對結果進行偏移
    if offset != 0:
        adosc = adosc.shift(offset)

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

    # 設定指標名稱和類別
    adosc.name = f"ADOSC_{fast}_{slow}"
    adosc.category = "volume"

    # 返回計算結果
    return adosc


# 設定 adosc 函式的文件字串
adosc.__doc__ = \
"""Accumulation/Distribution Oscillator or Chaikin Oscillator

Accumulation/Distribution Oscillator indicator utilizes
Accumulation/Distribution and treats it similarily to MACD
or APO.

Sources:
    https://www.investopedia.com/articles/active-trading/031914/understanding-chaikin-oscillator.asp

Calculation:
    Default Inputs:
        fast=12, slow=26
    AD = Accum/Dist
    ad = AD(high, low, close, open)
    fast_ad = EMA(ad, fast)
    slow_ad = EMA(ad, slow)
    ADOSC = fast_ad - slow_ad

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    open (pd.Series): Series of 'open's
    volume (pd.Series): Series of 'volume's
    fast (int): The short period. Default: 12
    slow (int): The long period. Default: 26
    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\volume\aobv.py

# -*- coding: utf-8 -*-
# 從 pandas 庫中匯入 DataFrame 類
from pandas import DataFrame
# 從當前目錄下的 obv 模組中匯入 obv 函式
from .obv import obv
# 從 pandas_ta.overlap 模組中匯入 ma 函式
from pandas_ta.overlap import ma
# 從 pandas_ta.trend 模組中匯入 long_run 和 short_run 函式
from pandas_ta.trend import long_run, short_run
# 從 pandas_ta.utils 模組中匯入 get_offset 和 verify_series 函式
from pandas_ta.utils import get_offset, verify_series

# 定義名為 aobv 的函式,計算 Archer On Balance Volume (AOBV) 指標
def aobv(close, volume, fast=None, slow=None, max_lookback=None, min_lookback=None, mamode=None, offset=None, **kwargs):
    """Indicator: Archer On Balance Volume (AOBV)"""
    # 驗證引數
    # 如果 fast 存在且大於 0,則將其轉換為整數,否則設為預設值 4
    fast = int(fast) if fast and fast > 0 else 4
    # 如果 slow 存在且大於 0,則將其轉換為整數,否則設為預設值 12
    slow = int(slow) if slow and slow > 0 else 12
    # 如果 max_lookback 存在且大於 0,則將其轉換為整數,否則設為預設值 2
    max_lookback = int(max_lookback) if max_lookback and max_lookback > 0 else 2
    # 如果 min_lookback 存在且大於 0,則將其轉換為整數,否則設為預設值 2
    min_lookback = int(min_lookback) if min_lookback and min_lookback > 0 else 2
    # 如果 slow 小於 fast,則交換它們的值
    if slow < fast:
        fast, slow = slow, fast
    # 如果 mamode 不是字串型別,則設為預設值 "ema"
    mamode = mamode if isinstance(mamode, str) else "ema"
    # 計算需要處理的資料長度
    _length = max(fast, slow, max_lookback, min_lookback)
    # 驗證 close 和 volume 是否為有效的資料序列,長度為 _length
    close = verify_series(close, _length)
    volume = verify_series(volume, _length)
    # 獲取偏移量
    offset = get_offset(offset)
    # 如果 kwargs 中存在 "length" 鍵,則將其移除
    if "length" in kwargs: kwargs.pop("length")
    # 從 kwargs 中獲取 "run_length" 鍵的值,如果不存在則設為預設值 2
    run_length = kwargs.pop("run_length", 2)

    # 如果 close 或 volume 為 None,則返回空
    if close is None or volume is None: return

    # 計算結果
    # 計算 On Balance Volume(OBV)
    obv_ = obv(close=close, volume=volume, **kwargs)
    # 計算 OBV 的快速移動平均線
    maf = ma(mamode, obv_, length=fast, **kwargs)
    # 計算 OBV 的慢速移動平均線
    mas = ma(mamode, obv_, length=slow, **kwargs)

    # 當快速和慢速移動平均線長度為指定長度時
    obv_long = long_run(maf, mas, length=run_length)
    obv_short = short_run(maf, mas, length=run_length)

    # 考慮偏移量
    if offset != 0:
        obv_ = obv_.shift(offset)
        maf = maf.shift(offset)
        mas = mas.shift(offset)
        obv_long = obv_long.shift(offset)
        obv_short = obv_short.shift(offset)

    # 處理填充值
    if "fillna" in kwargs:
        obv_.fillna(kwargs["fillna"], inplace=True)
        maf.fillna(kwargs["fillna"], inplace=True)
        mas.fillna(kwargs["fillna"], inplace=True)
        obv_long.fillna(kwargs["fillna"], inplace=True)
        obv_short.fillna(kwargs["fillna"], inplace=True)
    if "fill_method" in kwargs:
        obv_.fillna(method=kwargs["fill_method"], inplace=True)
        maf.fillna(method=kwargs["fill_method"], inplace=True)
        mas.fillna(method=kwargs["fill_method"], inplace=True)
        obv_long.fillna(method=kwargs["fill_method"], inplace=True)
        obv_short.fillna(method=kwargs["fill_method"], inplace=True)

    # 準備返回的 DataFrame
    _mode = mamode.lower()[0] if len(mamode) else ""
    data = {
        obv_.name: obv_,
        f"OBV_min_{min_lookback}": obv_.rolling(min_lookback).min(),
        f"OBV_max_{max_lookback}": obv_.rolling(max_lookback).max(),
        f"OBV{_mode}_{fast}": maf,
        f"OBV{_mode}_{slow}": mas,
        f"AOBV_LR_{run_length}": obv_long,
        f"AOBV_SR_{run_length}": obv_short,
    }
    # 建立 DataFrame
    aobvdf = DataFrame(data)

    # 給 DataFrame 命名並分類
    aobvdf.name = f"AOBV{_mode}_{fast}_{slow}_{min_lookback}_{max_lookback}_{run_length}"
    aobvdf.category = "volume"

    return aobvdf

.\pandas-ta\pandas_ta\volume\cmf.py

# -*- coding: utf-8 -*-

# 從 pandas_ta.utils 匯入一些必要的函式
from pandas_ta.utils import get_offset, non_zero_range, verify_series

# 定義 CMF 函式,計算 Chaikin Money Flow 指標
def cmf(high, low, close, volume, open_=None, length=None, offset=None, **kwargs):
    """Indicator: Chaikin Money Flow (CMF)"""
    # 驗證引數
    # 確保 length 是整數且大於 0,若未指定則預設為 20
    length = int(length) if length and length > 0 else 20
    # 最小期數為 length 或者 kwargs 中 min_periods 的值,若未指定則為 length
    min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
    # 取 length 和 min_periods 中的較大值作為真正的 length
    _length = max(length, min_periods)
    # 確保傳入的資料是合法的 Series 型別,長度為 _length
    high = verify_series(high, _length)
    low = verify_series(low, _length)
    close = verify_series(close, _length)
    volume = verify_series(volume, _length)
    # 獲取偏移量
    offset = get_offset(offset)

    # 若傳入的資料有缺失,則返回 None
    if high is None or low is None or close is None or volume is None: return

    # 計算結果
    if open_ is not None:
        # 若存在開盤價資料,則使用開盤價計算 AD
        open_ = verify_series(open_)
        ad = non_zero_range(close, open_)  # 使用開盤價計算 AD
    else:
        # 若不存在開盤價資料,則使用高、低、收盤價計算 AD
        ad = 2 * close - (high + low)  # 使用高、低、收盤價計算 AD

    # 計算 CMF
    ad *= volume / non_zero_range(high, low)
    cmf = ad.rolling(length, min_periods=min_periods).sum()
    cmf /= volume.rolling(length, min_periods=min_periods).sum()

    # 偏移結果
    if offset != 0:
        cmf = cmf.shift(offset)

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

    # 命名並分類
    cmf.name = f"CMF_{length}"
    cmf.category = "volume"

    # 返回結果
    return cmf

# 設定 CMF 函式的文件字串
cmf.__doc__ = \
"""Chaikin Money Flow (CMF)

Chailin Money Flow measures the amount of money flow volume over a specific
period in conjunction with Accumulation/Distribution.

Sources:
    https://www.tradingview.com/wiki/Chaikin_Money_Flow_(CMF)
    https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_money_flow_cmf

Calculation:
    Default Inputs:
        length=20
    if 'open':
        ad = close - open
    else:
        ad = 2 * close - high - low

    hl_range = high - low
    ad = ad * volume / hl_range
    CMF = SUM(ad, length) / SUM(volume, length)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    open_ (pd.Series): Series of 'open's. Default: None
    length (int): The short period. Default: 20
    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\volume\efi.py

# -*- coding: utf-8 -*-
# 從 pandas_ta.overlap 模組匯入 ma 函式
from pandas_ta.overlap import ma
# 從 pandas_ta.utils 模組匯入 get_drift、get_offset、verify_series 函式
from pandas_ta.utils import get_drift, get_offset, verify_series


# 定義 EFI 函式,計算 Elder's Force Index (EFI)
def efi(close, volume, length=None, mamode=None, drift=None, offset=None, **kwargs):
    """Indicator: Elder's Force Index (EFI)"""
    # 驗證引數
    # 將 length 轉換為整數,如果未提供或小於等於 0,則設為預設值 13
    length = int(length) if length and length > 0 else 13
    # 如果未提供 mamode 或不是字串,則設為預設值 'ema'
    mamode = mamode if isinstance(mamode, str) else "ema"
    # 驗證 close 和 volume 是否為有效序列,長度為 length
    close = verify_series(close, length)
    volume = verify_series(volume, length)
    # 獲取漂移和偏移量
    drift = get_drift(drift)
    offset = get_offset(offset)

    # 如果 close 或 volume 為空,則返回空
    if close is None or volume is None: return

    # 計算結果
    # 計算價格和成交量的差值,再乘以漂移量
    pv_diff = close.diff(drift) * volume
    # 計算 EFI,使用指定的移動平均模式和長度
    efi = ma(mamode, pv_diff, length=length)

    # 偏移
    # 如果偏移量不為零,則對 EFI 進行偏移
    if offset != 0:
        efi = efi.shift(offset)

    # 處理填充
    # 如果 kwargs 中包含 'fillna',則用指定值填充 EFI 的缺失值
    if "fillna" in kwargs:
        efi.fillna(kwargs["fillna"], inplace=True)
    # 如果 kwargs 中包含 'fill_method',則使用指定的填充方法填充 EFI 的缺失值
    if "fill_method" in kwargs:
        efi.fillna(method=kwargs["fill_method"], inplace=True)

    # 設定名稱和分類
    # 將 EFI 的名稱設為字串模板 "EFI_{length}"
    efi.name = f"EFI_{length}"
    # 將 EFI 的分類設為 "volume"
    efi.category = "volume"

    # 返回 EFI
    return efi


# 設定 EFI 函式的文件字串
efi.__doc__ = \
"""Elder's Force Index (EFI)

Elder's Force Index measures the power behind a price movement using price
and volume as well as potential reversals and price corrections.

Sources:
    https://www.tradingview.com/wiki/Elder%27s_Force_Index_(EFI)
    https://www.motivewave.com/studies/elders_force_index.htm

Calculation:
    Default Inputs:
        length=20, drift=1, mamode=None
    EMA = Exponential Moving Average
    SMA = Simple Moving Average

    pv_diff = close.diff(drift) * volume
    if mamode == 'sma':
        EFI = SMA(pv_diff, length)
    else:
        EFI = EMA(pv_diff, length)

Args:
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    length (int): The short period. Default: 13
    drift (int): The diff period. Default: 1
    mamode (str): See ```help(ta.ma)```py. Default: 'ema'
    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\volume\eom.py

# -*- coding: utf-8 -*-
# 匯入需要的庫
from pandas_ta.overlap import hl2, sma
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series

# 定義 Ease of Movement (EOM) 函式
def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=None, **kwargs):
    """Indicator: Ease of Movement (EOM)"""
    # 驗證引數
    length = int(length) if length and length > 0 else 14
    divisor = divisor if divisor and divisor > 0 else 100000000
    high = verify_series(high, length)
    low = verify_series(low, length)
    close = verify_series(close, length)
    volume = verify_series(volume, length)
    drift = get_drift(drift)
    offset = get_offset(offset)

    # 如果任何一個輸入序列為 None,則返回 None
    if high is None or low is None or close is None or volume is None: return

    # 計算結果
    # 計算高低價範圍
    high_low_range = non_zero_range(high, low)
    # 計算距離
    distance  = hl2(high=high, low=low)
    distance -= hl2(high=high.shift(drift), low=low.shift(drift))
    # 計算箱比率
    box_ratio = volume / divisor
    box_ratio /= high_low_range
    # 計算 EOM
    eom = distance / box_ratio
    # 對 EOM 序列進行簡單移動平均
    eom = sma(eom, length=length)

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

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

    # 設定名稱和類別
    eom.name = f"EOM_{length}_{divisor}"
    eom.category = "volume"

    return eom


# 設定 EOM 函式的文件字串
eom.__doc__ = \
"""Ease of Movement (EOM)

Ease of Movement is a volume based oscillator that is designed to measure the
relationship between price and volume flucuating across a zero line.

Sources:
    https://www.tradingview.com/wiki/Ease_of_Movement_(EOM)
    https://www.motivewave.com/studies/ease_of_movement.htm
    https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ease_of_movement_emv

Calculation:
    Default Inputs:
        length=14, divisor=100000000, drift=1
    SMA = Simple Moving Average
    hl_range = high - low
    distance = 0.5 * (high - high.shift(drift) + low - low.shift(drift))
    box_ratio = (volume / divisor) / hl_range
    eom = distance / box_ratio
    EOM = SMA(eom, length)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    length (int): The short period. Default: 14
    drift (int): The diff 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\volume\kvo.py

# 設定檔案編碼為 UTF-8
# 匯入 DataFrame 類
from pandas import DataFrame
# 從 pandas_ta.overlap 模組匯入 hlc3 和 ma 函式
from pandas_ta.overlap import hlc3, ma
# 從 pandas_ta.utils 模組匯入 get_drift, get_offset, signed_series, verify_series 函式
from pandas_ta.utils import get_drift, get_offset, signed_series, verify_series


# 定義 Klinger Volume Oscillator (KVO) 指標函式
def kvo(high, low, close, volume, fast=None, slow=None, signal=None, mamode=None, drift=None, offset=None, **kwargs):
    """Indicator: Klinger Volume Oscillator (KVO)"""
    # 驗證引數
    # 如果 fast 存在且大於 0,則將其轉換為整數;否則預設為 34
    fast = int(fast) if fast and fast > 0 else 34
    # 如果 slow 存在且大於 0,則將其轉換為整數;否則預設為 55
    slow = int(slow) if slow and slow > 0 else 55
    # 如果 signal 存在且大於 0,則將其轉換為整數;否則預設為 13
    signal = int(signal) if signal and signal > 0 else 13
    # 如果 mamode 存在且是字串型別,則將其轉換為小寫;否則預設為 'ema'
    mamode = mamode.lower() if mamode and isinstance(mamode, str) else "ema"
    # 計算引數中最大的長度
    _length = max(fast, slow, signal)
    # 驗證輸入序列的長度
    high = verify_series(high, _length)
    low = verify_series(low, _length)
    close = verify_series(close, _length)
    volume = verify_series(volume, _length)
    # 獲取漂移值
    drift = get_drift(drift)
    # 獲取偏移值
    offset = get_offset(offset)

    # 如果輸入的 high、low、close、volume 有任何一個為 None,則返回 None
    if high is None or low is None or close is None or volume is None: return

    # 計算結果
    # 計算帶符號的成交量
    signed_volume = volume * signed_series(hlc3(high, low, close), 1)
    # 從第一個有效索引開始取值
    sv = signed_volume.loc[signed_volume.first_valid_index():,]
    # 計算 KVO 指標
    kvo = ma(mamode, sv, length=fast) - ma(mamode, sv, length=slow)
    # 計算 KVO 的訊號線
    kvo_signal = ma(mamode, kvo.loc[kvo.first_valid_index():,], length=signal)

    # 調整偏移
    if offset != 0:
        kvo = kvo.shift(offset)
        kvo_signal = kvo_signal.shift(offset)

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

    # 設定指標的名稱和分類
    _props = f"_{fast}_{slow}_{signal}"
    kvo.name = f"KVO{_props}"
    kvo_signal.name = f"KVOs{_props}"
    kvo.category = kvo_signal.category = "volume"

    # 準備返回的 DataFrame
    data = {kvo.name: kvo, kvo_signal.name: kvo_signal}
    df = DataFrame(data)
    df.name = f"KVO{_props}"
    df.category = kvo.category

    return df


# 設定函式文件字串
kvo.__doc__ = \
"""Klinger Volume Oscillator (KVO)

This indicator was developed by Stephen J. Klinger. It is designed to predict
price reversals in a market by comparing volume to price.

Sources:
    https://www.investopedia.com/terms/k/klingeroscillator.asp
    https://www.daytrading.com/klinger-volume-oscillator

Calculation:
    Default Inputs:
        fast=34, slow=55, signal=13, drift=1
    EMA = Exponential Moving Average

    SV = volume * signed_series(HLC3, 1)
    KVO = EMA(SV, fast) - EMA(SV, slow)
    Signal = EMA(KVO, signal)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    fast (int): The fast period. Default: 34
    long (int): The long period. Default: 55
    length_sig (int): The signal period. Default: 13
    mamode (str): See ```help(ta.ma)```py. Default: 'ema'

"""
    offset (int): How many periods to offset the result. Default: 0

# 偏移量(int):結果要偏移的週期數。預設值為0。
# 引數說明部分,描述函式的引數和返回值
Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)
    fill_method (value, optional): Type of fill method
Returns:
    pd.DataFrame: KVO and Signal columns.

.\pandas-ta\pandas_ta\volume\mfi.py

# -*- coding: utf-8 -*-

# 從 pandas 庫中匯入 DataFrame 類
from pandas import DataFrame
# 從 pandas_ta 庫中匯入 Imports 模組
from pandas_ta import Imports
# 從 pandas_ta.overlap 模組中匯入 hlc3 函式
from pandas_ta.overlap import hlc3
# 從 pandas_ta.utils 模組中匯入 get_drift, get_offset, verify_series 函式
from pandas_ta.utils import get_drift, get_offset, verify_series

# 定義 Money Flow Index (MFI) 函式
def mfi(high, low, close, volume, length=None, talib=None, drift=None, offset=None, **kwargs):
    """Indicator: Money Flow Index (MFI)"""

    # 驗證引數
    length = int(length) if length and length > 0 else 14
    high = verify_series(high, length)
    low = verify_series(low, length)
    close = verify_series(close, length)
    volume = verify_series(volume, length)
    drift = get_drift(drift)
    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 or volume is None: return

    # 計算結果
    if Imports["talib"] and mode_tal:
        # 如果 TA Lib 已安裝且 talib 引數為 True,則使用 TA Lib 實現的 MFI 函式計算結果
        from talib import MFI
        mfi = MFI(high, low, close, volume, length)
    else:
        # 否則,使用自定義方法計算 MFI 指標

        # 計算典型價格和原始資金流
        typical_price = hlc3(high=high, low=low, close=close)
        raw_money_flow = typical_price * volume

        # 建立包含不同情況的資料框
        tdf = DataFrame({"diff": 0, "rmf": raw_money_flow, "+mf": 0, "-mf": 0})

        # 根據典型價格的變化情況更新資料框中的不同列
        tdf.loc[(typical_price.diff(drift) > 0), "diff"] = 1
        tdf.loc[tdf["diff"] == 1, "+mf"] = raw_money_flow
        tdf.loc[(typical_price.diff(drift) < 0), "diff"] = -1
        tdf.loc[tdf["diff"] == -1, "-mf"] = raw_money_flow

        # 計算正和負資金流的滾動和
        psum = tdf["+mf"].rolling(length).sum()
        nsum = tdf["-mf"].rolling(length).sum()

        # 計算資金流比率和 MFI 指標
        tdf["mr"] = psum / nsum
        mfi = 100 * psum / (psum + nsum)
        tdf["mfi"] = mfi

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

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

    # 設定名稱和分類
    mfi.name = f"MFI_{length}"
    mfi.category = "volume"

    return mfi

# 設定 MFI 函式的文件字串
mfi.__doc__ = \
"""Money Flow Index (MFI)

Money Flow Index is an oscillator indicator that is used to measure buying and
selling pressure by utilizing both price and volume.

Sources:
    https://www.tradingview.com/wiki/Money_Flow_(MFI)

Calculation:
    Default Inputs:
        length=14, drift=1
    tp = typical_price = hlc3 = (high + low + close) / 3
    rmf = raw_money_flow = tp * volume

    pmf = pos_money_flow = SUM(rmf, length) if tp.diff(drift) > 0 else 0
    nmf = neg_money_flow = SUM(rmf, length) if tp.diff(drift) < 0 else 0

    MFR = money_flow_ratio = pmf / nmf
    MFI = money_flow_index = 100 * pmf / (pmf + nmf)

Args:
    high (pd.Series): Series of 'high's
    low (pd.Series): Series of 'low's
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    length (int): The sum period. Default: 14
    talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib
        version. Default: True
    drift (int): The difference period. Default: 1
"""
    offset (int): How many periods to offset the result. Default: 0

# 定義函式引數 offset,表示結果偏移的週期數,預設值為 0。
# 定義函式引數
Kwargs:
    fillna (value, optional): pd.DataFrame.fillna(value)  # 填充缺失值的方法和值
    fill_method (value, optional): Type of fill method  # 填充方法的型別

# 返回一個新的 pandas Series 物件,表示生成的新特徵
Returns:
    pd.Series: New feature generated.  # 返回新生成的特徵,型別為 pandas 的 Series 物件

.\pandas-ta\pandas_ta\volume\nvi.py

# -*- coding: utf-8 -*-
# 從 pandas_ta 庫中匯入動量指標 roc
from pandas_ta.momentum import roc
# 從 pandas_ta 庫中匯入工具函式 get_offset, signed_series, verify_series
from pandas_ta.utils import get_offset, signed_series, verify_series


# 定義函式 nvi,計算負量指數(NVI)
def nvi(close, volume, length=None, initial=None, offset=None, **kwargs):
    """Indicator: Negative Volume Index (NVI)"""
    # 驗證引數
   length = int(length) if length and length > 0 else 1
    initial = int(initial) if initial and initial > 0 else 1000
    close = verify_series(close, length)
    volume = verify_series(volume, length)
    offset = get_offset(offset)

    if close is None or volume is None: return

    # 計算結果
    roc_ = roc(close=close, length=length)
    signed_volume = signed_series(volume, 1)
    nvi = signed_volume[signed_volume < 0].abs() * roc_
    nvi.fillna(0, inplace=True)
    nvi.iloc[0] = initial
    nvi = nvi.cumsum()

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

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

    # 命名和分類
    nvi.name = f"NVI_{length}"
    nvi.category = "volume"

    return nvi


# 設定 nvi 函式的文件字串
nvi.__doc__ = \
"""Negative Volume Index (NVI)

The Negative Volume Index is a cumulative indicator that uses volume change in
an attempt to identify where smart money is active.

Sources:
    https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:negative_volume_inde
    https://www.motivewave.com/studies/negative_volume_index.htm

Calculation:
    Default Inputs:
        length=1, initial=1000
    ROC = Rate of Change

    roc = ROC(close, length)
    signed_volume = signed_series(volume, initial=1)
    nvi = signed_volume[signed_volume < 0].abs() * roc_
    nvi.fillna(0, inplace=True)
    nvi.iloc[0]= initial
    nvi = nvi.cumsum()

Args:
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    length (int): The short period. Default: 13
    initial (int): The short period. Default: 1000
    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\volume\obv.py

# -*- coding: utf-8 -*-
# 匯入所需的庫
from pandas_ta import Imports
from pandas_ta.utils import get_offset, signed_series, verify_series

# 定義 On Balance Volume (OBV) 指標函式
def obv(close, volume, talib=None, offset=None, **kwargs):
    """Indicator: On Balance Volume (OBV)"""
    # 驗證引數
    # 確保 'close' 和 'volume' 是有效的 Series 物件
    close = verify_series(close)
    volume = verify_series(volume)
    # 獲取偏移量
    offset = get_offset(offset)
    # 確定是否使用 TA Lib
    mode_tal = bool(talib) if isinstance(talib, bool) else True

    # 計算結果
    if Imports["talib"] and mode_tal:
        # 如果 TA Lib 可用且 talib 引數為 True,則使用 TA Lib 中的 OBV 函式
        from talib import OBV
        obv = OBV(close, volume)
    else:
        # 否則,計算 signed_volume 並累積求和得到 OBV
        signed_volume = signed_series(close, initial=1) * volume
        obv = signed_volume.cumsum()

    # 偏移結果
    if offset != 0:
        obv = obv.shift(offset)

    # 處理填充
    # 如果 'fillna' 引數存在,則使用指定值填充缺失值
    if "fillna" in kwargs:
        obv.fillna(kwargs["fillna"], inplace=True)
    # 如果 'fill_method' 引數存在,則使用指定的填充方法填充缺失值
    if "fill_method" in kwargs:
        obv.fillna(method=kwargs["fill_method"], inplace=True)

    # 為結果新增名稱和分類資訊
    obv.name = f"OBV"
    obv.category = "volume"

    return obv


# 設定函式文件字串
obv.__doc__ = \
"""On Balance Volume (OBV)

On Balance Volume is a cumulative indicator to measure buying and selling
pressure.

Sources:
    https://www.tradingview.com/wiki/On_Balance_Volume_(OBV)
    https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/on-balance-volume-obv/
    https://www.motivewave.com/studies/on_balance_volume.htm

Calculation:
    signed_volume = signed_series(close, initial=1) * volume
    obv = signed_volume.cumsum()

Args:
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    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\volume\pvi.py

# -*- coding: utf-8 -*-
# 從 pandas_ta 庫中匯入動量模組中的 roc 函式
from pandas_ta.momentum import roc
# 從 pandas_ta 庫中匯入工具模組中的 get_offset, signed_series, verify_series 函式
from pandas_ta.utils import get_offset, signed_series, verify_series


# 定義 Positive Volume Index (PVI) 指標函式
def pvi(close, volume, length=None, initial=None, offset=None, **kwargs):
    """Indicator: Positive Volume Index (PVI)"""
    # 驗證引數
    length = int(length) if length and length > 0 else 1
    # initial 預設為 1000
    initial = int(initial) if initial and initial > 0 else 1000
    # 驗證 close 和 volume 引數是否為 Series 型別,並且長度符合要求
    close = verify_series(close, length)
    volume = verify_series(volume, length)
    # 獲取偏移量
    offset = get_offset(offset)

    # 如果 close 或 volume 為 None,則返回空
    if close is None or volume is None: return

    # 計算結果
    # 將 volume 序列轉換為帶符號的序列
    signed_volume = signed_series(volume, 1)
    # 計算 PVI
    pvi = roc(close=close, length=length) * signed_volume[signed_volume > 0].abs()
    # 將 NaN 值填充為 0
    pvi.fillna(0, inplace=True)
    # 將第一個值設定為 initial
    pvi.iloc[0] = initial
    # 對 PVI 序列進行累積求和
    pvi = pvi.cumsum()

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

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

    # 設定指標名稱和分類
    pvi.name = f"PVI_{length}"
    pvi.category = "volume"

    return pvi


# 設定 PVI 函式的文件字串
pvi.__doc__ = \
"""Positive Volume Index (PVI)

The Positive Volume Index is a cumulative indicator that uses volume change in
an attempt to identify where smart money is active.
Used in conjunction with NVI.

Sources:
    https://www.investopedia.com/terms/p/pvi.asp

Calculation:
    Default Inputs:
        length=1, initial=1000
    ROC = Rate of Change

    roc = ROC(close, length)
    signed_volume = signed_series(volume, initial=1)
    pvi = signed_volume[signed_volume > 0].abs() * roc_
    pvi.fillna(0, inplace=True)
    pvi.iloc[0]= initial
    pvi = pvi.cumsum()

Args:
    close (pd.Series): Series of 'close's
    volume (pd.Series): Series of 'volume's
    length (int): The short period. Default: 13
    initial (int): The short period. Default: 1000
    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.
"""

相關文章