kquant API document (0.3.6)

backtest_stock_port_daily

주식 포트폴리오의 일간기준 백테스트 수행

  • 일간 종가정보 기반
  • 하루에 한 번, 종가 기준으로 체결
  • 동일 종목의 매수와 매도를 당일에 같이 하는 것은 불가능
  • 실현손익은 선입선출(FIFO: First-In-First-Out)방식으로 계산
backtest_stock_port_daily(
    order: 'Union[pd.DataFrame, Callable[[dt.datetime, dict[str, pd.DataFrame], dict[str, pd.DataFrame], logging.Logger], list[tuple[str, int]]]]',
    start_date: 'DATE_IN'=None,
    end_date: 'DATE_IN'=None,
    init_cash: 'int'=0,
    close_on_end: 'bool'=False,
    allow_loan: 'bool'=False,
    broker_fee_percent: 'float'=0.0,
    exchange_fee_percent: 'float'=0.0,
    trade_tax_percent: 'float'=0.0,
    slippage_tick: 'int'=0,
    return_position: 'bool'=False,
    use_notadj: 'bool'=False,
    logger: 'Optional[logging.Logger]'=None,
    logname: 'Optional[str]'=None,
    logpath: 'Optional[str]'=None,
    loglevel: 'Optional[int]'=None,
    return_logger: 'Optional[bool]'=False,
) -> Union[dict[str, pd.DataFrame], tuple[dict[str, pd.DataFrame], dict[str, pd.DataFrame]], tuple[dict[str, pd.DataFrame], logging.Logger], tuple[dict[str, pd.DataFrame], dict[str, pd.DataFrame], logging.Logger]]
  • order (Union[pd.DataFrame, Callable]): 주식 주문 정보를 포함하는 데이터프레임 혹은 함수.

    order 인수가 데이터프레임인 경우 다음과 같은 열을 포함하고 있어야 합니다.

    • “DATE”: 날짜 열. daily_stock 함수로 출력된 데이터프레임과 마찬가지로 datetime64[ns] 형식이거나 pd.to_datetime 함수로 변환가능한 형식의 문자열
    • “SYMBOL”: 주식 종목 단축코드 문자열
    • “ORDER”: 주문수량 열 (양수는 매수, 음수는 매도)

    order 인수가 함수인 경우 다음과 같은 형식이어야 합니다.

    import pandas as pd
    import datetime as dt
    import logging
    
    def trade_func(
        date: dt.date,
        dict_df_result: dict[str,pd.DataFrame],
        dict_df_position: dict[str,pd.DataFrame],
        logger: logging.Logger,
    ) -> list[tuple[str,int]]:
        symbols_and_orders = [
    ("주문주식단축코드1",주문수량1),
    ("주문주식단축코드2",주문수량2),
    ...
    ]
        return symbols_and_orders
  • start_date (DATE_IN): 백테스트 시작 날짜 문자열. None이면 order 데이터프레임 날짜 중 가장 첫번째 날짜를 이용.

  • end_date (DATE_IN): 백테스트 종료 날짜 문자열 None이면 order 데이터프레임 날짜 중 가장 마지막 날짜를 이용.

  • init_cash (int): 초기 보유현금. 디폴트 0원

  • close_on_end (bool): True면 백테스트 마지막날에는 매매정보를 무시하고 항상 보유잔고 정리. 디폴트 False

  • allow_loan (bool): 융자가능여부 즉, 음수인 보유현금 허용 설정값. 디폴트 False

  • broker_fee_percent (float): 증권사 수수료(%). 디폴트 0

  • exchange_fee_percent (float): 유관기관 수수료(%). 디폴트 0

  • trade_tax_percent (float): 매도시 주식양도세(%). 디폴트 0

  • slippage_tick (int): 거래시 발생하는 슬리피지(slippage)틱(호가가격단위), 디폴트 0

  • return_position (bool): 포지션 정보 데이터프레임을 반환할지의 여부. 디폴트 False

  • use_notadj (bool): 수정주가가 아닌 원주가 사용. 디폴트 False

  • logger (Optional[logging.Logger]): 로거 객체. None이면 자동생성

  • logname (Optional[str]): 로거 이름. 로거 객체 logger가 None이면 이 이름으로 생성

  • logpath (Optional[str]): 로그파일 경로 문자열. None이 아니면 로그파일 생성

  • loglevel (Optional[int]): 로그레벨. 디폴트 logging.INFO :param Optional[bool] return logger: True이면 로거 객체를 추가적으로 반환

  • 반환값 (pd.DataFrame): 매매 결과를 포함하는 데이터프레임.

    매매 결과 데이터프레임은 다음과 같은 열을 포함

    • DATE: 날짜
    • SYMBOL: 종목단축코드
    • PRICE: 주식 평가를 위한 당일 종가
    • ORDER: 주문수량, 양수이면 매수, 음수이면 매도
    • QTY: 실제 매매수량, 양수이면 매수, 음수이면 매도
    • TRADE_PRICE: 체결 가격, 매매일 종가에서 슬리피지(slippage)만큼 손실을 보면서 체결
    • FEE: 증권사 및 유관기관 수수료 금액
    • TRADE_TAX: 매도시 발생하는 증권거래세 금액
    • SLIPPAGE: 주식의 현재 가격과 실제 매매 가격의 차이에 의해 발생하는 슬리피지(slippage)
    • CASHFLOW: 현금흐름, 양수이면 매도시 발생하는 현금유입, 음수이면 매수시 발생하는 현금유출
    • CASH: 당일의 보유 현금 금액
    • POSITION: 당일의 보유 주식 수량
    • AVG_PRICE: 당일기준 보유 주식의 역사적 평균가격, 선입선출 방식으로 계산
    • HIST_VALUE: 보유 주식의 매수 금액, 선입선출 방식으로 계산
    • STOCK_VALUE: 당일의 주식 평가액
    • TOTAL_VALUE: 당일의 주식 평가액과 현금 보유액의 합계
    • REAL_PROFIT: 주식 매도시 발생하는 실현손익, 음수이면 손실
    • UNREAL_PROFIT: 보유 주식에 대한 평가손익, 음수이면 손실
    • PROFIT: 총손익, 누적 실현손익과 최종 평가손익의 합, 음수이면 손실

    만약 return_position = True 인 경우 다음과 같은 포지션 정보 데이터프레임 추가

    • DATE: 체결 날짜
    • SYMBOL: 종목단축코드
    • QTY: 현재 보유 수량
    • TRADE_PRICE: 체결 가격
    • HIST_VALUE: 현재 보유 수량의 역사적 가치
    • FEE: 현재 보유 수량에 해당하는 수수료 합계
    • NOT_DELETE: 내부적으로 사용하는 필드
예제 코드
import pandas as pd
df_order = pd.DataFrame({
    "DATE": "20230102",
    "SYMBOL": ["005930","000660"],
    "ORDER": [10,10],
})
df_order
DATE SYMBOL ORDER
0 20230102 005930 10
1 20230102 000660 10
import kquant as kq
dict_df_result = kq.backtest_stock_port_daily(
    df_order,
    init_cash=100_000_000,
)
[2023-01-02] 종목: 005930, 주문전 보유수량:      0 주문수량:     10, 매매수량:     10, 주문후 보유수량:     10
[2023-01-02] 종목: 000660, 주문전 보유수량:      0 주문수량:     10, 매매수량:     10, 주문후 보유수량:     10
symbols = list(dict_df_result.keys())
symbols
['005930', '000660', 'TOTAL']
df_result_005930 = dict_df_result["005930"]
df_result_005930
DATE SYMBOL PRICE ORDER QTY TRADE_PRICE POSITION AVG_PRICE FEE TRADE_TAX SLIPPAGE CASHFLOW CASH HIST_VALUE STOCK_VALUE TOTAL_VALUE REAL_PROFIT UNREAL_PROFIT PROFIT HIGHWATERMARK DRAWDOWN
0 2023-01-02 005930 55,500 10 10 55,500 10 55,500.0000 0 0 0 -555,000 -555,000 555,000 555,000 0 0 0 0 0 0
df_result_total = dict_df_result["TOTAL"]
df_result_total
DATE SYMBOL PRICE ORDER QTY TRADE_PRICE POSITION AVG_PRICE FEE TRADE_TAX SLIPPAGE CASHFLOW CASH HIST_VALUE STOCK_VALUE TOTAL_VALUE REAL_PROFIT UNREAL_PROFIT PROFIT HIGHWATERMARK DRAWDOWN
0 2023-01-02 TOTAL 0 0 0 0 20 0.0000 0 0 0 -1,312,000 98,688,000 1,312,000 1,312,000 100,000,000 0 0 0 100,000,000 0
from dateutil.parser import parse
import datetime as dt
import logging
def trade_func(
    date: dt.date,
    dict_df_result: dict[str,pd.DataFrame],
    dict_df_position: dict[str,pd.DataFrame],
    logger: logging.Logger,
) -> list[tuple[str,int]] | None:
    if date == parse("20230102").date():
        return [("005930",10),("000660",10)]
    else:
        return []
dict_df_result = kq.backtest_stock_port_daily(
    trade_func,
    "20230102",
    "20230106",
    init_cash=100_000_000,
)
[2023-01-02] 종목: 005930, 주문전 보유수량:      0 주문수량:     10, 매매수량:     10, 주문후 보유수량:     10
[2023-01-02] 종목: 000660, 주문전 보유수량:      0 주문수량:     10, 매매수량:     10, 주문후 보유수량:     10
[2023-01-03] 종목: 005930, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-04] 종목: 005930, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-05] 종목: 005930, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-06] 종목: 005930, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-03] 종목: 000660, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-04] 종목: 000660, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-05] 종목: 000660, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
[2023-01-06] 종목: 000660, 주문전 보유수량:     10 주문수량:      0, 매매수량:      0, 주문후 보유수량:     10
df_result_005930 = dict_df_result["005930"]
df_result_005930
DATE SYMBOL PRICE ORDER QTY TRADE_PRICE POSITION AVG_PRICE FEE TRADE_TAX SLIPPAGE CASHFLOW CASH HIST_VALUE STOCK_VALUE TOTAL_VALUE REAL_PROFIT UNREAL_PROFIT PROFIT HIGHWATERMARK DRAWDOWN
0 2023-01-02 005930 55,500 10 10 55,500 10 55,500.0000 0 0 0 -555,000 -555,000 555,000 555,000 0 0 0 0 0 0
1 2023-01-03 005930 55,400 0 0 0 10 55,500.0000 0 0 0 0 -555,000 555,000 554,000 -1,000 0 -1,000 -1,000 0 1,000
2 2023-01-04 005930 57,800 0 0 0 10 55,500.0000 0 0 0 0 -555,000 555,000 578,000 23,000 0 23,000 23,000 23,000 0
3 2023-01-05 005930 58,200 0 0 0 10 55,500.0000 0 0 0 0 -555,000 555,000 582,000 27,000 0 27,000 27,000 27,000 0
4 2023-01-06 005930 59,000 0 0 0 10 55,500.0000 0 0 0 0 -555,000 555,000 590,000 35,000 0 35,000 35,000 35,000 0
df_result_total = dict_df_result["TOTAL"]
df_result_total
DATE SYMBOL PRICE ORDER QTY TRADE_PRICE POSITION AVG_PRICE FEE TRADE_TAX SLIPPAGE CASHFLOW CASH HIST_VALUE STOCK_VALUE TOTAL_VALUE REAL_PROFIT UNREAL_PROFIT PROFIT HIGHWATERMARK DRAWDOWN
0 2023-01-02 TOTAL 0 0 0 0 20 0.0000 0 0 0 -1,312,000 98,688,000 1,312,000 1,312,000 100,000,000 0 0 0 100,000,000 0
1 2023-01-03 TOTAL 0 0 0 0 20 0.0000 0 0 0 0 98,688,000 1,312,000 1,310,000 99,998,000 0 -2,000 -2,000 100,000,000 2,000
2 2023-01-04 TOTAL 0 0 0 0 20 0.0000 0 0 0 0 98,688,000 1,312,000 1,388,000 100,076,000 0 76,000 76,000 100,076,000 0
3 2023-01-05 TOTAL 0 0 0 0 20 0.0000 0 0 0 0 98,688,000 1,312,000 1,396,000 100,084,000 0 84,000 84,000 100,084,000 0
4 2023-01-06 TOTAL 0 0 0 0 20 0.0000 0 0 0 0 98,688,000 1,312,000 1,421,000 100,109,000 0 109,000 109,000 100,109,000 0