Skip to content

zerohertzLib.quant

Quant

FinanceDataReaderKoreaInvestment 를 통해 수집한 data를 이용해 매수, 매도 signal을 포착하고 test하는 quant function 및 class들

Important

signals["signals"] 부호의 의미

  • +1: 매수
  • -1: 매도

Modules:

Name Description
backtest
koreainvestment
methods
quant
util

Classes:

Name Description
Balance

한국투자증권의 국내 계좌 정보 조회 class

Experiments

Full factorial design 기반의 backtest 수행 class

Quant

한 가지 종목에 대해 full factorial design 기반의 backtest를 수행하고 최적의 전략을 융합하는 class

QuantBot

입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Slack으로 message와 graph를 전송하는 class

QuantBotFDR

FinanceDataReader module 기반으로 입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Bot을 통해 message와 graph를 전송하는 class

QuantBotKI

한국투자증권 API를 기반으로 입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Bot을 통해 message와 graph를 전송하는 class

Functions:

Name Description
bollinger_bands

Bollinger band 기반 매수 및 매도 signal을 생성하는 function

experiments

Full factorial design 기반의 backtest 수행 function

macd

MACD 기반 매수 및 매도 signal을 생성하는 function

momentum

Momentum 기반 매수 및 매도 signal을 생성하는 function

moving_average

단기 및 장기 이동 평균 기반 매수 및 매도 signal을 생성하는 function

rsi

RSI 기반 매수 및 매도 signal을 생성하는 function

__all__ module-attribute

__all__ = ['Balance', 'Experiments', 'Quant', 'QuantBot', 'QuantBotFDR', 'QuantBotKI', 'backtest', 'bollinger_bands', 'experiments', 'macd', 'momentum', 'moving_average', 'rsi']

Balance

Balance(account_no: str, path: str = './', kor: bool = True)

Bases: KoreaInvestment

한국투자증권의 국내 계좌 정보 조회 class

Parameters:

Name Type Description Default
account_no str

API 호출 시 사용할 계좌 번호

required
path str

secret.key 혹은 token.dat 이 포함된 경로

'./'
kor bool

국내 여부

True

Attributes:

Name Type Description
balance

현재 보유 주식과 계좌의 금액 정보

Examples:

kor=True:

>>> balance = zz.quant.Balance("00000000-00")
>>> "LG전자" in balance
True
>>> "삼성전자" in balance
False
>>> len(balance)
1
>>> balance[0]
['066570', 102200.0, 100200, 1, -1.95, -2000]
>>> balance()
000
kor=False:
>>> balance = zz.quant.Balance("00000000-00", kor=False)
>>> "아마존닷컴" in balance
True
>>> "삼성전자" in balance
False
>>> len(balance)
1
>>> balance[0]
['META', 488.74, 510.92, 1, 4.53, 22.18]
>>> balance()
000.000

Methods:

Name Description
__call__

현재 보유 금액을 반환

__contains__

보유 종목 여부를 확인

__getitem__

Index에 따른 주식 정보를 반환

__len__

보유 주식 종류의 개수를 반환

barv

현재 보유 종목의 이익과 손실을 bar chart로 시각화

bought_symbols

보유 주식의 종목 code return

items

보유 주식의 반복문 사용을 위한 method

merge

현재 계좌와 입력 계좌의 정보를 병합하는 function

pie

현재 보유 종목을 pie chart로 시각화

table

현재 계좌의 상태를 image로 저장

Source code in zerohertzLib/quant/koreainvestment.py
def __init__(self, account_no: str, path: str = "./", kor: bool = True) -> None:
    super().__init__(account_no, path)
    self.balance = {"stock": defaultdict(list)}
    self.kor = kor
    response = self.get_balance(kor)
    if self.kor:
        for stock in response["output1"]:
            if int(stock["hldg_qty"]) > 0:  # 보유수량
                self.balance["stock"][stock["prdt_name"]] = [
                    stock["pdno"],  # 종목번호
                    float(
                        stock["pchs_avg_pric"]
                    ),  # 매입평균가격 (매입금액 / 보유수량)
                    int(stock["prpr"]),  # 현재가
                    int(stock["hldg_qty"]),  # 보유수량
                    float(stock["evlu_pfls_rt"]),  # 평가손익율
                    int(
                        stock["evlu_pfls_amt"]
                    ),  # 평가손익금액 (평가금액 - 매입금액)
                ]
        self.balance["cash"] = int(response["output2"][0]["nass_amt"])  # 순자산금액
    else:
        for stock in response["output1"]:
            if int(float(stock["ccld_qty_smtl1"])) > 0:  # 체결수량합계
                self.balance["stock"][stock["prdt_name"]] = [
                    stock["pdno"],  # 종목번호
                    float(stock["avg_unpr3"]),  # 평균단가
                    float(stock["ovrs_now_pric1"]),  # 해외현재가격
                    int(float(stock["ccld_qty_smtl1"])),  # 해외잔고수량
                    float(stock["evlu_pfls_rt1"]),  # 평가손익율
                    float(stock["evlu_pfls_amt2"]),  # 평가손익금액
                ]
        self.balance["cash"] = (
            float(response["output3"]["evlu_amt_smtl_amt"])  # 평가금액합계금액
            + float(response["output3"]["frcr_use_psbl_amt"])  # 외화사용가능금액
            + float(response["output3"]["ustl_sll_amt_smtl"])  # 미결제매도금액합계
            - float(response["output3"]["ustl_buy_amt_smtl"])  # 미결제매수금액합계
        ) / self._exchange()
    self.balance["stock"] = dict(
        sorted(
            self.balance["stock"].items(),
            key=lambda item: item[1][1] * item[1][3],
            reverse=True,
        )
    )
    self.symbols = list(self.balance["stock"].keys())

balance instance-attribute

balance = {'stock': defaultdict(list)}

kor instance-attribute

kor = kor

symbols instance-attribute

symbols = list(keys())

__call__

__call__() -> int

현재 보유 금액을 반환

Returns:

Type Description
int

현재 보유 금액

Source code in zerohertzLib/quant/koreainvestment.py
def __call__(self) -> int:
    """현재 보유 금액을 반환

    Returns:
        현재 보유 금액
    """
    return self.balance["cash"]

__contains__

__contains__(item: Any) -> bool

보유 종목 여부를 확인

Parameters:

Name Type Description Default
item Any

보유 여부를 판단할 종목명

required

Returns:

Type Description
bool

입력 종목명의 보유 여부

Source code in zerohertzLib/quant/koreainvestment.py
def __contains__(self, item: Any) -> bool:
    """보유 종목 여부를 확인

    Args:
        item: 보유 여부를 판단할 종목명

    Returns:
        입력 종목명의 보유 여부
    """
    return item in self.balance["stock"]

__getitem__

__getitem__(idx: int) -> list[int | float | str]

Index에 따른 주식 정보를 반환

Parameters:

Name Type Description Default
idx int

Index

required

Returns:

Type Description
list[int | float | str]

Index에 따른 주식의 매수 시점과 현재의 정보

Source code in zerohertzLib/quant/koreainvestment.py
def __getitem__(self, idx: int) -> list[int | float | str]:
    """Index에 따른 주식 정보를 반환

    Args:
        idx: Index

    Returns:
        Index에 따른 주식의 매수 시점과 현재의 정보
    """
    return self.balance["stock"][self.symbols[idx]]

__len__

__len__() -> int

보유 주식 종류의 개수를 반환

Returns:

Type Description
int

보유 주식 종류의 수

Source code in zerohertzLib/quant/koreainvestment.py
def __len__(self) -> int:
    """보유 주식 종류의 개수를 반환

    Returns:
        보유 주식 종류의 수
    """
    return len(self.balance["stock"])

_exchange

_exchange() -> float

USD/KRW의 현재 시세를 조회

Returns:

Type Description
float

USD/KRW의 현재 시세

Source code in zerohertzLib/quant/koreainvestment.py
def _exchange(self) -> float:
    """USD/KRW의 현재 시세를 조회

    Returns:
        USD/KRW의 현재 시세
    """
    now = datetime.now()
    data = fdr.DataReader("USD/KRW", now - timedelta(days=10))
    return float(data.Close[-1])

barv

barv() -> str

현재 보유 종목의 이익과 손실을 bar chart로 시각화

Returns:

Type Description
str

저장된 graph의 절대 경로

Examples:

>>> balance.barv()
Source code in zerohertzLib/quant/koreainvestment.py
def barv(self) -> str:
    """현재 보유 종목의 이익과 손실을 bar chart로 시각화

    Returns:
        저장된 graph의 절대 경로

    Examples:
        >>> balance.barv()
    """
    if self.kor:
        dim = "₩"
    else:
        dim = "$"
    data = {}
    for value in self:
        data[value[0]] = value[5]
    figure((30, 10))
    barv(
        data,
        xlab="",
        ylab=f"Profit and Loss (P&L) [{dim}]",
        title="",
        dim="",
        dimsize=16,
    )
    plt.gca().yaxis.set_major_formatter(
        ticker.FuncFormatter(lambda x, p: format(int(x), ","))
    )
    return savefig("ProfitLoss", 100)

bought_symbols

bought_symbols() -> list[str]

보유 주식의 종목 code return

Returns:

Type Description
list[str]

보유 주식의 종목 code들

Examples:

>>> balance.bought_symbols():
['066570']
Source code in zerohertzLib/quant/koreainvestment.py
def bought_symbols(self) -> list[str]:
    """보유 주식의 종목 code return

    Returns:
        보유 주식의 종목 code들

    Examples:
        >>> balance.bought_symbols():
        ['066570']
    """
    return [value[0] for _, value in self.items()]

items

items() -> ItemsView[str, list[int | float | str]]

보유 주식의 반복문 사용을 위한 method

Returns:

Type Description
ItemsView[str, list[int | float | str]]

보유 종목 code와 그에 따른 정보들

Examples:

>>> for k, v in balance.items():
>>>     print(k, v)
Source code in zerohertzLib/quant/koreainvestment.py
def items(self) -> ItemsView[str, list[int | float | str]]:
    """보유 주식의 반복문 사용을 위한 method

    Returns:
        보유 종목 code와 그에 따른 정보들

    Examples:
        >>> for k, v in balance.items():
        >>>     print(k, v)
    """
    return self.balance["stock"].items()

merge

merge(balance: Balance) -> None

현재 계좌와 입력 계좌의 정보를 병합하는 function

Parameters:

Name Type Description Default
balance Balance

병합될 계좌 정보

required

Returns:

Type Description
None

현재 계좌에 정보 update

Examples:

>>> balance_1.merge(balance_2)
Source code in zerohertzLib/quant/koreainvestment.py
def merge(self, balance: "Balance") -> None:
    """현재 계좌와 입력 계좌의 정보를 병합하는 function

    Args:
        balance: 병합될 계좌 정보

    Returns:
        현재 계좌에 정보 update

    Examples:
        >>> balance_1.merge(balance_2)
    """
    merged_balance = copy.deepcopy(balance.balance)
    if self.kor != balance.kor:
        exchange = self._exchange()
        if not self.kor:
            exchange = 1 / exchange
        for key, value in balance.items():
            merged_balance["stock"][key][1] = value[1] * exchange
            merged_balance["stock"][key][2] = value[2] * exchange
            merged_balance["stock"][key][-1] = value[-1] * exchange
        merged_balance["cash"] = balance.balance["cash"] * exchange
    for key, value in merged_balance["stock"].items():
        if key in self:
            (
                _merged_code,
                _merged_buy_price,
                _merged_present_price,
                _merged_cnt,
                _merged_pandl_per,
                _merged_pandl_abs,
            ) = self.balance["stock"][key]
            (
                _tmp_code,
                _tmp_buy_price,
                _tmp_present_price,
                _tmp_cnt,
                _tmp_pandl_per,
                _tmp_pandl_abs,
            ) = value
            assert _merged_code == _tmp_code
            _merged_buy_price = (
                _merged_buy_price * _merged_cnt + _tmp_buy_price * _tmp_cnt
            ) / (_merged_cnt + _tmp_cnt)
            _merged_present_price = (_merged_present_price + _tmp_present_price) / 2
            _merged_cnt += _tmp_cnt
            _merged_pandl_abs = (
                _merged_present_price - _merged_buy_price
            ) * _merged_cnt
            _merged_pandl_per = (
                (_merged_present_price - _merged_buy_price)
                / _merged_buy_price
                * 100
            )
            self.balance["stock"][key] = [
                _merged_code,
                _merged_buy_price,
                _merged_present_price,
                _merged_cnt,
                _merged_pandl_per,
                _merged_pandl_abs,
            ]
        else:
            self.symbols.append(key)
            self.balance["stock"][key] = value
    self.balance["cash"] += merged_balance["cash"]
    self.balance["stock"] = dict(
        sorted(
            self.balance["stock"].items(),
            key=lambda item: item[1][1] * item[1][3],
            reverse=True,
        )
    )
    self.symbols = list(self.balance["stock"].keys())

pie

pie() -> str

현재 보유 종목을 pie chart로 시각화

Returns:

Type Description
str

저장된 graph의 절대 경로

Examples:

>>> balance.pie()
Source code in zerohertzLib/quant/koreainvestment.py
def pie(self) -> str:
    """현재 보유 종목을 pie chart로 시각화

    Returns:
        저장된 graph의 절대 경로

    Examples:
        >>> balance.pie()
    """
    if self() == 0:
        return None
    if self.kor:
        dim = "₩"
    else:
        dim = "$"
    data = defaultdict(float)
    data["Cash"] = 0
    for name, value in self.items():
        _, purchase, _, quantity, _, _ = value
        data[name] = purchase * quantity
    cash = self() - sum(data.values())
    data["Cash"] = max(data["Cash"], cash)
    return pie(data, dim, title="Portfolio", dpi=100, int_label=self.kor)

table

table() -> str

현재 계좌의 상태를 image로 저장

Returns:

Type Description
str

저장된 image의 절대 경로

Examples:

>>> balance.table()
Source code in zerohertzLib/quant/koreainvestment.py
def table(self) -> str:
    """현재 계좌의 상태를 image로 저장

    Returns:
        저장된 image의 절대 경로

    Examples:
        >>> balance.table()
    """
    if self() == 0:
        return None
    if self.kor:
        col = [
            "Purchase Price [₩]",
            "Current Price [₩]",
            "Quantity",
            "Profit and Loss (P&L) [%]",
            "Profit and Loss (P&L) [₩]",
        ]
    else:
        col = [
            "Purchase Price [$]",
            "Current Price [$]",
            "Quantity",
            "Profit and Loss (P&L) [%]",
            "Profit and Loss (P&L) [$]",
        ]
    row = []
    data = []
    purchase_total = 0
    current_total = 0
    for name, value in self.items():
        _, purchase, current, quantity, pandl_per, pandl_abs = value
        row.append(name)
        data.append(
            [
                _cash2str(purchase, self.kor),
                _cash2str(current, self.kor),
                quantity,
                f"{pandl_per:.2f}%",
                _cash2str(pandl_abs, self.kor),
            ]
        )
        purchase_total += purchase * quantity
        current_total += current * quantity
    row.append("TOTAL")
    if purchase_total == 0:
        pandl_total = 0
    else:
        pandl_total = (current_total - purchase_total) / purchase_total * 100
    data.append(
        [
            _cash2str(purchase_total, self.kor),
            _cash2str(current_total, self.kor),
            "-",
            f"{pandl_total:.2f}%",
            f"{_cash2str(current_total - purchase_total, self.kor)}\n\n{_cash2str(self(), self.kor)}",
        ]
    )
    return table(
        data,
        col,
        row,
        title="balance",
        figsize=(16, int(1.2 * len(row))),
        dpi=100,
    )

Experiments

Experiments(title: str, data: DataFrame, ohlc: str = '', vis: bool = False, report: bool = True)

Full factorial design 기반의 backtest 수행 class

Parameters:

Name Type Description Default
title str

종목 이름

required
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
ohlc str

사용할 data 의 column 이름

''
vis bool

Candle chart 시각화 여부

False
report bool

Experiment 결과 출력 여부

True

Examples:

>>> experiments = zz.quant.Experiments(title, data)

Methods:

Name Description
bollinger_bands

Bollinger bands 전략 실험

macd

MACD 전략 실험

momentum

Momentum 전략 실험

moving_average

Moving average 전략 실험

rsi

RSI 전략 실험

Attributes:

Name Type Description
data
exps_bollinger_bands
exps_macd
exps_momentum
exps_moving_average
exps_rsi
ohlc
report
title
vis
Source code in zerohertzLib/quant/backtest.py
def __init__(
    self,
    title: str,
    data: pd.DataFrame,
    ohlc: str = "",
    vis: bool = False,
    report: bool = True,
) -> None:
    self.title = title
    self.data = data
    self.ohlc = ohlc
    self.vis = vis
    self.report = report
    self.exps_moving_average = [[20, 30, 40], [60, 70, 80], [0.0, 0.5, 1.0]]
    self.exps_rsi = [[10, 15, 20], [60, 70, 80], [15, 30]]
    self.exps_bollinger_bands = [[10, 30, 60], [1.9, 2, 2.25, 2.5]]
    self.exps_momentum = [[5, 10, 15, 30]]
    self.exps_macd = [[6, 12, 24, 36], [5, 9, 18]]

data instance-attribute

data = data

exps_bollinger_bands instance-attribute

exps_bollinger_bands = [[10, 30, 60], [1.9, 2, 2.25, 2.5]]

exps_macd instance-attribute

exps_macd = [[6, 12, 24, 36], [5, 9, 18]]

exps_momentum instance-attribute

exps_momentum = [[5, 10, 15, 30]]

exps_moving_average instance-attribute

exps_moving_average = [[20, 30, 40], [60, 70, 80], [0.0, 0.5, 1.0]]

exps_rsi instance-attribute

exps_rsi = [[10, 15, 20], [60, 70, 80], [15, 30]]

ohlc instance-attribute

ohlc = ohlc

report instance-attribute

report = report

title instance-attribute

title = title

vis instance-attribute

vis = vis

_experiments

_experiments(method: Callable[[Any], DataFrame], exps: list[list[Any]]) -> dict[str, list[Any]]
Source code in zerohertzLib/quant/backtest.py
def _experiments(
    self,
    method: Callable[[Any], pd.DataFrame],
    exps: list[list[Any]],
) -> dict[str, list[Any]]:
    return experiments(
        self.title,
        self.data,
        method,
        exps,
        ohlc=self.ohlc,
        vis=self.vis,
        report=self.report,
    )

bollinger_bands

bollinger_bands(exps: list[list[Any]] | None = None) -> dict[str, list[Any]]

Bollinger bands 전략 실험

Parameters:

Name Type Description Default
exps list[list[Any]] | None

전략 function에 입력될 변수들의 범위

None

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Source code in zerohertzLib/quant/backtest.py
def bollinger_bands(
    self, exps: list[list[Any]] | None = None
) -> dict[str, list[Any]]:
    """Bollinger bands 전략 실험

    Args:
        exps: 전략 function에 입력될 변수들의 범위

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters
    """
    if exps is None:
        exps = self.exps_bollinger_bands
    return self._experiments(bollinger_bands, exps)

macd

macd(exps: list[list[Any]] | None = None) -> dict[str, list[Any]]

MACD 전략 실험

Parameters:

Name Type Description Default
exps list[list[Any]] | None

전략 function에 입력될 변수들의 범위

None

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Source code in zerohertzLib/quant/backtest.py
def macd(self, exps: list[list[Any]] | None = None) -> dict[str, list[Any]]:
    """MACD 전략 실험

    Args:
        exps: 전략 function에 입력될 변수들의 범위

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters
    """
    if exps is None:
        exps = self.exps_macd
    return self._experiments(macd, exps)

momentum

momentum(exps: list[list[Any]] | None = None) -> dict[str, list[Any]]

Momentum 전략 실험

Parameters:

Name Type Description Default
exps list[list[Any]] | None

전략 function에 입력될 변수들의 범위

None

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Source code in zerohertzLib/quant/backtest.py
def momentum(self, exps: list[list[Any]] | None = None) -> dict[str, list[Any]]:
    """Momentum 전략 실험

    Args:
        exps: 전략 function에 입력될 변수들의 범위

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters
    """
    if exps is None:
        exps = self.exps_momentum
    return self._experiments(momentum, exps)

moving_average

moving_average(exps: list[list[Any]] | None = None) -> dict[str, list[Any]]

Moving average 전략 실험

Parameters:

Name Type Description Default
exps list[list[Any]] | None

전략 function에 입력될 변수들의 범위

None

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Source code in zerohertzLib/quant/backtest.py
def moving_average(
    self, exps: list[list[Any]] | None = None
) -> dict[str, list[Any]]:
    """Moving average 전략 실험

    Args:
        exps: 전략 function에 입력될 변수들의 범위

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters
    """
    if exps is None:
        exps = self.exps_moving_average
    return self._experiments(moving_average, exps)

rsi

rsi(exps: list[list[Any]] | None = None) -> dict[str, list[Any]]

RSI 전략 실험

Parameters:

Name Type Description Default
exps list[list[Any]] | None

전략 function에 입력될 변수들의 범위

None

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Source code in zerohertzLib/quant/backtest.py
def rsi(self, exps: list[list[Any]] | None = None) -> dict[str, list[Any]]:
    """RSI 전략 실험

    Args:
        exps: 전략 function에 입력될 변수들의 범위

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters
    """
    if exps is None:
        exps = self.exps_rsi
    return self._experiments(rsi, exps)

Quant

Quant(title: str, data: DataFrame, ohlc: str = '', top: int = 1, methods: dict[str, list[list[Any]]] | None = None, report: bool = False)

Bases: Experiments

한 가지 종목에 대해 full factorial design 기반의 backtest를 수행하고 최적의 전략을 융합하는 class

Parameters:

Name Type Description Default
title str

종목 이름

required
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
ohlc str

사용할 data의 column 이름

''
top int

Experiment 과정에서 사용할 각 전략별 수

1
methods dict[str, list[list[Any]]] | None

사용할 전략들의 function명 및 parameters

None
report bool

Experiment 결과 출력 여부

False

Attributes:

Name Type Description
signals

융합된 전략의 signal

methods

융합된 전략명

profit

융합된 전략의 backtest profit

buy

융합된 전략의 backtest 시 총 매수

sell

융합된 전략의 backtest 시 총 매도

transaction

융합된 전략의 backtest 시 거래 정보 (매수가, 매도가, 수익률, 거래 기간)

threshold_buy

융합된 전략의 매수 signal threshold

threshold_sell

융합된 전략의 매도 signal threshold

total_cnt

융합된 전략의 수

methods_cnt

각 전략에 따른 이익이 존재하는 수

exps_cnt

각 전략과 parameter에 따른 이익이 존재하는 수

exps_str

각 전략에 따른 이익이 존재하는 paramter 문자열

Examples:

>>> qnt = zz.quant.Quant(title, data, top=3)
>>> qnt.signals.columns
Index(['moving_average', 'rsi', 'bollinger_bands', 'momentum', 'macd', 'signals', 'logic'], dtype='object')
>>> qnt.methods
('moving_average', 'bollinger_bands', 'macd')
>>> qnt.profit
23.749412256412935
>>> qnt.buy
3828200.0
>>> qnt.sell
4737375.0
>>> qnt.transaction
defaultdict(<class 'list'>, {'buy': [92850.0, ...], 'sell': [105275.0, ...], 'profit': [11.802422227499406, ...], 'period': [205, ...]})
>>> qnt.threshold_buy
1
>>> qnt.threshold_sell
-4
>>> qnt.total_cnt
9
>>> qnt.methods_cnt
defaultdict(<class 'int'>, {'moving_average': 3, 'rsi': 3, 'bollinger_bands': 3, 'momentum': 1, 'macd': 3})
>>> qnt.exps_cnt
defaultdict(None, {'moving_average': [defaultdict(<class 'int'>, {'20': 3}), ...], ...})
>>> qnt.exps_str
defaultdict(<class 'list'>, {'moving_average': ['20-70-1.0', '20-60-1.0', '20-70-0.0'], ...})
>>> qnt()
defaultdict(<class 'list'>, {'moving_average': [0, 0.0], 'bollinger_bands': [0, 0.0], 'macd': [0, 0.0], 'logic': 0, 'total': [0, 0.0], 'position': 'None'})
>>> qnt("20231211")
defaultdict(<class 'list'>, {'moving_average': [0, 0.0], 'bollinger_bands': [3, 100.0], 'macd': [0, 0.0], 'logic': 1, 'total': [3, 33.33333333333333], 'position': 'Buy'})
>>> qnt("2023-12-08")
defaultdict(<class 'list'>, {'moving_average': [0, 0.0], 'bollinger_bands': [3, 100.0], 'macd': [0, 0.0], 'logic': 1, 'total': [3, 33.33333333333333], 'position': 'Buy'})

Methods:

Name Description
__call__

입력된 날짜에 대해 분석 정보 return

Source code in zerohertzLib/quant/quant.py
def __init__(
    self,
    title: str,
    data: pd.DataFrame,
    ohlc: str = "",
    top: int = 1,
    methods: dict[str, list[list[Any]]] | None = None,
    report: bool = False,
) -> None:
    super().__init__(title, data, ohlc, False, report)
    self.signals = pd.DataFrame(index=data.index)
    self.total_cnt = 0
    self.methods_cnt = defaultdict(int)
    self.exps_cnt = defaultdict()
    self.exps_str = defaultdict(list)
    if methods is None:
        methods = {
            "moving_average": self.exps_moving_average,
            "rsi": self.exps_rsi,
            "bollinger_bands": self.exps_bollinger_bands,
            "momentum": self.exps_momentum,
            "macd": self.exps_macd,
        }
    # 선정한 전략들의 parameter 최적화
    is_profit = 0
    for method, exps in methods.items():
        if hasattr(self, method):
            self.signals[method] = 0
            results = getattr(self, method)(exps)
            profits, signals, exps_str, exps_tup = (
                results["profits"],
                results["signals"],
                results["exps_str"],
                results["exps_tup"],
            )
            exps_cnt = [defaultdict(int) for _ in range(len(exps_tup[0]))]
            for profit, signal, exp_str, exp_tup in zip(
                profits[:top], signals[:top], exps_str[:top], exps_tup[:top]
            ):
                if profit > 0:
                    self.signals[method] += signal["signals"]
                    self.exps_str[method].append(exp_str)
                    for i, ex in enumerate(exp_tup):
                        exps_cnt[i][str(ex)] += 1
                    self.methods_cnt[method] += 1
                    is_profit += 1
            self.exps_cnt[method] = exps_cnt
        else:
            raise AttributeError(f"'Quant' object has no attribute '{method}'")
    # 전략 간 조합 최적화
    if is_profit >= 1:
        backtests = []
        for cnt in range(1, min(3, len(methods)) + 1):
            for methods_in_use in combinations(methods.keys(), cnt):
                miu_total = 0
                for miu in methods_in_use:
                    miu_total += self.methods_cnt[miu]
                    if self.methods_cnt[miu] < 1:
                        miu_total = 0
                        break
                if miu_total == 0:
                    continue
                self.signals["signals"] = self.signals.loc[:, methods_in_use].sum(1)
                for threshold_sell in range(1, max(2, miu_total)):
                    for threshold_buy in range(1, max(2, miu_total)):
                        results = backtest(
                            self.data,
                            self.signals,
                            ohlc=ohlc,
                            threshold=(-threshold_sell, threshold_buy),
                        )
                        backtests.append(
                            {
                                "profit": results["profit"],
                                "weighted_profit": results["weighted_profit"],
                                "threshold": (-threshold_sell, threshold_buy),
                                "methods": methods_in_use,
                                "total": miu_total,
                                "transaction": results["transaction"],
                                "buy": results["buy"],
                                "sell": results["sell"],
                                "logic": self.signals["logic"].copy(),
                            }
                        )
        backtests.sort(key=lambda x: x["weighted_profit"], reverse=True)
        # 최적 융합 전략
        self.signals["signals"] = self.signals.loc[:, backtests[0]["methods"]].sum(
            1
        )
        self.signals["logic"] = backtests[0]["logic"]
        self.methods = backtests[0]["methods"]
        self.profit = backtests[0]["profit"]
        self.total_cnt = backtests[0]["total"]
        self.buy, self.sell = backtests[0]["buy"], backtests[0]["sell"]
        self.transaction = backtests[0]["transaction"]
        self.threshold_sell, self.threshold_buy = backtests[0]["threshold"]

exps_cnt instance-attribute

exps_cnt = defaultdict()

exps_str instance-attribute

exps_str = defaultdict(list)

methods instance-attribute

methods = backtests[0]['methods']

methods_cnt instance-attribute

methods_cnt = defaultdict(int)

profit instance-attribute

profit = backtests[0]['profit']

signals instance-attribute

signals = DataFrame(index=index)

total_cnt instance-attribute

total_cnt = 0

transaction instance-attribute

transaction = backtests[0]['transaction']

__call__

__call__(day: str | int = -1) -> dict[str, Any]

입력된 날짜에 대해 분석 정보 return

Parameters:

Name Type Description Default
day str | int

분석할 날짜

-1

Returns:

Type Description
dict[str, Any]

각 전략에 따른 분석 정보 및 결론

Source code in zerohertzLib/quant/quant.py
def __call__(self, day: str | int = -1) -> dict[str, Any]:
    """
    입력된 날짜에 대해 분석 정보 return

    Args:
        day: 분석할 날짜

    Returns:
        각 전략에 따른 분석 정보 및 결론
    """
    if self.total_cnt < 1:
        return {"position": "NULL"}
    if day != -1 and "-" not in day:
        day = day[:4] + "-" + day[4:6] + "-" + day[6:8]
    possibility = defaultdict(list)
    for key in self.methods:
        possibility[key] = [
            self.signals[key].iloc[day],
            self.signals[key].iloc[day] / self.methods_cnt[key] * 100,
        ]
    possibility["logic"] = self.signals["logic"].iloc[day]
    possibility["total"] = [
        self.signals["signals"].iloc[day],
        self.signals["signals"].iloc[day] / self.total_cnt * 100,
    ]
    if 0 < possibility["logic"]:
        possibility["position"] = "Buy"
    elif 0 > possibility["logic"]:
        possibility["position"] = "Sell"
    elif self.threshold_buy <= possibility["total"][0]:
        possibility["position"] = "Buy"
        possibility["logic"] = 1
    elif self.threshold_sell >= possibility["total"][0]:
        possibility["position"] = "Sell"
        possibility["logic"] = -1
    else:
        possibility["position"] = "None"
    return possibility

QuantBot

QuantBot(symbols: int | list[str], start_day: str = '', ohlc: str = '', top: int = 1, methods: dict[str, list[list[Any]]] | None = None, report: bool = False, token: str | None = None, channel: str | None = None, name: str | None = None, icon_emoji: str | None = None, mp_num: int = 0, analysis: bool = False, kor: bool = True)

입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Slack으로 message와 graph를 전송하는 class

Note

Abstract Base Class: 종목 code에 따른 종목명과 data를 불러오는 abstract method _get_data 정의 후 사용

def _get_data(self, symbol: str) -> tuple[str, pd.DataFrame]:
    title = data = None
    return title, data

Parameters:

Name Type Description Default
symbols int | list[str]

종목 code들

required
start_day str

조회 시작 일자 (YYYYMMDD)

''
ohlc str

사용할 data의 column 이름

''
top int

Experiment 과정에서 사용할 각 전략별 수

1
methods dict[str, list[list[Any]]] | None

사용할 전략들의 function명 및 parameters

None
report bool

Experiment 결과 출력 여부

False
token str | None

Bot의 token (xoxb- prefix로 시작하면 SlackBot, 아니면 DiscordBot)

None
channel str | None

Bot이 전송할 channel

None
name str | None

Bot의 표시될 이름

None
icon_emoji str | None

Bot의 표시될 사진 (emoji)

None
mp_num int

병렬 처리에 사용될 process의 수 (0: 직렬 처리)

0
analysis bool

각 전략의 보고서 전송 여부

False
kor bool

국내 여부

True

Attributes:

Name Type Description
exps

각 전략에 따른 parameter 분포

Examples:

>>> qsb = zz.quant.QuantBot(symbols, token=token, channel=channel)
>>> qsb.index()

QuantSlackBot example

Methods:

Name Description
buy

매수 signals 탐색

index

모든 signals 탐색

Source code in zerohertzLib/quant/quant.py
def __init__(
    self,
    symbols: int | list[str],
    start_day: str = "",
    ohlc: str = "",
    top: int = 1,
    methods: dict[str, list[list[Any]]] | None = None,
    report: bool = False,
    token: str | None = None,
    channel: str | None = None,
    name: str | None = None,
    icon_emoji: str | None = None,
    mp_num: int = 0,
    analysis: bool = False,
    kor: bool = True,
) -> None:
    if token is None or channel is None:
        self.bot = MockedBot()
    elif token.startswith("xoxb-"):
        self.bot = SlackBot(
            token=token, channel=channel, name=name, icon_emoji=icon_emoji
        )
    else:
        self.bot = DiscordBot(token=token, channel=channel)
    self.symbols = symbols
    self.start_day = start_day
    self.ohlc = ohlc
    self.top = top
    self.methods = methods
    if mp_num > mp.cpu_count():
        self.mp_num = mp.cpu_count()
    else:
        self.mp_num = mp_num
    self.analysis = analysis
    self.kor = kor
    self.report = report

analysis instance-attribute

analysis = analysis

bot instance-attribute

bot = MockedBot()

kor instance-attribute

kor = kor

methods instance-attribute

methods = methods

mp_num instance-attribute

mp_num = cpu_count()

ohlc instance-attribute

ohlc = ohlc

report instance-attribute

report = report

start_day instance-attribute

start_day = start_day

symbols instance-attribute

symbols = symbols

top instance-attribute

top = top

_analysis_send

_analysis_send() -> None
Source code in zerohertzLib/quant/quant.py
def _analysis_send(self) -> None:
    response = self.bot.message("## :memo: Parameter Analysis")
    thread_id = self.bot.get_thread_id(response, name="Parameter Analysis")
    figure((30, 20))
    subplot(2, 2, 1)
    barv(
        dict(sorted(self.miu_cnt.items())),
        title=f"Methods in Use (Avg: {sum(self.miu_cnt.values()) / self.quant_cnt:.2f})",
        dim="%",
    )
    subplot(2, 2, 2)
    hist(
        {"": self.total_cnt},
        title=f"Distribution of Methods in Use (Avg: {sum(self.total_cnt) / self.quant_cnt:.2f})",
        cnt=max(self.total_cnt) * 2,
        ovp=True,
    )
    subplot(2, 2, 3)
    barv(
        dict(
            ((key, sum(value)) for key, value in sorted(self.methods_cnt.items()))
        ),
        title="Available Methods",
        dim="%",
    )
    subplot(2, 2, 4)
    hist(
        dict(sorted(self.methods_cnt.items())),
        title="Distribution of Available Methods",
        cnt=self.top * 2,
        ovp=False,
    )
    path = savefig("Methods", 100)
    self.bot.file(path, thread_id=thread_id)
    for method, cnt in self.exps_cnt.items():
        figure((18, 8))
        stg = True
        for idx, count in enumerate(cnt):
            try:
                subplot(1, len(cnt), idx + 1)
                barh(count, title="", dim="%")
            except IndexError:
                stg = False
                break
        if stg:
            path = savefig(method, dpi=100)
            self.bot.file(path, thread_id=thread_id)
        else:
            self.bot.message(
                f":no_bell: '{method}' was not available", thread_id=thread_id
            )

_analysis_update

_analysis_update(quant: Quant) -> None
Source code in zerohertzLib/quant/quant.py
def _analysis_update(
    self,
    quant: Quant,
) -> None:
    methods, total_cnt, methods_cnt, exps_cnt = (
        quant.methods,
        quant.total_cnt,
        quant.methods_cnt,
        quant.exps_cnt,
    )
    self.quant_cnt += 1
    for method in methods:
        self.miu_cnt[_method2str(method)] += 1
    self.total_cnt.append(total_cnt)
    for method, cnt in methods_cnt.items():
        if cnt > 0:
            self.methods_cnt[_method2str(method)].append(cnt)
    for method, cnt in exps_cnt.items():
        if method not in self.exps_cnt.keys():
            self.exps_cnt[method] = [defaultdict(int) for _ in range(len(cnt))]
        for idx, _cnt in enumerate(cnt):
            for param, __cnt in _cnt.items():
                self.exps_cnt[method][idx][param] += __cnt

_get_data abstractmethod

_get_data(symbol: str) -> tuple[str, DataFrame]
Source code in zerohertzLib/quant/quant.py
@abstractmethod
def _get_data(self, symbol: str) -> tuple[str, pd.DataFrame]:
    title = data = None
    return title, data

_inference

_inference(symbols: list[str], mode: Literal['Buy', 'Sell', 'All']) -> None
Source code in zerohertzLib/quant/quant.py
def _inference(
    self, symbols: list[str], mode: Literal["Buy", "Sell", "All"]
) -> None:
    start = time.time()
    if self.analysis:
        # 유효한 Quant instance의 수
        self.quant_cnt = 0
        # [Methods in Use: O] 사용된 전략 종류의 수
        self.miu_cnt = defaultdict(int)
        # [Methods in Use: O] 사용된 전략의 수 (같은 전략 포함)
        self.total_cnt = []
        # [Methods in Use: X] 전략에 따른 이익이 존재하는 수
        self.methods_cnt = defaultdict(list)
        # [Methods in Use: X] 전략과 parameter에 따른 이익이 존재하는 수
        self.exps_cnt = defaultdict(list)
    response = self.bot.message(f"# :moneybag: **Check {mode} Signals**")
    progress_thread_id = self.bot.get_thread_id(
        response, name=f"Check {mode} Signals"
    )
    self.bot.message(
        ", ".join(symbols), codeblock=True, thread_id=progress_thread_id
    )
    if self.mp_num == 0 or self.mp_num >= len(symbols):
        quants = []
        for idx, symbol in enumerate(symbols):
            quants.append(
                self._run(
                    symbol=symbol,
                    mode=mode,
                    idx=idx,
                    progress_thread_id=progress_thread_id,
                )
            )
    else:
        with mp.Manager() as manager:
            idx = manager.Value("i", 0)
            args = [(symbol, mode, idx, progress_thread_id) for symbol in symbols]
            with mp.Pool(processes=self.mp_num) as pool:
                quants = pool.map(self._run_mp, args)
    if self.analysis:
        for quant in quants:
            if quant is None:
                continue
            self._analysis_update(quant)
        self._analysis_send()
    end = time.time()
    self.bot.message(
        f"# :tada: **Done!**\n> `{_seconds_to_hms(end - start, sign=2)}`"
    )

_plot

_plot(quant: Quant) -> tuple[str, str]
Source code in zerohertzLib/quant/quant.py
def _plot(self, quant: Quant) -> tuple[str, str]:
    candle_path = candle(
        quant.data[-500:],
        quant.title,
        signals=quant.signals.iloc[-500:, :].loc[
            :, [*quant.methods, "signals", "logic"]
        ],
        dpi=100,
        threshold=(quant.threshold_sell, quant.threshold_buy),
    )
    figure((10, 18))
    subplot(3, 1, 1)
    if self.kor:
        dim = "₩"
    else:
        dim = "$"
    hist(
        {"Buy": quant.transaction["buy"], "Sell": quant.transaction["sell"]},
        xlab=f"매수/매도가 [{dim}]",
        title="",
    )
    subplot(3, 1, 2)
    hist(
        {"Profit": quant.transaction["profit"]},
        xlab="이율 [%]",
        title="",
    )
    subplot(3, 1, 3)
    hist(
        {"Period": quant.transaction["period"]},
        xlab="거래 기간 [일]",
        title="",
    )
    hist_path = savefig(f"{quant.title}_backtest", 100)
    return candle_path, hist_path

_progress

_progress(symbol: str, idx: int | ValueProxy, progress_thread_id: str, status: Literal['completed', 'failed']) -> None
Source code in zerohertzLib/quant/quant.py
def _progress(
    self,
    symbol: str,
    idx: int | ValueProxy,
    progress_thread_id: str,
    status: Literal["completed", "failed"],
) -> None:
    progress_message = "`{:.2f}%` (`{}/{}`) - `{}` analysis {}!"
    if isinstance(idx, ValueProxy):
        idx.value += 1
        tmp_idx = idx.get()
    else:
        tmp_idx = idx + 1
    self.bot.message(
        message=progress_message.format(
            tmp_idx / len(self.symbols) * 100,
            tmp_idx,
            len(self.symbols),
            symbol,
            status,
        ),
        thread_id=progress_thread_id,
    )

_report

_report(symbol: str, quant: Quant, today: dict[str, Any]) -> None
Source code in zerohertzLib/quant/quant.py
def _report(self, symbol: str, quant: Quant, today: dict[str, Any]) -> None:
    logic = {-2: "손절", -1: "매도", 0: "중립", 1: "매수", 2: "추가 매수"}
    report = defaultdict(str)
    if today["position"] == "Buy":
        report["main"] += "## :chart_with_upwards_trend: [Buy Signal]"
    elif today["position"] == "Sell":
        report["main"] += "## :chart_with_downwards_trend: [Sell Signal]"
    else:
        report["main"] += "## :egg: [None Signal]"
    report["main"] += f" **{quant.title}** (`{symbol}`)\n"
    report["main"] += (
        f"\t:technologist: Signal Info: {today['total'][1]:.2f}% ({int(today['total'][0])}/{int(quant.total_cnt)}) → {logic[today['logic']]}\n"
    )
    report["param"] += "> :information_desk_person: *Parameter Info*"
    for key in quant.methods:
        report["main"] += (
            f"\t\t:hammer: {_method2str(key)}: {today[key][1]:.2f}% ({int(today[key][0])}/{int(quant.methods_cnt[key])})\n"
        )
        report["param"] += (
            f"\n\t:hammer: {_method2str(key)}: `{'`, `'.join(quant.exps_str[key])}`"
        )
    report["main"] += "\t:memo: Threshold:\n"
    report["main"] += (
        f"\t\t:arrow_double_up: Buy: {quant.threshold_buy}\n\t\t:arrow_double_down: Sell: {quant.threshold_sell}"
    )
    report["backtest"] += (
        f"> :computer: *Backtest* ({self.start_day[:4]}/{self.start_day[4:6]}/{self.start_day[6:]} ~)\n\t:money_with_wings: Total Profit:\t{quant.profit:.2f}%\n"
    )
    report["backtest"] += (
        f"\t:chart_with_upwards_trend: Total Buy:\t{_cash2str(quant.buy, self.kor)}\n"
    )
    report["backtest"] += (
        f"\t:chart_with_downwards_trend: Total Sell:\t{_cash2str(quant.sell, self.kor)}\n"
    )
    report["candle"], report["hist"] = self._plot(quant)
    response = self.bot.message(report["main"])
    thread_id = self.bot.get_thread_id(response, name=symbol)
    self.bot.file(report["candle"], thread_id=thread_id)
    response = self.bot.message(report["backtest"], thread_id=thread_id)
    self.bot.file(report["hist"], thread_id=thread_id)
    response = self.bot.message(report["param"], thread_id=thread_id)

_run

_run(symbol: str, mode: str, idx: int | ValueProxy, progress_thread_id: str) -> Quant | None
Source code in zerohertzLib/quant/quant.py
def _run(
    self,
    symbol: str,
    mode: str,
    idx: int | ValueProxy,
    progress_thread_id: str,
) -> Quant | None:
    try:
        title, data = self._get_data(symbol)
        if len(data) < 20:
            raise KeyError(f"`data` is too short ({len(data)=})")
    except (KeyError, HTTPError) as error:
        self._traceback(
            error=error,
            message=f"## :x: `{symbol}` was not found",
            symbol=symbol,
            idx=idx,
            progress_thread_id=progress_thread_id,
        )
        return
    try:
        quant = Quant(
            title,
            data,
            ohlc=self.ohlc,
            top=self.top,
            methods=self.methods,
            report=self.report,
        )
        today = quant()
    except IndexError as error:
        self._traceback(
            error=error,
            message=f"## :x: `{symbol}` ({title}): {data.index[0]} ({len(data)})",
            symbol=symbol,
            idx=idx,
            progress_thread_id=progress_thread_id,
        )
        return
    if mode == "Buy":
        positions = ["Buy"]
    else:
        positions = ["Buy", "Sell", "None"]
    if today["position"] in positions:
        self._report(symbol, quant, today)
    self._progress(
        symbol=symbol,
        idx=idx,
        progress_thread_id=progress_thread_id,
        status="completed",
    )
    return quant if today["position"] != "NULL" else None

_run_mp

_run_mp(args: tuple[str, str, ValueProxy, str]) -> Quant | None
Source code in zerohertzLib/quant/quant.py
def _run_mp(self, args: tuple[str, str, ValueProxy, str]) -> Quant | None:
    symbol, mode, idx, progress_thread_id = args
    return self._run(
        symbol=symbol,
        mode=mode,
        idx=idx,
        progress_thread_id=progress_thread_id,
    )

_traceback

_traceback(error: Exception, message: str, symbol: str, idx: int | ValueProxy, progress_thread_id: str) -> None
Source code in zerohertzLib/quant/quant.py
def _traceback(
    self,
    error: Exception,
    message: str,
    symbol: str,
    idx: int | ValueProxy,
    progress_thread_id: str,
) -> None:
    response = self.bot.message(message=message)
    thread_id = self.bot.get_thread_id(response, name=message)
    self.bot.message(str(error), codeblock=True, thread_id=thread_id)
    self.bot.message(traceback.format_exc(), codeblock=True, thread_id=thread_id)
    self._progress(
        symbol=symbol,
        idx=idx,
        progress_thread_id=progress_thread_id,
        status="failed",
    )

buy

buy() -> None

매수 signals 탐색

Source code in zerohertzLib/quant/quant.py
def buy(self) -> None:
    """매수 signals 탐색"""
    self._inference(self.symbols, "Buy")

index

index() -> None

모든 signals 탐색

Source code in zerohertzLib/quant/quant.py
def index(self) -> None:
    """모든 signals 탐색"""
    self._inference(self.symbols, "All")

QuantBotFDR

QuantBotFDR(symbols: int | list[str], start_day: str = '', ohlc: str = '', top: int = 1, methods: dict[str, list[list[Any]]] | None = None, report: bool = False, token: str | None = None, channel: str | None = None, name: str | None = None, icon_emoji: str | None = None, mp_num: int = 0, analysis: bool = False, kor: bool = True)

Bases: QuantBot

FinanceDataReader module 기반으로 입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Bot을 통해 message와 graph를 전송하는 class

Parameters:

Name Type Description Default
symbols int | list[str]

종목 code들 혹은 시가 총액 순위

required
start_day str

조회 시작 일자 (YYYYMMDD)

''
ohlc str

사용할 data의 column 이름

''
top int

Experiment 과정에서 사용할 각 전략별 수

1
methods dict[str, list[list[Any]]] | None

사용할 전략들의 function명 및 parameters

None
report bool

Experiment 결과 출력 여부

False
token str | None

Bot의 token (xoxb- prefix로 시작하면 SlackBot, 아니면 DiscordBot)

None
channel str | None

Bot이 전송할 channel

None
name str | None

Bot의 표시될 이름

None
icon_emoji str | None

Bot의 표시될 사진 (emoji)

None
mp_num int

병렬 처리에 사용될 process의 수 (0: 직렬 처리)

0
analysis bool

각 전략의 보고서 전송 여부

False
kor bool

국내 여부

True

Attributes:

Name Type Description
exps

각 전략에 따른 parameter 분포

market

kor에 따른 시장 목록

Examples:

>>> qbf = zz.quant.QuantBotFDR(symbols, token=token, channel=channel)
>>> qbf = zz.quant.QuantBotFDR(10, token=token, channel=channel)
Source code in zerohertzLib/quant/quant.py
def __init__(
    self,
    symbols: int | list[str],
    start_day: str = "",
    ohlc: str = "",
    top: int = 1,
    methods: dict[str, list[list[Any]]] | None = None,
    report: bool = False,
    token: str | None = None,
    channel: str | None = None,
    name: str | None = None,
    icon_emoji: str | None = None,
    mp_num: int = 0,
    analysis: bool = False,
    kor: bool = True,
) -> None:
    QuantBot.__init__(
        self,
        symbols=symbols,
        start_day=start_day,
        ohlc=ohlc,
        top=top,
        methods=methods,
        report=report,
        token=token,
        channel=channel,
        name=name,
        icon_emoji=icon_emoji,
        mp_num=mp_num,
        analysis=analysis,
        kor=kor,
    )
    if kor:
        # FIXME:
        # FDR 의존성 내에서 KRX-DESC 코드 사용 시 오류 발생
        # https://github.com/FinanceData/FinanceDataReader/pull/254
        market = os.getenv("QUANT_MARKET_KOR", "ETF/KR")
    else:
        market = os.getenv("QUANT_MARKET_OVS", "NASDAQ")
    self.market = fdr.StockListing(market)
    if isinstance(symbols, int):
        self.symbols = list(self.market[self.market.columns[0]])[:symbols]

market instance-attribute

market = StockListing(market)

symbols instance-attribute

symbols = list(market[columns[0]])[:symbols]

_get_data

_get_data(symbol: str) -> tuple[str, DataFrame]
Source code in zerohertzLib/quant/quant.py
def _get_data(self, symbol: str) -> tuple[str, pd.DataFrame]:
    try:
        title = self.market[
            self.market[self.market.columns[0]] == symbol
        ].Name.iloc[0]
    except IndexError:
        title = symbol
    data = fdr.DataReader(symbol, self.start_day)
    return title, data

QuantBotKI

QuantBotKI(account_no: str, symbols: list[str] | None = None, start_day: str = '', ohlc: str = '', top: int = 1, methods: dict[str, list[list[Any]]] | None = None, report: bool = False, token: str | None = None, channel: str | None = None, name: str | None = None, icon_emoji: str | None = None, mp_num: int = 0, analysis: bool = False, kor: bool = True, path: str = './')

Bases: Balance, QuantBot

한국투자증권 API를 기반으로 입력된 여러 종목에 대해 매수, 매도 signal을 판단하고 Bot을 통해 message와 graph를 전송하는 class

Parameters:

Name Type Description Default
account_no str

API 호출 시 사용할 계좌 번호

required
symbols list[str] | None

종목 code들

None
start_day str

조회 시작 일자 (YYYYMMDD)

''
ohlc str

사용할 data 의 column 이름

''
top int

Experiment 과정에서 사용할 각 전략별 수

1
methods dict[str, list[list[Any]]] | None

사용할 전략들의 function명 및 parameters

None
report bool

Experiment 결과 출력 여부

False
token str | None

Bot의 token (xoxb- prefix로 시작하면 SlackBot, 아니면 DiscordBot)

None
channel str | None

Bot이 전송할 channel

None
name str | None

Bot의 표시될 이름

None
icon_emoji str | None

Bot의 표시될 사진 (emoji)

None
mp_num int

병렬 처리에 사용될 process의 수 (0: 직렬 처리)

0
analysis bool

각 전략의 보고서 전송 여부

False
kor bool

국내 여부

True
path str

secret.key 혹은 token.dat 이 포함된 경로

'./'

Attributes:

Name Type Description
exps

각 전략에 따른 parameter 분포

Examples:

>>> qbki = zz.quant.QuantBotKI("00000000-00", token=token, channel=channel)

Methods:

Name Description
sell

매도 signals 탐색

Source code in zerohertzLib/quant/koreainvestment.py
def __init__(
    self,
    account_no: str,
    symbols: list[str] | None = None,
    start_day: str = "",
    ohlc: str = "",
    top: int = 1,
    methods: dict[str, list[list[Any]]] | None = None,
    report: bool = False,
    token: str | None = None,
    channel: str | None = None,
    name: str | None = None,
    icon_emoji: str | None = None,
    mp_num: int = 0,
    analysis: bool = False,
    kor: bool = True,
    path: str = "./",
) -> None:
    Balance.__init__(self, account_no, path, kor)
    if symbols is None:
        symbols = []
    QuantBot.__init__(
        self,
        symbols=symbols,
        start_day=start_day,
        ohlc=ohlc,
        top=top,
        methods=methods,
        report=report,
        token=token,
        channel=channel,
        name=name,
        icon_emoji=icon_emoji,
        mp_num=mp_num,
        analysis=analysis,
        kor=kor,
    )
    self.symbols_bought = self.bought_symbols()

symbols_bought instance-attribute

symbols_bought = bought_symbols()

_get_data

_get_data(symbol: str) -> tuple[str, DataFrame]
Source code in zerohertzLib/quant/koreainvestment.py
def _get_data(self, symbol: str) -> tuple[str, pd.DataFrame]:
    response = self.get_ohlcv(symbol, start_day=self.start_day, kor=self.kor)
    title, data = self.response2ohlcv(response)
    return title, data

sell

sell() -> None

매도 signals 탐색

한국투자증권의 잔고와 주식 보유 상황을 image로 변환하여 slack으로 전송 및 보유 중인 주식에 대해 매도 signals 탐색

Source code in zerohertzLib/quant/koreainvestment.py
def sell(self) -> None:
    """매도 signals 탐색

    한국투자증권의 잔고와 주식 보유 상황을 image로 변환하여 slack으로 전송 및 보유 중인 주식에 대해 매도 signals 탐색
    """
    path_balance, path_portfolio = self.table(), self.pie()
    if path_balance is None:
        self.bot.message("Balance: NULL", True)
        return None
    response = self.bot.message("> :bank: Balance")
    thread_id = self.bot.get_thread_id(response)
    self.bot.file(path_balance, thread_id=thread_id)
    self.bot.file(path_portfolio, thread_id=thread_id)
    self._inference(self.symbols_bought, "Sell")
    return None

bollinger_bands

bollinger_bands(data: DataFrame, window: int = 60, num_std_dev: float = 2.5, ohlc: str = '') -> DataFrame

Bollinger band 기반 매수 및 매도 signal을 생성하는 function

Note

Bollinger Band

  • Definition: 1980년대에 John Bollinger에 의해 개발된 가격 변동성 및 추세 파악 기법
  • Mean
    • Middle Band: 기본적인 중간 가격 추세
    • Upper Band: Middle band에서 일정 표준편차 위에 위치 (과도한 상승 추세나 고평가 상태)
    • Lower Band: Middle band에서 일정 표준편차 아래에 위치 (과도한 하락 추세나 저평가 상태)
  • 매수 신호: 주가가 하단 Bollinger band (lower_band) 아래로 감소할 때 생성 (과매도 상태)
  • 매도 신호: 주가가 상단 Bollinger band (upper_band) 위로 상승할 때 생성 (과매수 상태)

Parameters:

Name Type Description Default
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
window int

이동 평균을 계산하기 위한 widnow 크기

60
num_std_dev float

표준편차의 배수

2.5
ohlc str

이동 평균을 계산할 때 사용할 data 의 column 이름

''

Returns:

Type Description
DataFrame

각 날짜에 대한 signal ("signals") 정보

Examples:

>>> zz.quant.bollinger_bands(data)
              middle_band     upper_band    lower_band  signals
Date
2022-01-03            NaN            NaN           NaN        0
...                   ...            ...           ...      ...
2023-12-19  102771.666667  111527.577705  94015.755629        0
[485 rows x 4 columns]

Bollinger bands example

Source code in zerohertzLib/quant/methods.py
def bollinger_bands(
    data: pd.DataFrame,
    window: int = 60,
    num_std_dev: float = 2.5,
    ohlc: str = "",
) -> pd.DataFrame:
    """Bollinger band 기반 매수 및 매도 signal을 생성하는 function

    Note:
        Bollinger Band

        - Definition: 1980년대에 John Bollinger에 의해 개발된 가격 변동성 및 추세 파악 기법
        - Mean
            - Middle Band: 기본적인 중간 가격 추세
            - Upper Band: Middle band에서 일정 표준편차 위에 위치 (과도한 상승 추세나 고평가 상태)
            - Lower Band: Middle band에서 일정 표준편차 아래에 위치 (과도한 하락 추세나 저평가 상태)

    - 매수 신호: 주가가 하단 Bollinger band (`lower_band`) 아래로 감소할 때 생성 (과매도 상태)
    - 매도 신호: 주가가 상단 Bollinger band (`upper_band`) 위로 상승할 때 생성 (과매수 상태)

    Args:
        data: OHLCV (Open, High, Low, Close, Volume) data
        window: 이동 평균을 계산하기 위한 widnow 크기
        num_std_dev: 표준편차의 배수
        ohlc: 이동 평균을 계산할 때 사용할 `data` 의 column 이름

    Returns:
        각 날짜에 대한 signal (`"signals"`) 정보

    Examples:
        >>> zz.quant.bollinger_bands(data)
                      middle_band     upper_band    lower_band  signals
        Date
        2022-01-03            NaN            NaN           NaN        0
        ...                   ...            ...           ...      ...
        2023-12-19  102771.666667  111527.577705  94015.755629        0
        [485 rows x 4 columns]

        ![Bollinger bands example](../../../assets/quant/bollinger_bands.png){ width="500" }
    """
    signals = _bollinger_bands(data, window, num_std_dev, ohlc)
    signals["signals"] = 0
    if ohlc == "":
        signals["signals"] = np.where(
            data.iloc[:, :4].mean(1) < signals["lower_band"], 1, signals["signals"]
        )
        signals["signals"] = np.where(
            data.iloc[:, :4].mean(1) > signals["upper_band"], -1, signals["signals"]
        )
    else:
        signals["signals"] = np.where(
            data[ohlc] < signals["lower_band"], 1, signals["signals"]
        )
        signals["signals"] = np.where(
            data[ohlc] > signals["upper_band"], -1, signals["signals"]
        )
    return signals

experiments

experiments(title: str, data: DataFrame, method: Callable[[Any], DataFrame], exps: list[list[Any]], ohlc: str = '', vis: bool = False, dpi: int = 100, report: bool = True) -> dict[str, list[Any]]

Full factorial design 기반의 backtest 수행 function

Parameters:

Name Type Description Default
title str

종목 이름

required
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
method Callable[[Any], DataFrame]

Full factorial을 수행할 전략 function

required
exps list[list[Any]]

전략 function에 입력될 변수들의 범위

required
ohlc str

사용할 data 의 column 이름

''
vis bool

Candle chart 시각화 여부

False
dpi int

Graph 저장 시 DPI (Dots Per Inch)

100
report bool

Experiment 결과 출력 여부

True

Returns:

Type Description
dict[str, list[Any]]

손실 거래 비율에 따른 수익률, signals, parameters

Examples:

>>> exps = [[10, 20, 25, 30], [70, 75, 80, 85, 90], [14, 21, 31]]
>>> results = zz.quant.experiments(title, data, zz.quant.rsi, exps)
+----------------------+------------+------------+
| EXP                  |     PROFIT | LOSS RATIO |
+----------------------+------------+------------+
| 10-70-14             |      5.65% |      0.00% |
| 10-70-21             |   -100.00% |    100.00% |
| ...                  |        ... |        ... |
| 30-90-21             |     21.25% |      0.00% |
| 30-90-31             |     20.98% |      0.00% |
| ==================== | ========== | ========== |
| WORST (10-70-21)     |   -100.00% |    100.00% |
| BEST (25-90-31)      |     21.53% |      0.00% |
| OPTIM (25-75-31)     |     21.53% |      0.00% |
+----------------------+------------+------------+
>>> results.keys()
dict_keys(['profits', 'signals', 'exps_str', 'exps_tup'])
>>> results["profits"]
[21.530811750223275, ...]
>>> results["signals"][0].columns
Index(['RSI', 'signals', 'logic'], dtype='object')
>>> results["exps_str"]
['25-75-31', ...]
>>> results["exps_tup"]
[(25, 75, 31), ...]
Source code in zerohertzLib/quant/backtest.py
def experiments(
    title: str,
    data: pd.DataFrame,
    method: Callable[[Any], pd.DataFrame],
    exps: list[list[Any]],
    ohlc: str = "",
    vis: bool = False,
    dpi: int = 100,
    report: bool = True,
) -> dict[str, list[Any]]:
    """Full factorial design 기반의 backtest 수행 function

    Args:
        title: 종목 이름
        data: OHLCV (Open, High, Low, Close, Volume) data
        method: Full factorial을 수행할 전략 function
        exps: 전략 function에 입력될 변수들의 범위
        ohlc: 사용할 `data` 의 column 이름
        vis: Candle chart 시각화 여부
        dpi: Graph 저장 시 DPI (Dots Per Inch)
        report: Experiment 결과 출력 여부

    Returns:
        손실 거래 비율에 따른 수익률, `signals`, parameters

    Examples:
        >>> exps = [[10, 20, 25, 30], [70, 75, 80, 85, 90], [14, 21, 31]]
        >>> results = zz.quant.experiments(title, data, zz.quant.rsi, exps)
        +----------------------+------------+------------+
        | EXP                  |     PROFIT | LOSS RATIO |
        +----------------------+------------+------------+
        | 10-70-14             |      5.65% |      0.00% |
        | 10-70-21             |   -100.00% |    100.00% |
        | ...                  |        ... |        ... |
        | 30-90-21             |     21.25% |      0.00% |
        | 30-90-31             |     20.98% |      0.00% |
        | ==================== | ========== | ========== |
        | WORST (10-70-21)     |   -100.00% |    100.00% |
        | BEST (25-90-31)      |     21.53% |      0.00% |
        | OPTIM (25-75-31)     |     21.53% |      0.00% |
        +----------------------+------------+------------+
        >>> results.keys()
        dict_keys(['profits', 'signals', 'exps_str', 'exps_tup'])
        >>> results["profits"]
        [21.530811750223275, ...]
        >>> results["signals"][0].columns
        Index(['RSI', 'signals', 'logic'], dtype='object')
        >>> results["exps_str"]
        ['25-75-31', ...]
        >>> results["exps_tup"]
        [(25, 75, 31), ...]
    """
    results = []
    if report:
        reports = PrettyTable(["EXP", "PROFIT", "LOSS RATIO"])
        reports.align["EXP"] = "l"
        reports.align["PROFIT"] = "r"
        reports.align["LOSS RATIO"] = "r"
    for exp in product(*exps):
        signals = method(data, *exp, ohlc=ohlc)
        backtest_results = backtest(data, signals, ohlc=ohlc)
        exp_str = "-".join(list(map(str, exp)))
        profit_total = backtest_results["profit"]
        loss_total = backtest_results["loss"]
        weighted_profit_total = backtest_results["weighted_profit"]
        if report:
            reports.add_row([exp_str, f"{profit_total:.2f}%", f"{loss_total:.2f}%"])
        if profit_total == 0:
            continue
        if vis:
            candle(data[-500:], f"{title}-{exp_str}", signals=signals, dpi=dpi)
        results.append(
            (
                profit_total,
                weighted_profit_total,
                {
                    "exps_tup": exp,
                    "exp_str": exp_str,
                    "signals": signals,
                    "profit_total": profit_total,
                    "loss_total": loss_total,
                },
            )
        )
    if report:
        reports.add_row(["=" * 20, "=" * 10, "=" * 10])
        results.sort(key=lambda x: x[0])
        reports.add_row(
            [
                f"WORST ({results[0][2]['exp_str']})",
                f"{results[0][2]['profit_total']:.2f}%",
                f"{results[0][2]['loss_total']:.2f}%",
            ]
        )
        reports.add_row(
            [
                f"BEST ({results[-1][2]['exp_str']})",
                f"{results[-1][2]['profit_total']:.2f}%",
                f"{results[-1][2]['loss_total']:.2f}%",
            ]
        )
    results.sort(key=lambda x: x[1], reverse=True)
    if report:
        reports.add_row(
            [
                f"OPTIM ({results[0][2]['exp_str']})",
                f"{results[0][2]['profit_total']:.2f}%",
                f"{results[0][2]['loss_total']:.2f}%",
            ]
        )
        print(reports)
    profits, signals, exps_str, exps_tup = [], [], [], []
    for result in results:
        profits.append(result[2]["profit_total"])
        signals.append(result[2]["signals"])
        exps_str.append(result[2]["exp_str"])
        exps_tup.append(result[2]["exps_tup"])
    return {
        "profits": profits,
        "signals": signals,
        "exps_str": exps_str,
        "exps_tup": exps_tup,
    }

macd

macd(data: DataFrame, n_fast: int = 12, n_signal: int = 9, ohlc: str = '') -> DataFrame

MACD 기반 매수 및 매도 signal을 생성하는 function

Note

MACD (Moving Average Convergence Divergence)

  • Definition: 빠른 EMA (n_fast)와 느린 EMA (n_slow)의 차이
    • n_slow = n_fast * 2
  • Mean
    • EMA: 최근 가격에 더 많은 가중치를 두어 계산하는 이동 평균
    • Signal line: MACD의 추세를 평활화하여 추세의 방향과 강도를 파악
  • 매수 신호: MACD가 signal line 위로 상승할 때 생성 (상승 추세)
  • 매도 신호: MACD가 signal line 아래로 하락할 때 생성 (하락 추세)

Parameters:

Name Type Description Default
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
n_fast int

빠른 EMA 계산을 위한 기간

12
n_signal int

MACD signal line 계산을 위한 기간

9
ohlc str

Momentum을 계산할 때 사용할 data 의 column 이름

''

Returns:

Type Description
DataFrame

각 날짜에 대한 signal ("signals") 정보

Examples:

>>> zz.quant.macd(data)
                   MACD  signals
Date
2022-01-03     0.000000        0
...                 ...      ...
2023-12-19 -1950.006134        0
[485 rows x 2 columns]

MACD example

Source code in zerohertzLib/quant/methods.py
def macd(
    data: pd.DataFrame,
    n_fast: int = 12,
    n_signal: int = 9,
    ohlc: str = "",
) -> pd.DataFrame:
    """MACD 기반 매수 및 매도 signal을 생성하는 function

    Note:
        MACD (Moving Average Convergence Divergence)

        - Definition: 빠른 EMA (`n_fast`)와 느린 EMA (`n_slow`)의 차이
            - `n_slow = n_fast * 2`
        - Mean
            - EMA: 최근 가격에 더 많은 가중치를 두어 계산하는 이동 평균
            - Signal line: MACD의 추세를 평활화하여 추세의 방향과 강도를 파악

    - 매수 신호: MACD가 signal line 위로 상승할 때 생성 (상승 추세)
    - 매도 신호: MACD가 signal line 아래로 하락할 때 생성 (하락 추세)

    Args:
        data: OHLCV (Open, High, Low, Close, Volume) data
        n_fast: 빠른 EMA 계산을 위한 기간
        n_signal: MACD signal line 계산을 위한 기간
        ohlc: Momentum을 계산할 때 사용할 `data` 의 column 이름

    Returns:
        각 날짜에 대한 signal (`"signals"`) 정보

    Examples:
        >>> zz.quant.macd(data)
                           MACD  signals
        Date
        2022-01-03     0.000000        0
        ...                 ...      ...
        2023-12-19 -1950.006134        0
        [485 rows x 2 columns]

        ![MACD example](../../../assets/quant/macd.png){ width="500" }
    """
    n_slow = n_fast * 2
    signals = pd.DataFrame(index=data.index)
    if ohlc == "":
        data_ = data.iloc[:, :4].mean(1)
    else:
        data_ = data[ohlc]
    ema_fast = data_.ewm(span=n_fast, adjust=False).mean()
    ema_slow = data_.ewm(span=n_slow, adjust=False).mean()
    signals["MACD"] = 0
    signals["MACD"] = ema_fast - ema_slow
    signal = signals["MACD"].ewm(span=n_signal, adjust=False).mean()
    macd_diff = signals["MACD"] - signal
    signals["signals"] = 0
    signals.loc[macd_diff > 0, "signals"] = 1
    signals.loc[macd_diff < 0, "signals"] = -1
    buy_signals = (signals["signals"] > 0) & (signals["signals"].shift(1) < 0)
    sell_signals = (signals["signals"] < 0) & (signals["signals"].shift(1) > 0)
    signals["signals"] = 0
    signals.loc[buy_signals, "signals"] = 1
    signals.loc[sell_signals, "signals"] = -1
    return signals

momentum

momentum(data: DataFrame, window: int = 5, ohlc: str = '') -> DataFrame

Momentum 기반 매수 및 매도 signal을 생성하는 function

Note

Momentum

  • Definition: data[ohlc].diff(window) 를 통해 window 일 전 가격 사이의 차이 계산
  • Mean
    • 양의 momentum: 가격 상승
    • 음의 momentum: 가격 하락
    • Momentum의 크기: 추세의 강도
  • 매수 신호: 주가 momentum이 양수일 때 생성 (상승 추세)
  • 매도 신호: 주가 momentum이 음수일 때 생성 (하락 추세)

Parameters:

Name Type Description Default
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
window int

Momentum을 계산하기 위한 widnow 크기

5
ohlc str

Momentum을 계산할 때 사용할 data 의 column 이름

''

Returns:

Type Description
DataFrame

각 날짜에 대한 signal ("signals") 정보

Examples:

>>> zz.quant.momentum(data)
            momentum  signals
Date
2022-01-03       NaN        0
...              ...      ...
2023-12-19     550.0        0
[485 rows x 2 columns]

Momentum example

Source code in zerohertzLib/quant/methods.py
def momentum(
    data: pd.DataFrame,
    window: int = 5,
    ohlc: str = "",
) -> pd.DataFrame:
    """Momentum 기반 매수 및 매도 signal을 생성하는 function

    Note:
        Momentum

        - Definition: `data[ohlc].diff(window)` 를 통해 `window` 일 전 가격 사이의 차이 계산
        - Mean
            - 양의 momentum: 가격 상승
            - 음의 momentum: 가격 하락
            - Momentum의 크기: 추세의 강도

    - 매수 신호: 주가 momentum이 양수일 때 생성 (상승 추세)
    - 매도 신호: 주가 momentum이 음수일 때 생성 (하락 추세)

    Args:
        data: OHLCV (Open, High, Low, Close, Volume) data
        window: Momentum을 계산하기 위한 widnow 크기
        ohlc: Momentum을 계산할 때 사용할 `data` 의 column 이름

    Returns:
        각 날짜에 대한 signal (`"signals"`) 정보

    Examples:
        >>> zz.quant.momentum(data)
                    momentum  signals
        Date
        2022-01-03       NaN        0
        ...              ...      ...
        2023-12-19     550.0        0
        [485 rows x 2 columns]

        ![Momentum example](../../../assets/quant/momentum.png){ width="500" }
    """
    signals = pd.DataFrame(index=data.index)
    if ohlc == "":
        signals["momentum"] = data.iloc[:, :4].mean(1).diff(window)
    else:
        signals["momentum"] = data[ohlc].diff(window)
    buy_signals = (signals["momentum"] > 0) & (signals["momentum"].shift(1) < 0)
    sell_signals = (signals["momentum"] < 0) & (signals["momentum"].shift(1) > 0)
    signals["signals"] = 0
    signals.loc[buy_signals, "signals"] = 1
    signals.loc[sell_signals, "signals"] = -1
    return signals

moving_average

moving_average(data: DataFrame, short_window: int = 40, long_window: int = 80, threshold: float = 0.0, ohlc: str = '') -> DataFrame

단기 및 장기 이동 평균 기반 매수 및 매도 signal을 생성하는 function

Note

Moving Average

  • Definition: 일정 기간 동안 평균화하여 추세 파악 및 noise 감소
  • 매수 신호: 단기 이동 평균이 장기 이동 평균보다 높을 때 생성 (상승 추세)
  • 매도 신호: 단기 이동 평균이 장기 이동 평균보다 낮을 때 생성 (하락 추세)

Parameters:

Name Type Description Default
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
short_window int

단기 이동 평균을 계산하기 위한 window 크기

40
long_window int

장기 이동 평균을 계산하기 위한 widnow 크기

80
threshold float

신호를 발생 시킬 임계값

0.0
ohlc str

이동 평균을 계산할 때 사용할 data 의 column 이름

''

Returns:

Type Description
DataFrame

각 날짜에 대한 signal ("signals") 정보

Examples:

>>> zz.quant.moving_average(data)
            short_mavg    long_mavg  signals
Date
2022-01-03  139375.000  139375.0000        0
...                ...          ...      ...
2023-12-19  102450.000  102337.1875        0
[485 rows x 3 columns]

Moving average example

Source code in zerohertzLib/quant/methods.py
def moving_average(
    data: pd.DataFrame,
    short_window: int = 40,
    long_window: int = 80,
    threshold: float = 0.0,
    ohlc: str = "",
) -> pd.DataFrame:
    """단기 및 장기 이동 평균 기반 매수 및 매도 signal을 생성하는 function

    Note:
        Moving Average

        - Definition: 일정 기간 동안 평균화하여 추세 파악 및 noise 감소

    - 매수 신호: 단기 이동 평균이 장기 이동 평균보다 높을 때 생성 (상승 추세)
    - 매도 신호: 단기 이동 평균이 장기 이동 평균보다 낮을 때 생성 (하락 추세)

    Args:
        data: OHLCV (Open, High, Low, Close, Volume) data
        short_window: 단기 이동 평균을 계산하기 위한 window 크기
        long_window: 장기 이동 평균을 계산하기 위한 widnow 크기
        threshold: 신호를 발생 시킬 임계값
        ohlc: 이동 평균을 계산할 때 사용할 `data` 의 column 이름

    Returns:
        각 날짜에 대한 signal (`"signals"`) 정보

    Examples:
        >>> zz.quant.moving_average(data)
                    short_mavg    long_mavg  signals
        Date
        2022-01-03  139375.000  139375.0000        0
        ...                ...          ...      ...
        2023-12-19  102450.000  102337.1875        0
        [485 rows x 3 columns]

        ![Moving average example](../../../assets/quant/moving_average.png){ width="500" }
    """
    signals = pd.DataFrame(index=data.index)
    if ohlc == "":
        signals["short_mavg"] = (
            data.iloc[:, :4].mean(1).rolling(window=short_window, min_periods=1).mean()
        )
        signals["long_mavg"] = (
            data.iloc[:, :4].mean(1).rolling(window=long_window, min_periods=1).mean()
        )
    else:
        signals["short_mavg"] = (
            data[ohlc].rolling(window=short_window, min_periods=1).mean()
        )
        signals["long_mavg"] = (
            data[ohlc].rolling(window=long_window, min_periods=1).mean()
        )
    feature = signals["short_mavg"] - signals["long_mavg"]
    threshold = feature.abs().mean() * threshold
    signals["signals"] = 0
    signals.loc[feature > threshold, "signals"] = 1
    signals.loc[feature < -threshold, "signals"] = -1
    buy_signals = (signals["signals"] == 1) & (signals["signals"].shift(1) < 1)
    sell_signals = (signals["signals"] == -1) & (signals["signals"].shift(1) > -1)
    signals["signals"] = 0
    signals.loc[buy_signals, "signals"] = 1
    signals.loc[sell_signals, "signals"] = -1
    return signals

rsi

rsi(data: DataFrame, lower_bound: int = 20, upper_bound: int = 80, window: int = 30, ohlc: str = '') -> DataFrame

RSI 기반 매수 및 매도 signal을 생성하는 function

Note

RSI (Relative Strength Index)

  • Definition

    • \(RS = \frac{Average\ Gain}{Average\ Loss}\)
    • \(RSI = 100 - \frac{100}{1+RS}\)
  • Mean

    • -10: 과매수 상태에서 중립 상태로 변화 (매도 position 청산)
    • 0-1: 과매수 상태로의 진입 (새로운 매도 position)
    • +10: 과매도 상태에서 중립 상태로 변화 (매수 position 청산)
    • 0+1: 과매도 상태로의 진입 (새로운 매수 position)
  • 매수 신호: RSI 값이 lower_bound 보다 낮을 때 생성 (과매도 상태)
  • 매도 신호: RSI 값이 upper_bound 보다 높을 때 생성 (과매수 상태)

Parameters:

Name Type Description Default
data DataFrame

OHLCV (Open, High, Low, Close, Volume) data

required
lower_bound int

RSI 과매도 기준

20
upper_bound int

RSI 과매수 기준

80
window int

이동 평균을 계산하기 위한 widnow 크기

30
ohlc str

RSI를 계산할 때 사용할 data 의 column 이름

''

Returns:

Type Description
DataFrame

각 날짜에 대한 signal ("signals") 정보

Examples:

>>> zz.quant.rsi(data)
                  RSI  signals
Date
2022-01-03        NaN        0
...               ...      ...
2023-12-19  35.671343        0
[485 rows x 2 columns]

RSI example

Source code in zerohertzLib/quant/methods.py
def rsi(
    data: pd.DataFrame,
    lower_bound: int = 20,
    upper_bound: int = 80,
    window: int = 30,
    ohlc: str = "",
) -> pd.DataFrame:
    r"""RSI 기반 매수 및 매도 signal을 생성하는 function

    Note:
        RSI (Relative Strength Index)

        - Definition
            - $RS = \frac{Average\ Gain}{Average\ Loss}$
            - $RSI = 100 - \frac{100}{1+RS}$

        - Mean
            - `-1` → `0`: 과매수 상태에서 중립 상태로 변화 (매도 position 청산)
            - `0` → `-1`: 과매수 상태로의 진입 (새로운 매도 position)
            - `+1` → `0`: 과매도 상태에서 중립 상태로 변화 (매수 position 청산)
            - `0` → `+1`: 과매도 상태로의 진입 (새로운 매수 position)

    - 매수 신호: RSI 값이 `lower_bound` 보다 낮을 때 생성 (과매도 상태)
    - 매도 신호: RSI 값이 `upper_bound` 보다 높을 때 생성 (과매수 상태)

    Args:
        data: OHLCV (Open, High, Low, Close, Volume) data
        lower_bound: RSI 과매도 기준
        upper_bound: RSI 과매수 기준
        window: 이동 평균을 계산하기 위한 widnow 크기
        ohlc: RSI를 계산할 때 사용할 `data` 의 column 이름

    Returns:
        각 날짜에 대한 signal (`"signals"`) 정보

    Examples:
        >>> zz.quant.rsi(data)
                          RSI  signals
        Date
        2022-01-03        NaN        0
        ...               ...      ...
        2023-12-19  35.671343        0
        [485 rows x 2 columns]

        ![RSI example](../../../assets/quant/rsi.png){ width="500" }
    """
    signals = pd.DataFrame(index=data.index)
    if ohlc == "":
        signals["RSI"] = _rsi(data.iloc[:, :4].mean(1), window)
    else:
        signals["RSI"] = _rsi(data[ohlc], window)
    signals["signals"] = 0
    signals["signals"] = np.where(
        signals["RSI"] > upper_bound, -1, np.where(signals["RSI"] < lower_bound, 1, 0)
    )
    return signals