import pandas as pdkquant 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- “DATE”: 날짜 열.
start_date(DATE_IN): 백테스트 시작 날짜 문자열. None이면 order 데이터프레임 날짜 중 가장 첫번째 날짜를 이용.end_date(DATE_IN): 백테스트 종료 날짜 문자열 None이면 order 데이터프레임 날짜 중 가장 마지막 날짜를 이용.init_cash(int): 초기 보유현금. 디폴트 0원close_on_end(bool): True면 백테스트 마지막날에는 매매정보를 무시하고 항상 보유잔고 정리. 디폴트 Falseallow_loan(bool): 융자가능여부 즉, 음수인 보유현금 허용 설정값. 디폴트 Falsebroker_fee_percent(float): 증권사 수수료(%). 디폴트 0exchange_fee_percent(float): 유관기관 수수료(%). 디폴트 0trade_tax_percent(float): 매도시 주식양도세(%). 디폴트 0slippage_tick(int): 거래시 발생하는 슬리피지(slippage)틱(호가가격단위), 디폴트 0return_position(bool): 포지션 정보 데이터프레임을 반환할지의 여부. 디폴트 Falseuse_notadj(bool): 수정주가가 아닌 원주가 사용. 디폴트 Falselogger(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: 내부적으로 사용하는 필드
예제 코드
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 kqdict_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 parseimport datetime as dtimport loggingdef 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 |