CH05 포트폴리오 최적화와 성과 평가¶
전략 평가
- 전략의 파라미터를 최적화하기 위한 과거 데이터에 대한 백테스팅
- 새로운 표본 외 데이터에 대해 표본 내 성과를 입증하는 전진 분석
전략 평가의 목표: 특정 과거 상황에 맞게 전략을 조정해 거짓 발견을 방지하는 것
5장에서 다루는 내용
- 포트폴리오 리스크와 수익률을 측정하는 방법
- 평균 분산 최적화와 대안들을 이용해 포트폴리오 가중치를 관리하는 방법
- 포트폴리오 상황에서 자산 배분을 최적화하고자 머신러닝을 사용하는 방법
- 집라인을 이용해 알파 팩터에 기반을 둔 거래 시뮬레이션과 포트폴리오를 생성하는 방법
- 파이폴리오를 이용해 포트폴리오 성과를 평가하는 방법
포트폴리오 성과를 측정하는 방법¶
투자와 거래에서 가장 일반적인 목표는 투자 포트폴리오의 수익과 리스크이다. 이러한 지표는 대체 투자 기회를 나타내는 벤치마크와 비교된다.
- 미국 주식에 대한 S&P 500
- 채권에 대한 무위험 이자율
과 같은 투자 유니버스의 요약이다.
R은 1 기간의 단순 포트폴리오의 시계열이라하고, $$R=(r_1, ..., r_T)$$로 표현되며 여기서 날짜는 1에서 T까지 이다. $$R^f=(r_1^f, ..., r_T^f)$$는 무위험 이자율의 대응되는 시계열로, 그 결과 $$R_e=R-R_f=(r_1-r_1^f, ..., r_T-r_T^f)$$
단일 숫자로 리스크 수익 트레이드오프 포착¶
- 수익과 리스크 목표 -> 트레이드오프 의미
- 리스크 단위당 수익률 측정값을 계산하는 비율
- 서로 다른 전략이 트레이드오프를 탐색하는 방법을 비교
샤프 비율¶
- 샤프 비율(SR, Sharp Ratio): 포트폴리오의 기대 초과 수익률을 표준 편차로 측정되는 초과 수익률의 변동성과 비교
- 리스크를 감수한 단위당 평균초과 수익률로 보상을 측정한다.
- 기대 수익률과 변동성은 관찰할 수 없으나 다음과 같이 과거 데이터를 사용해 추정할 수 있다.
독립적으로 동일하게 분포된(IID) 수익률에 대해 통계적 유의성 검정을 위한 SR 추정치 분포의 편차는 대규모 표본 통계 이론에 따르면 중심 극한 정리(CLT)를 적용해 $\mu$와 $\sigma_{R^e}^2$에 따른다.
정보 비율¶
- 정보 비율(IR, Unformation Ratio)
- 포트폴리오 수익률이 벤치마크에서 벗어나는 추적 오차와 관련해 알파(포트폴리오 초과 수익률)를 측정
높은 정보 비율은 추가적인 리스크와 비교해 매력적인 초과 성과를 반영한다.
적극적 운용의 근본 법칙¶
- 적극적 운영의 근본 법칙은 높은 정보 비율을 달성하는 방법을 설명
- 정보 계수(IC)와 전략 폭의 곱으로 IR를 근사화한다.
포트폴리오 리스크와 수익률 관리 방법¶
포트폴리오 관리의 목표: 벤치마크와 관련해 원하는 리스크 수익률 트레이드오프를 달성하는 금융 상품의 포지션을 선택하고 크기를 조정하는 것
분산 투자를 최적화하는 포지션은 목표 리스크 프로파일을 달성하거나 유지하고자 가격 변동으로 인한 가중치 변화를 설명할 때 리밸런싱이 필요하다.
현대 포트폴리오 관리의 진화¶
현대 포트폴리오 이론
- 적절한 포트폴리오 가중치를 선택해 분산 투자를 최적화하는 수학적 도구를 제공
- 포트폴리오 리스크의 최대 수준을 감안할 때 포트폴리오 수익을 최대화하는 포트폴리오의 효율적 프론티어가 존재
- 포트폴리오 수익률의 표준 편차로 측정된 포트폴리오 리스크가 모든 자산 수익률과 상대적 가중치 사이의 공분산에 어떻게 의존하는지 보여줌
- 표본 오차를 줄기 위해 입력 변수를 제약하는 평균 분산 포트폴리오 나은 성과를 보임
- 제약의 특별한 경우: 동일 가중, 최소 분산, 리스크 패리티 포트폴리오
자본 자산 가격 결정 모델(CAPM)
- 현대 포트폴리오 이론(MPT) 리스크 수익률 관계를 기반으로 하는 자산 가치 평가 모델
- 프리미엄: 분산 투자를 통해 제거될 수 없는 화폐의 시간 가치와 전체 시장 리스크 노출을 보상한다.
평균 분산 최적화¶
01_backtest_with_trades.ipynb¶
!pip install quandl
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting quandl Downloading Quandl-3.7.0-py2.py3-none-any.whl (26 kB) Requirement already satisfied: pandas>=0.14 in /usr/local/lib/python3.10/dist-packages (from quandl) (1.5.3) Requirement already satisfied: numpy>=1.8 in /usr/local/lib/python3.10/dist-packages (from quandl) (1.22.4) Requirement already satisfied: requests>=2.7.0 in /usr/local/lib/python3.10/dist-packages (from quandl) (2.27.1) Collecting inflection>=0.3.1 (from quandl) Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB) Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/dist-packages (from quandl) (2.8.2) Requirement already satisfied: six in /usr/local/lib/python3.10/dist-packages (from quandl) (1.16.0) Requirement already satisfied: more-itertools in /usr/local/lib/python3.10/dist-packages (from quandl) (9.1.0) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=0.14->quandl) (2022.7.1) Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.7.0->quandl) (1.26.15) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.7.0->quandl) (2022.12.7) Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.10/dist-packages (from requests>=2.7.0->quandl) (2.0.12) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.7.0->quandl) (3.4) Installing collected packages: inflection, quandl Successfully installed inflection-0.5.1 quandl-3.7.0
!wget https://launchpad.net/~mario-mariomedina/+archive/ubuntu/talib/+files/libta-lib0_0.4.0-oneiric1_amd64.deb -qO libta.deb
!wget https://launchpad.net/~mario-mariomedina/+archive/ubuntu/talib/+files/ta-lib0-dev_0.4.0-oneiric1_amd64.deb -qO ta.deb
!dpkg -i libta.deb ta.deb
!pip install ta-lib
import talib
!pip install zipline-reloaded
!pip install pyfolio
Selecting previously unselected package libta-lib0. (Reading database ... 123312 files and directories currently installed.) Preparing to unpack libta.deb ... Unpacking libta-lib0 (0.4.0-oneiric1) ... Selecting previously unselected package ta-lib0-dev. Preparing to unpack ta.deb ... Unpacking ta-lib0-dev (0.4.0-oneiric1) ... Setting up libta-lib0 (0.4.0-oneiric1) ... Setting up ta-lib0-dev (0.4.0-oneiric1) ... Processing triggers for man-db (2.9.1-1) ... Processing triggers for libc-bin (2.31-0ubuntu9.9) ... Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting ta-lib Downloading TA-Lib-0.4.26.tar.gz (272 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 272.6/272.6 kB 17.7 MB/s eta 0:00:00 Installing build dependencies ... done Getting requirements to build wheel ... done Installing backend dependencies ... done Preparing metadata (pyproject.toml) ... done Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from ta-lib) (1.22.4) Building wheels for collected packages: ta-lib Building wheel for ta-lib (pyproject.toml) ... done Created wheel for ta-lib: filename=TA_Lib-0.4.26-cp310-cp310-linux_x86_64.whl size=2239099 sha256=3b22c75c44c2b60c646e5f436f28a8ac8034f64f91f37d9a8e742301670bf1e4 Stored in directory: /root/.cache/pip/wheels/ad/be/fe/93a0b4e4efd7fbb963157b79d512c747e935be08fc7b9e3a53 Successfully built ta-lib Installing collected packages: ta-lib Successfully installed ta-lib-0.4.26 Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting zipline-reloaded Downloading zipline_reloaded-2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.9 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.9/9.9 MB 106.9 MB/s eta 0:00:00 Collecting alembic>=0.7.7 (from zipline-reloaded) Downloading alembic-1.11.1-py3-none-any.whl (224 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 224.5/224.5 kB 24.1 MB/s eta 0:00:00 Collecting bcolz-zipline>=1.2.6 (from zipline-reloaded) Downloading bcolz_zipline-1.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.8 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.8/4.8 MB 92.5 MB/s eta 0:00:00 Collecting bottleneck>=1.0.0 (from zipline-reloaded) Downloading Bottleneck-1.3.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (354 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.0/354.0 kB 39.3 MB/s eta 0:00:00 Requirement already satisfied: click>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (8.1.3) Collecting empyrical-reloaded>=0.5.7 (from zipline-reloaded) Downloading empyrical_reloaded-0.5.9-py3-none-any.whl (32 kB) Requirement already satisfied: h5py>=2.7.1 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (3.8.0) Collecting intervaltree>=2.1.0 (from zipline-reloaded) Downloading intervaltree-3.1.0.tar.gz (32 kB) Preparing metadata (setup.py) ... done Collecting iso3166>=2.1.1 (from zipline-reloaded) Downloading iso3166-2.1.1-py3-none-any.whl (9.8 kB) Collecting iso4217>=1.6.20180829 (from zipline-reloaded) Downloading iso4217-1.11.20220401-py2.py3-none-any.whl (10 kB) Collecting lru-dict>=1.1.4 (from zipline-reloaded) Downloading lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (28 kB) Requirement already satisfied: multipledispatch>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (0.6.0) Requirement already satisfied: networkx>=2.0 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (3.1) Requirement already satisfied: numexpr>=2.6.1 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (2.8.4) Requirement already satisfied: numpy<1.24,>=1.14.5 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (1.22.4) Requirement already satisfied: pandas<1.6,>=1.3 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (1.5.3) Requirement already satisfied: patsy>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (0.5.3) Requirement already satisfied: python-dateutil>=2.4.2 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (2.8.2) Collecting python-interface>=1.5.3 (from zipline-reloaded) Using cached python-interface-1.6.1.tar.gz (19 kB) Preparing metadata (setup.py) ... done Requirement already satisfied: pytz>=2018.5 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (2022.7.1) Requirement already satisfied: requests>=2.9.1 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (2.27.1) Requirement already satisfied: scipy>=0.17.1 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (1.10.1) Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (1.16.0) Collecting sqlalchemy<2,>=1.0.8A (from zipline-reloaded) Downloading SQLAlchemy-1.4.48-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.6 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 94.1 MB/s eta 0:00:00 Requirement already satisfied: statsmodels>=0.6.1 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (0.13.5) Requirement already satisfied: ta-lib>=0.4.09 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (0.4.26) Requirement already satisfied: tables>=3.4.3 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (3.8.0) Requirement already satisfied: toolz>=0.8.2 in /usr/local/lib/python3.10/dist-packages (from zipline-reloaded) (0.12.0) Collecting exchange-calendars>=4.2.4 (from zipline-reloaded) Downloading exchange_calendars-4.2.8-py3-none-any.whl (191 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 191.4/191.4 kB 23.9 MB/s eta 0:00:00 Collecting Mako (from alembic>=0.7.7->zipline-reloaded) Downloading Mako-1.2.4-py3-none-any.whl (78 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.7/78.7 kB 10.6 MB/s eta 0:00:00 Requirement already satisfied: typing-extensions>=4 in /usr/local/lib/python3.10/dist-packages (from alembic>=0.7.7->zipline-reloaded) (4.5.0) Requirement already satisfied: pandas-datareader>=0.4 in /usr/local/lib/python3.10/dist-packages (from empyrical-reloaded>=0.5.7->zipline-reloaded) (0.10.0) Requirement already satisfied: yfinance>=0.1.63 in /usr/local/lib/python3.10/dist-packages (from empyrical-reloaded>=0.5.7->zipline-reloaded) (0.2.18) Collecting pyluach (from exchange-calendars>=4.2.4->zipline-reloaded) Downloading pyluach-2.2.0-py3-none-any.whl (25 kB) Requirement already satisfied: korean-lunar-calendar in /usr/local/lib/python3.10/dist-packages (from exchange-calendars>=4.2.4->zipline-reloaded) (0.3.1) Requirement already satisfied: sortedcontainers<3.0,>=2.0 in /usr/local/lib/python3.10/dist-packages (from intervaltree>=2.1.0->zipline-reloaded) (2.4.0) Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.9.1->zipline-reloaded) (1.26.15) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.9.1->zipline-reloaded) (2022.12.7) Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.10/dist-packages (from requests>=2.9.1->zipline-reloaded) (2.0.12) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.9.1->zipline-reloaded) (3.4) Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from sqlalchemy<2,>=1.0.8A->zipline-reloaded) (2.0.2) Requirement already satisfied: packaging>=21.3 in /usr/local/lib/python3.10/dist-packages (from statsmodels>=0.6.1->zipline-reloaded) (23.1) Requirement already satisfied: cython>=0.29.21 in /usr/local/lib/python3.10/dist-packages (from tables>=3.4.3->zipline-reloaded) (0.29.34) Requirement already satisfied: blosc2~=2.0.0 in /usr/local/lib/python3.10/dist-packages (from tables>=3.4.3->zipline-reloaded) (2.0.0) Requirement already satisfied: py-cpuinfo in /usr/local/lib/python3.10/dist-packages (from tables>=3.4.3->zipline-reloaded) (9.0.0) Requirement already satisfied: msgpack in /usr/local/lib/python3.10/dist-packages (from blosc2~=2.0.0->tables>=3.4.3->zipline-reloaded) (1.0.5) Requirement already satisfied: lxml in /usr/local/lib/python3.10/dist-packages (from pandas-datareader>=0.4->empyrical-reloaded>=0.5.7->zipline-reloaded) (4.9.2) Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (0.0.11) Requirement already satisfied: appdirs>=1.4.4 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (1.4.4) Requirement already satisfied: frozendict>=2.3.4 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (2.3.7) Requirement already satisfied: cryptography>=3.3.2 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (40.0.2) Requirement already satisfied: beautifulsoup4>=4.11.1 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (4.11.2) Requirement already satisfied: html5lib>=1.1 in /usr/local/lib/python3.10/dist-packages (from yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (1.1) Requirement already satisfied: MarkupSafe>=0.9.2 in /usr/local/lib/python3.10/dist-packages (from Mako->alembic>=0.7.7->zipline-reloaded) (2.1.2) Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4>=4.11.1->yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (2.4.1) Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.10/dist-packages (from cryptography>=3.3.2->yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (1.15.1) Requirement already satisfied: webencodings in /usr/local/lib/python3.10/dist-packages (from html5lib>=1.1->yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (0.5.1) Requirement already satisfied: pycparser in /usr/local/lib/python3.10/dist-packages (from cffi>=1.12->cryptography>=3.3.2->yfinance>=0.1.63->empyrical-reloaded>=0.5.7->zipline-reloaded) (2.21) Building wheels for collected packages: intervaltree, python-interface Building wheel for intervaltree (setup.py) ... done Created wheel for intervaltree: filename=intervaltree-3.1.0-py2.py3-none-any.whl size=26099 sha256=bf8d22dd85e99567d68c84b93d48b00de85af48837445383c79bad91b0617e48 Stored in directory: /root/.cache/pip/wheels/fa/80/8c/43488a924a046b733b64de3fac99252674c892a4c3801c0a61 Building wheel for python-interface (setup.py) ... done Created wheel for python-interface: filename=python_interface-1.6.1-py3-none-any.whl size=23221 sha256=e0a70b3d15ce1e85b1eba4b2becfba50095dc697711eb8ab285fc84fcfc2c23b Stored in directory: /root/.cache/pip/wheels/14/be/d4/63092bf0e2bcab18e7ce315aa8935d786907f5782db969fbe8 Successfully built intervaltree python-interface Installing collected packages: lru-dict, sqlalchemy, python-interface, pyluach, Mako, iso4217, iso3166, intervaltree, bottleneck, bcolz-zipline, alembic, exchange-calendars, empyrical-reloaded, zipline-reloaded Attempting uninstall: sqlalchemy Found existing installation: SQLAlchemy 2.0.10 Uninstalling SQLAlchemy-2.0.10: Successfully uninstalled SQLAlchemy-2.0.10 Successfully installed Mako-1.2.4 alembic-1.11.1 bcolz-zipline-1.2.6 bottleneck-1.3.7 empyrical-reloaded-0.5.9 exchange-calendars-4.2.8 intervaltree-3.1.0 iso3166-2.1.1 iso4217-1.11.20220401 lru-dict-1.2.0 pyluach-2.2.0 python-interface-1.6.1 sqlalchemy-1.4.48 zipline-reloaded-2.4 Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting pyfolio Downloading pyfolio-0.9.2.tar.gz (91 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 91.1/91.1 kB 8.3 MB/s eta 0:00:00 Preparing metadata (setup.py) ... done Requirement already satisfied: ipython>=3.2.3 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (7.34.0) Requirement already satisfied: matplotlib>=1.4.0 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (3.7.1) Requirement already satisfied: numpy>=1.11.1 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (1.22.4) Requirement already satisfied: pandas>=0.18.1 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (1.5.3) Requirement already satisfied: pytz>=2014.10 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (2022.7.1) Requirement already satisfied: scipy>=0.14.0 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (1.10.1) Requirement already satisfied: scikit-learn>=0.16.1 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (1.2.2) Requirement already satisfied: seaborn>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from pyfolio) (0.12.2) Collecting empyrical>=0.5.0 (from pyfolio) Downloading empyrical-0.5.5.tar.gz (52 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.8/52.8 kB 6.7 MB/s eta 0:00:00 Preparing metadata (setup.py) ... done Requirement already satisfied: pandas-datareader>=0.2 in /usr/local/lib/python3.10/dist-packages (from empyrical>=0.5.0->pyfolio) (0.10.0) Requirement already satisfied: setuptools>=18.5 in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (67.7.2) Collecting jedi>=0.16 (from ipython>=3.2.3->pyfolio) Downloading jedi-0.18.2-py2.py3-none-any.whl (1.6 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 87.3 MB/s eta 0:00:00 Requirement already satisfied: decorator in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (4.4.2) Requirement already satisfied: pickleshare in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (0.7.5) Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (5.7.1) Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (3.0.38) Requirement already satisfied: pygments in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (2.14.0) Requirement already satisfied: backcall in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (0.2.0) Requirement already satisfied: matplotlib-inline in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (0.1.6) Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.10/dist-packages (from ipython>=3.2.3->pyfolio) (4.8.0) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (1.0.7) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (0.11.0) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (4.39.3) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (1.4.4) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (23.1) Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (8.4.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (3.0.9) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=1.4.0->pyfolio) (2.8.2) Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn>=0.16.1->pyfolio) (1.2.0) Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn>=0.16.1->pyfolio) (3.1.0) Requirement already satisfied: parso<0.9.0,>=0.8.0 in /usr/local/lib/python3.10/dist-packages (from jedi>=0.16->ipython>=3.2.3->pyfolio) (0.8.3) Requirement already satisfied: lxml in /usr/local/lib/python3.10/dist-packages (from pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (4.9.2) Requirement already satisfied: requests>=2.19.0 in /usr/local/lib/python3.10/dist-packages (from pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (2.27.1) Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.10/dist-packages (from pexpect>4.3->ipython>=3.2.3->pyfolio) (0.7.0) Requirement already satisfied: wcwidth in /usr/local/lib/python3.10/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=3.2.3->pyfolio) (0.2.6) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=1.4.0->pyfolio) (1.16.0) Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (1.26.15) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (2022.12.7) Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (2.0.12) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (3.4) Building wheels for collected packages: pyfolio, empyrical Building wheel for pyfolio (setup.py) ... done Created wheel for pyfolio: filename=pyfolio-0.9.2-py3-none-any.whl size=88663 sha256=d07d16936b8e6e5e99b0548b16d3345fc91890d0ddb9c877c86da5d8626fcb1a Stored in directory: /root/.cache/pip/wheels/71/38/bc/e53700cfd8b0ad6b539d2fbaaf060ed8a299e7622a5b86ef42 Building wheel for empyrical (setup.py) ... done Created wheel for empyrical: filename=empyrical-0.5.5-py3-none-any.whl size=39762 sha256=2c6128f48cff76c4182ad1e0e1ad9e92041bb55fce45445c6549a12964ea1864 Stored in directory: /root/.cache/pip/wheels/0e/2e/f2/d6d2d9a1eb8fbbd9949bb5d4c00f753e3b74e5bd7ed10b1d36 Successfully built pyfolio empyrical Installing collected packages: jedi, empyrical, pyfolio Successfully installed empyrical-0.5.5 jedi-0.18.2 pyfolio-0.9.2
%env QUANDL_API_KEY='Your key'
!zipline ingest -b quandl
zipline MeanReversion Backtest¶
4장에서는 Zipline
을 소개하여 트레일링 군집 시장, 기본 및 대체 데이터에서 알파 팩터의 계산을 시뮬레이션했습니다.
이제 알파 팩터를 활용하여 이전 장에서 개발한 사용자 정의 MeanReversion 팩터를 기반으로 매수 및 매도 신호를 도출하고 실행할 것입니다.
Imports¶
import warnings
warnings.filterwarnings('ignore')
!pip install logbook
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting logbook Using cached Logbook-1.5.3.tar.gz (85 kB) Preparing metadata (setup.py) ... done Building wheels for collected packages: logbook Building wheel for logbook (setup.py) ... done Created wheel for logbook: filename=Logbook-1.5.3-cp310-cp310-linux_x86_64.whl size=65612 sha256=d191924b4c459b398e097a2e50b8f3888ba6af99ffd8715d20356708913404be Stored in directory: /root/.cache/pip/wheels/ed/50/39/0e0dd103e8e1f2666080c17c7d35feffae80bfb66720e177d6 Successfully built logbook Installing collected packages: logbook Successfully installed logbook-1.5.3
import sys
import numpy as np
import pandas as pd
from pytz import UTC
from logbook import (NestedSetup, NullHandler, Logger, StreamHandler, StderrHandler,
INFO, WARNING, DEBUG, ERROR)
from zipline import run_algorithm
from zipline.api import (attach_pipeline,
date_rules,
time_rules,
get_datetime,
order_target_percent,
pipeline_output,
record,
schedule_function,
get_open_orders,
calendars,
set_commission,
set_slippage)
from zipline.finance import commission, slippage
from zipline.pipeline import Pipeline, CustomFactor
from zipline.pipeline.factors import Returns, AverageDollarVolume
from pyfolio.utils import extract_rets_pos_txn_from_zipline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
Logging Setup¶
# setup stdout logging
format_string = '[{record.time: %H:%M:%S.%f}]: {record.level_name}: {record.message}'
zipline_logging = NestedSetup([NullHandler(level=DEBUG),
StreamHandler(sys.stdout, format_string=format_string, level=INFO),
StreamHandler(sys.stderr, level=ERROR)])
zipline_logging.push_application()
log = Logger('Algorithm')
Algo Settings¶
# Settings
MONTH = 21
YEAR = 12 * MONTH
N_LONGS = 50
N_SHORTS = 50
VOL_SCREEN = 500
import pytz
# start = pd.Timestamp('2013-01-01', tz=UTC)
# end = pd.Timestamp('2017-01-01', tz=UTC)
start = pd.Timestamp('2013-01-01', tz=pytz.UTC).replace(tzinfo=None)
end = pd.Timestamp('2017-01-01', tz=pytz.UTC).replace(tzinfo=None)
capital_base = 1e7
Mean Reversion Factor¶
class MeanReversion(CustomFactor):
"""Compute ratio of latest monthly return to 12m average,
normalized by std dev of monthly returns"""
inputs = [Returns(window_length=MONTH)]
window_length = YEAR
def compute(self, today, assets, out, monthly_returns):
df = pd.DataFrame(monthly_returns)
out[:] = df.iloc[-1].sub(df.mean()).div(df.std())
Create Pipeline¶
compute_factors()
메서드로 생성된 파이프라인은 마지막 한 달 수익률과 연간 평균의 표준 편차로 정규화한 값의 가장 큰 음수와 양수 편차를 가진 25개 주식에 대한 long 및 short 컬럼을 포함한 테이블을 반환합니다. 또한, 지난 30개 거래일 동안의 평균 거래량이 가장 높은 500개 주식으로 유니버스를 제한했습니다.
def compute_factors():
"""Create factor pipeline incl. mean reversion,
filtered by 30d Dollar Volume; capture factor ranks"""
mean_reversion = MeanReversion()
dollar_volume = AverageDollarVolume(window_length=30)
return Pipeline(columns={'longs' : mean_reversion.bottom(N_LONGS),
'shorts' : mean_reversion.top(N_SHORTS),
'ranking': mean_reversion.rank(ascending=False)},
screen=dollar_volume.top(VOL_SCREEN))
Before_trading_start() ensures the daily execution of the pipeline and the recording of the results, including the current prices.
def before_trading_start(context, data):
"""Run factor pipeline"""
context.factor_data = pipeline_output('factor_pipeline')
record(factor_data=context.factor_data.ranking)
assets = context.factor_data.index
record(prices=data.current(assets, 'price'))
Set up Rebalancing¶
rebalance()
메서드는 파이프라인에 의해 롱 포지션과 숏 포지션으로 플래그가 지정된 자산에 대한 거래 주문을 exec_trades()
메서드에 제출합니다. 이때, 양의 가중치와 음의 가중치가 동일합니다. 또한, 팩터 신호에 더 이상 포함되지 않은 현재 보유 자산은 매각합니다.
def rebalance(context, data):
"""Compute long, short and obsolete holdings; place trade orders"""
factor_data = context.factor_data
assets = factor_data.index
longs = assets[factor_data.longs]
shorts = assets[factor_data.shorts]
divest = context.portfolio.positions.keys() - longs.union(shorts)
log.info('{} | Longs: {:2.0f} | Shorts: {:2.0f} | {:,.2f}'.format(get_datetime().date(),
len(longs),
len(shorts),
context.portfolio.portfolio_value))
exec_trades(data, assets=divest, target_percent=0)
exec_trades(data, assets=longs, target_percent=1 / N_LONGS if N_LONGS else 0)
exec_trades(data, assets=shorts, target_percent=-1 / N_SHORTS if N_SHORTS else 0)
def exec_trades(data, assets, target_percent):
"""Place orders for assets using target portfolio percentage"""
for asset in assets:
if data.can_trade(asset) and not get_open_orders(asset):
order_target_percent(asset, target_percent)
Initialize Backtest¶
rebalance()
메서드는 schedule_function()
유틸리티에서 설정한 date_rules
및 time_rules
에 따라 실행됩니다. 이는 US_EQUITIES 캘린더에 따라 시장 오픈 직후 주간 시작 시점에 실행됩니다 (자세한 규칙은 문서를 참조하세요).
상대적인 용어와 최소 금액으로 거래 수수료를 지정할 수도 있습니다. 또한 거래 결정과 실행 사이에서 가격이 불리한 방향으로 변동되는 비용을 나타내는 슬리피지를 정의할 수 있는 옵션이 있습니다.
def initialize(context):
"""Setup: register pipeline, schedule rebalancing,
and set trading params"""
attach_pipeline(compute_factors(), 'factor_pipeline')
schedule_function(rebalance,
date_rules.week_start(),
time_rules.market_open(),
calendar=calendars.US_EQUITIES)
set_commission(us_equities=commission.PerShare(cost=0.00075,
min_trade_cost=.01))
set_slippage(us_equities=slippage.VolumeShareSlippage(volume_limit=0.0025,
price_impact=0.01))
Run Algorithm¶
The algorithm executes upon calling the run_algorithm() function and returns the backtest performance DataFrame.
backtest = run_algorithm(start=start,
end=end,
initialize=initialize,
before_trading_start=before_trading_start,
bundle='quandl',
capital_base=capital_base)
[ 14:36:09.680056]: INFO: 2013-01-07 | Longs: 10 | Shorts: 2 | 10,000,000.00 [ 14:36:15.460660]: INFO: 2013-01-14 | Longs: 13 | Shorts: 3 | 10,027,422.36 [ 14:36:16.223053]: INFO: 2013-01-22 | Longs: 8 | Shorts: 5 | 10,097,663.57 [ 14:36:16.859822]: INFO: 2013-01-28 | Longs: 5 | Shorts: 14 | 10,106,150.06 [ 14:36:17.643716]: INFO: 2013-02-04 | Longs: 12 | Shorts: 11 | 10,135,417.26 [ 14:36:18.500280]: INFO: 2013-02-11 | Longs: 11 | Shorts: 13 | 10,182,644.42 [ 14:36:19.337618]: INFO: 2013-02-19 | Longs: 21 | Shorts: 11 | 10,127,755.45 [ 14:36:20.055704]: INFO: 2013-02-25 | Longs: 16 | Shorts: 8 | 10,180,357.23 [ 14:36:20.928898]: INFO: 2013-03-04 | Longs: 10 | Shorts: 10 | 10,200,300.19 [ 14:36:21.745734]: INFO: 2013-03-11 | Longs: 12 | Shorts: 7 | 10,240,453.13 [ 14:36:22.583522]: INFO: 2013-03-18 | Longs: 7 | Shorts: 7 | 10,150,394.01 [ 14:36:24.026072]: INFO: 2013-03-25 | Longs: 8 | Shorts: 7 | 10,149,029.25 [ 14:36:25.070659]: INFO: 2013-04-01 | Longs: 4 | Shorts: 6 | 10,178,638.39 [ 14:36:25.822338]: INFO: 2013-04-08 | Longs: 6 | Shorts: 13 | 10,188,518.05 [ 14:36:26.589889]: INFO: 2013-04-15 | Longs: 5 | Shorts: 14 | 10,185,696.44 [ 14:36:27.396851]: INFO: 2013-04-22 | Longs: 5 | Shorts: 24 | 10,154,587.17 [ 14:36:28.262129]: INFO: 2013-04-29 | Longs: 5 | Shorts: 10 | 10,189,194.78 [ 14:36:29.088967]: INFO: 2013-05-06 | Longs: 2 | Shorts: 10 | 10,166,211.32 [ 14:36:29.837503]: INFO: 2013-05-13 | Longs: 4 | Shorts: 8 | 9,981,220.24 [ 14:36:30.559414]: INFO: 2013-05-20 | Longs: 8 | Shorts: 9 | 9,943,518.58 [ 14:36:31.303992]: INFO: 2013-05-28 | Longs: 13 | Shorts: 5 | 9,905,180.12 [ 14:36:31.947713]: INFO: 2013-06-03 | Longs: 19 | Shorts: 6 | 9,962,705.11 [ 14:36:32.755362]: INFO: 2013-06-10 | Longs: 10 | Shorts: 3 | 9,953,403.78 [ 14:36:33.580594]: INFO: 2013-06-17 | Longs: 9 | Shorts: 6 | 9,978,043.58 [ 14:36:34.343032]: INFO: 2013-06-24 | Longs: 9 | Shorts: 2 | 9,875,082.86 [ 14:36:35.323074]: INFO: 2013-07-01 | Longs: 15 | Shorts: 3 | 9,932,122.01 [ 14:36:36.526087]: INFO: 2013-07-08 | Longs: 13 | Shorts: 3 | 9,976,143.93 [ 14:36:39.248943]: INFO: 2013-07-15 | Longs: 11 | Shorts: 4 | 10,094,240.18 [ 14:36:40.011754]: INFO: 2013-07-22 | Longs: 6 | Shorts: 2 | 10,056,638.95 [ 14:36:40.727296]: INFO: 2013-07-29 | Longs: 12 | Shorts: 3 | 10,051,076.70 [ 14:36:41.471297]: INFO: 2013-08-05 | Longs: 8 | Shorts: 12 | 10,138,418.95 [ 14:36:42.260613]: INFO: 2013-08-12 | Longs: 7 | Shorts: 10 | 10,146,586.21 [ 14:36:43.052051]: INFO: 2013-08-19 | Longs: 7 | Shorts: 9 | 10,181,590.85 [ 14:36:43.850262]: INFO: 2013-08-26 | Longs: 7 | Shorts: 2 | 10,178,592.71 [ 14:36:44.592609]: INFO: 2013-09-03 | Longs: 5 | Shorts: 2 | 10,226,884.83 [ 14:36:45.175096]: INFO: 2013-09-09 | Longs: 6 | Shorts: 4 | 10,210,627.07 [ 14:36:45.921861]: INFO: 2013-09-16 | Longs: 6 | Shorts: 5 | 10,266,501.96 [ 14:36:46.666895]: INFO: 2013-09-23 | Longs: 6 | Shorts: 3 | 10,258,815.75 [ 14:36:47.648918]: INFO: 2013-09-30 | Longs: 10 | Shorts: 8 | 10,233,855.17 [ 14:36:49.081750]: INFO: 2013-10-07 | Longs: 13 | Shorts: 7 | 10,227,149.62 [ 14:36:50.082705]: INFO: 2013-10-14 | Longs: 14 | Shorts: 3 | 10,278,149.27 [ 14:36:50.856340]: INFO: 2013-10-21 | Longs: 15 | Shorts: 8 | 10,187,013.14 [ 14:36:51.665376]: INFO: 2013-10-28 | Longs: 11 | Shorts: 6 | 10,323,170.17 [ 14:36:52.464230]: INFO: 2013-11-04 | Longs: 6 | Shorts: 10 | 10,214,759.93 [ 14:36:53.267255]: INFO: 2013-11-11 | Longs: 2 | Shorts: 5 | 10,257,347.04 [ 14:36:54.030689]: INFO: 2013-11-18 | Longs: 4 | Shorts: 9 | 10,245,333.87 [ 14:36:54.805998]: INFO: 2013-11-25 | Longs: 4 | Shorts: 8 | 10,279,469.50 [ 14:36:55.435019]: INFO: 2013-12-02 | Longs: 7 | Shorts: 2 | 10,302,532.04 [ 14:36:56.196548]: INFO: 2013-12-09 | Longs: 10 | Shorts: 3 | 10,320,355.73 [ 14:36:56.940723]: INFO: 2013-12-16 | Longs: 17 | Shorts: 2 | 10,353,234.81 [ 14:36:57.715393]: INFO: 2013-12-23 | Longs: 11 | Shorts: 4 | 10,269,888.43 [ 14:36:58.403611]: INFO: 2013-12-30 | Longs: 10 | Shorts: 6 | 10,293,416.34 [ 14:36:59.027922]: INFO: 2014-01-06 | Longs: 12 | Shorts: 5 | 10,300,370.23 [ 14:37:00.043810]: INFO: 2014-01-13 | Longs: 8 | Shorts: 3 | 10,321,419.65 [ 14:37:03.129907]: INFO: 2014-01-21 | Longs: 14 | Shorts: 4 | 10,301,321.23 [ 14:37:03.775406]: INFO: 2014-01-27 | Longs: 16 | Shorts: 4 | 10,272,301.28 [ 14:37:04.556854]: INFO: 2014-02-03 | Longs: 16 | Shorts: 7 | 10,190,046.47 [ 14:37:05.356740]: INFO: 2014-02-10 | Longs: 10 | Shorts: 8 | 10,173,544.07 [ 14:37:06.148174]: INFO: 2014-02-18 | Longs: 8 | Shorts: 11 | 10,146,175.51 [ 14:37:06.789721]: INFO: 2014-02-24 | Longs: 8 | Shorts: 11 | 10,135,284.41 [ 14:37:07.566033]: INFO: 2014-03-03 | Longs: 5 | Shorts: 10 | 10,206,465.49 [ 14:37:08.355129]: INFO: 2014-03-10 | Longs: 8 | Shorts: 5 | 10,193,416.12 [ 14:37:09.111691]: INFO: 2014-03-17 | Longs: 6 | Shorts: 4 | 10,150,092.53 [ 14:37:09.862038]: INFO: 2014-03-24 | Longs: 7 | Shorts: 3 | 10,157,127.09 [ 14:37:10.594943]: INFO: 2014-03-31 | Longs: 10 | Shorts: 3 | 10,127,717.05 [ 14:37:11.354922]: INFO: 2014-04-07 | Longs: 10 | Shorts: 10 | 9,940,554.60 [ 14:37:12.348607]: INFO: 2014-04-14 | Longs: 10 | Shorts: 12 | 9,922,172.04 [ 14:37:13.562492]: INFO: 2014-04-21 | Longs: 9 | Shorts: 16 | 9,977,695.73 [ 14:37:14.681356]: INFO: 2014-04-28 | Longs: 8 | Shorts: 11 | 9,925,818.81 [ 14:37:15.660535]: INFO: 2014-05-05 | Longs: 6 | Shorts: 8 | 9,994,098.05 [ 14:37:16.577545]: INFO: 2014-05-12 | Longs: 3 | Shorts: 9 | 9,959,988.27 [ 14:37:17.941739]: INFO: 2014-05-19 | Longs: 2 | Shorts: 6 | 9,952,316.59 [ 14:37:18.872257]: INFO: 2014-05-27 | Longs: 5 | Shorts: 5 | 9,975,477.08 [ 14:37:20.040608]: INFO: 2014-06-02 | Longs: 6 | Shorts: 3 | 9,987,026.35 [ 14:37:21.663938]: INFO: 2014-06-09 | Longs: 8 | Shorts: 5 | 10,024,385.61 [ 14:37:22.400841]: INFO: 2014-06-16 | Longs: 13 | Shorts: 13 | 9,983,402.35 [ 14:37:23.200447]: INFO: 2014-06-23 | Longs: 8 | Shorts: 15 | 9,996,939.06 [ 14:37:24.018199]: INFO: 2014-06-30 | Longs: 12 | Shorts: 15 | 10,037,014.26 [ 14:37:25.151739]: INFO: 2014-07-07 | Longs: 14 | Shorts: 12 | 10,082,385.12 [ 14:37:26.623994]: INFO: 2014-07-14 | Longs: 5 | Shorts: 14 | 10,089,176.51 [ 14:37:29.070730]: INFO: 2014-07-21 | Longs: 13 | Shorts: 11 | 10,128,809.09 [ 14:37:29.929790]: INFO: 2014-07-28 | Longs: 11 | Shorts: 11 | 10,119,893.60 [ 14:37:30.747976]: INFO: 2014-08-04 | Longs: 11 | Shorts: 19 | 10,110,604.81 [ 14:37:31.787278]: INFO: 2014-08-11 | Longs: 14 | Shorts: 11 | 10,259,687.80 [ 14:37:33.150820]: INFO: 2014-08-18 | Longs: 11 | Shorts: 14 | 10,204,032.36 [ 14:37:34.189223]: INFO: 2014-08-25 | Longs: 12 | Shorts: 14 | 10,211,914.68 [ 14:37:35.241946]: INFO: 2014-09-02 | Longs: 7 | Shorts: 9 | 10,176,282.96 [ 14:37:36.066398]: INFO: 2014-09-08 | Longs: 2 | Shorts: 13 | 10,227,375.20 [ 14:37:37.499720]: INFO: 2014-09-15 | Longs: 5 | Shorts: 4 | 10,232,017.60 [ 14:37:39.145090]: INFO: 2014-09-22 | Longs: 6 | Shorts: 8 | 10,188,352.29 [ 14:37:40.141914]: INFO: 2014-09-29 | Longs: 4 | Shorts: 13 | 10,153,790.76 [ 14:37:41.080854]: INFO: 2014-10-06 | Longs: 12 | Shorts: 9 | 10,137,941.09 [ 14:37:42.063270]: INFO: 2014-10-13 | Longs: 12 | Shorts: 17 | 10,008,763.46 [ 14:37:43.024514]: INFO: 2014-10-20 | Longs: 21 | Shorts: 6 | 10,132,979.74 [ 14:37:44.099909]: INFO: 2014-10-27 | Longs: 13 | Shorts: 7 | 10,130,508.74 [ 14:37:45.064075]: INFO: 2014-11-03 | Longs: 6 | Shorts: 0 | 10,202,585.17 [ 14:37:45.986672]: INFO: 2014-11-10 | Longs: 7 | Shorts: 6 | 10,112,658.72 [ 14:37:46.869754]: INFO: 2014-11-17 | Longs: 6 | Shorts: 10 | 10,149,797.97 [ 14:37:47.829476]: INFO: 2014-11-24 | Longs: 6 | Shorts: 7 | 10,159,664.13 [ 14:37:48.567338]: INFO: 2014-12-01 | Longs: 12 | Shorts: 13 | 10,112,949.01 [ 14:37:51.264480]: INFO: 2014-12-08 | Longs: 11 | Shorts: 4 | 9,883,877.18 [ 14:37:52.444712]: INFO: 2014-12-15 | Longs: 13 | Shorts: 3 | 9,748,742.24 [ 14:37:53.887691]: INFO: 2014-12-22 | Longs: 7 | Shorts: 4 | 10,162,433.56 [ 14:37:54.811240]: INFO: 2014-12-29 | Longs: 10 | Shorts: 8 | 10,153,178.89 [ 14:37:55.553563]: INFO: 2015-01-05 | Longs: 6 | Shorts: 3 | 10,058,855.77 [ 14:37:56.540644]: INFO: 2015-01-12 | Longs: 7 | Shorts: 6 | 10,013,742.96 [ 14:37:59.650111]: INFO: 2015-01-20 | Longs: 12 | Shorts: 7 | 9,911,930.16 [ 14:38:00.431360]: INFO: 2015-01-26 | Longs: 11 | Shorts: 8 | 9,933,179.35 [ 14:38:01.383956]: INFO: 2015-02-02 | Longs: 18 | Shorts: 7 | 9,874,894.91 [ 14:38:03.175415]: INFO: 2015-02-09 | Longs: 15 | Shorts: 6 | 10,026,955.51 [ 14:38:04.635944]: INFO: 2015-02-17 | Longs: 12 | Shorts: 11 | 10,080,570.15 [ 14:38:05.476855]: INFO: 2015-02-23 | Longs: 13 | Shorts: 7 | 10,087,564.34 [ 14:38:06.478039]: INFO: 2015-03-02 | Longs: 16 | Shorts: 7 | 10,042,612.79 [ 14:38:07.438052]: INFO: 2015-03-09 | Longs: 12 | Shorts: 5 | 9,963,112.34 [ 14:38:08.343589]: INFO: 2015-03-16 | Longs: 15 | Shorts: 2 | 10,023,732.49 [ 14:38:09.288573]: INFO: 2015-03-23 | Longs: 10 | Shorts: 2 | 10,078,627.34 [ 14:38:10.203824]: INFO: 2015-03-30 | Longs: 13 | Shorts: 4 | 10,072,282.97 [ 14:38:10.925064]: INFO: 2015-04-06 | Longs: 12 | Shorts: 3 | 10,123,357.71 [ 14:38:11.801912]: INFO: 2015-04-13 | Longs: 5 | Shorts: 8 | 10,165,443.63 [ 14:38:12.681426]: INFO: 2015-04-20 | Longs: 14 | Shorts: 11 | 10,161,371.72 [ 14:38:14.123838]: INFO: 2015-04-27 | Longs: 12 | Shorts: 9 | 10,114,674.77 [ 14:38:15.896803]: INFO: 2015-05-04 | Longs: 8 | Shorts: 13 | 10,111,362.19 [ 14:38:17.617230]: INFO: 2015-05-11 | Longs: 7 | Shorts: 8 | 10,117,885.18 [ 14:38:18.729897]: INFO: 2015-05-18 | Longs: 5 | Shorts: 7 | 10,118,338.65 [ 14:38:20.016665]: INFO: 2015-05-26 | Longs: 3 | Shorts: 5 | 10,125,037.38 [ 14:38:20.600606]: INFO: 2015-06-01 | Longs: 5 | Shorts: 8 | 10,140,937.50 [ 14:38:21.337729]: INFO: 2015-06-08 | Longs: 10 | Shorts: 6 | 10,160,546.75 [ 14:38:22.091343]: INFO: 2015-06-15 | Longs: 14 | Shorts: 6 | 10,135,990.35 [ 14:38:22.891737]: INFO: 2015-06-22 | Longs: 10 | Shorts: 4 | 10,244,177.64 [ 14:38:23.680664]: INFO: 2015-06-29 | Longs: 18 | Shorts: 5 | 10,137,986.21 [ 14:38:24.321843]: INFO: 2015-07-06 | Longs: 12 | Shorts: 10 | 10,170,113.59 [ 14:38:25.111631]: INFO: 2015-07-13 | Longs: 7 | Shorts: 7 | 10,244,091.21 [ 14:38:25.891530]: INFO: 2015-07-20 | Longs: 5 | Shorts: 11 | 10,240,223.17 [ 14:38:29.122235]: INFO: 2015-07-27 | Longs: 7 | Shorts: 13 | 10,311,588.77 [ 14:38:29.900623]: INFO: 2015-08-03 | Longs: 5 | Shorts: 12 | 10,277,581.93 [ 14:38:30.670023]: INFO: 2015-08-10 | Longs: 3 | Shorts: 11 | 10,308,010.70 [ 14:38:31.433820]: INFO: 2015-08-17 | Longs: 5 | Shorts: 8 | 10,274,866.69 [ 14:38:32.192379]: INFO: 2015-08-24 | Longs: 17 | Shorts: 7 | 10,262,054.06 [ 14:38:32.991687]: INFO: 2015-08-31 | Longs: 14 | Shorts: 5 | 10,388,393.11 [ 14:38:33.787318]: INFO: 2015-09-08 | Longs: 21 | Shorts: 5 | 10,453,422.88 [ 14:38:34.457610]: INFO: 2015-09-14 | Longs: 11 | Shorts: 8 | 10,407,866.97 [ 14:38:35.251547]: INFO: 2015-09-21 | Longs: 12 | Shorts: 9 | 10,461,020.10 [ 14:38:36.031269]: INFO: 2015-09-28 | Longs: 15 | Shorts: 7 | 10,413,522.87 [ 14:38:36.837446]: INFO: 2015-10-05 | Longs: 2 | Shorts: 7 | 10,684,535.25 [ 14:38:37.610860]: INFO: 2015-10-12 | Longs: 10 | Shorts: 7 | 10,677,559.63 [ 14:38:38.374622]: INFO: 2015-10-19 | Longs: 6 | Shorts: 8 | 10,720,533.14 [ 14:38:39.310740]: INFO: 2015-10-26 | Longs: 11 | Shorts: 12 | 10,585,301.22 [ 14:38:40.815670]: INFO: 2015-11-02 | Longs: 14 | Shorts: 6 | 10,594,718.88 [ 14:38:41.811044]: INFO: 2015-11-09 | Longs: 10 | Shorts: 5 | 10,402,671.77 [ 14:38:42.570133]: INFO: 2015-11-16 | Longs: 13 | Shorts: 5 | 10,375,441.10 [ 14:38:43.352250]: INFO: 2015-11-23 | Longs: 9 | Shorts: 7 | 10,449,719.61 [ 14:38:43.988440]: INFO: 2015-11-30 | Longs: 11 | Shorts: 6 | 10,496,308.50 [ 14:38:44.756381]: INFO: 2015-12-07 | Longs: 12 | Shorts: 8 | 10,513,409.39 [ 14:38:45.555235]: INFO: 2015-12-14 | Longs: 9 | Shorts: 14 | 10,400,063.33 [ 14:38:46.376098]: INFO: 2015-12-21 | Longs: 10 | Shorts: 12 | 10,336,091.55 [ 14:38:47.051622]: INFO: 2015-12-28 | Longs: 9 | Shorts: 8 | 10,373,369.95 [ 14:38:47.710932]: INFO: 2016-01-04 | Longs: 5 | Shorts: 11 | 10,377,997.16 [ 14:38:48.484648]: INFO: 2016-01-11 | Longs: 8 | Shorts: 7 | 10,413,318.21 [ 14:38:49.259345]: INFO: 2016-01-19 | Longs: 8 | Shorts: 6 | 10,454,666.31 [ 14:38:51.615608]: INFO: 2016-01-25 | Longs: 9 | Shorts: 9 | 10,477,454.93 [ 14:38:53.060995]: INFO: 2016-02-01 | Longs: 14 | Shorts: 14 | 10,420,206.34 [ 14:38:54.109199]: INFO: 2016-02-08 | Longs: 18 | Shorts: 16 | 10,182,460.84 [ 14:38:54.994546]: INFO: 2016-02-16 | Longs: 12 | Shorts: 10 | 10,452,919.06 [ 14:38:55.711530]: INFO: 2016-02-22 | Longs: 6 | Shorts: 7 | 10,585,928.85 [ 14:38:56.495332]: INFO: 2016-02-29 | Longs: 4 | Shorts: 9 | 10,622,313.10 [ 14:38:57.260328]: INFO: 2016-03-07 | Longs: 9 | Shorts: 8 | 10,558,809.60 [ 14:38:58.017127]: INFO: 2016-03-14 | Longs: 4 | Shorts: 4 | 10,554,090.86 [ 14:38:58.776447]: INFO: 2016-03-21 | Longs: 5 | Shorts: 10 | 10,327,145.78 [ 14:38:59.383580]: INFO: 2016-03-28 | Longs: 5 | Shorts: 4 | 10,457,227.91 [ 14:39:00.093116]: INFO: 2016-04-04 | Longs: 3 | Shorts: 2 | 10,406,360.98 [ 14:39:00.793245]: INFO: 2016-04-11 | Longs: 7 | Shorts: 9 | 10,409,222.16 [ 14:39:01.529110]: INFO: 2016-04-18 | Longs: 15 | Shorts: 6 | 10,400,049.03 [ 14:39:02.335811]: INFO: 2016-04-25 | Longs: 19 | Shorts: 2 | 10,404,515.45 [ 14:39:03.148938]: INFO: 2016-05-02 | Longs: 13 | Shorts: 9 | 10,431,469.55 [ 14:39:04.343953]: INFO: 2016-05-09 | Longs: 10 | Shorts: 2 | 10,524,765.67 [ 14:39:05.825981]: INFO: 2016-05-16 | Longs: 14 | Shorts: 6 | 10,468,602.90 [ 14:39:06.654850]: INFO: 2016-05-23 | Longs: 11 | Shorts: 7 | 10,485,867.14 [ 14:39:07.436580]: INFO: 2016-05-31 | Longs: 7 | Shorts: 7 | 10,535,395.41 [ 14:39:08.081220]: INFO: 2016-06-06 | Longs: 9 | Shorts: 9 | 10,549,440.76 [ 14:39:08.871445]: INFO: 2016-06-13 | Longs: 13 | Shorts: 6 | 10,605,810.71 [ 14:39:09.718862]: INFO: 2016-06-20 | Longs: 8 | Shorts: 6 | 10,589,211.73 [ 14:39:10.505632]: INFO: 2016-06-27 | Longs: 17 | Shorts: 4 | 10,483,875.66 [ 14:39:11.292249]: INFO: 2016-07-05 | Longs: 6 | Shorts: 12 | 10,575,846.46 [ 14:39:11.956917]: INFO: 2016-07-11 | Longs: 7 | Shorts: 11 | 10,662,656.13 [ 14:39:12.734381]: INFO: 2016-07-18 | Longs: 3 | Shorts: 5 | 10,683,919.69 [ 14:39:17.745011]: INFO: 2016-07-25 | Longs: 7 | Shorts: 13 | 10,707,303.42 [ 14:39:18.735275]: INFO: 2016-08-01 | Longs: 14 | Shorts: 8 | 10,718,980.24 [ 14:39:19.587967]: INFO: 2016-08-08 | Longs: 12 | Shorts: 7 | 10,660,895.86 [ 14:39:20.393403]: INFO: 2016-08-15 | Longs: 7 | Shorts: 8 | 10,665,800.53 [ 14:39:21.173768]: INFO: 2016-08-22 | Longs: 8 | Shorts: 6 | 10,650,245.31 [ 14:39:21.941096]: INFO: 2016-08-29 | Longs: 11 | Shorts: 3 | 10,649,100.18 [ 14:39:22.705056]: INFO: 2016-09-06 | Longs: 14 | Shorts: 5 | 10,654,268.58 [ 14:39:23.344879]: INFO: 2016-09-12 | Longs: 29 | Shorts: 3 | 10,636,322.37 [ 14:39:24.209426]: INFO: 2016-09-19 | Longs: 19 | Shorts: 5 | 10,675,288.81 [ 14:39:25.070053]: INFO: 2016-09-26 | Longs: 14 | Shorts: 5 | 10,680,478.84 [ 14:39:25.887946]: INFO: 2016-10-03 | Longs: 10 | Shorts: 5 | 10,723,923.98 [ 14:39:26.685063]: INFO: 2016-10-10 | Longs: 5 | Shorts: 5 | 10,708,120.44 [ 14:39:27.420829]: INFO: 2016-10-17 | Longs: 6 | Shorts: 6 | 10,748,231.24 [ 14:39:28.169761]: INFO: 2016-10-24 | Longs: 5 | Shorts: 11 | 10,743,221.29 [ 14:39:29.600220]: INFO: 2016-10-31 | Longs: 7 | Shorts: 15 | 10,761,910.49 [ 14:39:30.825629]: INFO: 2016-11-07 | Longs: 11 | Shorts: 11 | 10,739,304.47 [ 14:39:31.682097]: INFO: 2016-11-14 | Longs: 18 | Shorts: 4 | 10,691,323.41 [ 14:39:32.521898]: INFO: 2016-11-21 | Longs: 18 | Shorts: 1 | 10,734,499.53 [ 14:39:33.184115]: INFO: 2016-11-28 | Longs: 20 | Shorts: 0 | 10,724,702.90 [ 14:39:33.998577]: INFO: 2016-12-05 | Longs: 25 | Shorts: 3 | 10,679,618.01 [ 14:39:34.820571]: INFO: 2016-12-12 | Longs: 21 | Shorts: 1 | 10,835,582.73 [ 14:39:35.652644]: INFO: 2016-12-19 | Longs: 13 | Shorts: 2 | 10,871,411.67 [ 14:39:36.428261]: INFO: 2016-12-27 | Longs: 12 | Shorts: 1 | 10,815,943.67
Extract pyfolio Inputs¶
The extract_rets_pos_txn_from_zipline
utility provided by pyfolio
extracts the data used to compute performance metrics.
returns, positions, transactions = extract_rets_pos_txn_from_zipline(backtest)
Persist Results for use with pyfolio
¶
with pd.HDFStore('backtests.h5') as store:
store.put('backtest/equal_weight', backtest)
store.put('returns/equal_weight', returns)
store.put('positions/equal_weight', positions)
store.put('transactions/equal_weight', transactions)
Plot Results¶
fig, axes= plt.subplots(nrows=2, figsize=(14,6))
returns.add(1).cumprod().sub(1).plot(ax=axes[0], title='Cumulative Returns')
transactions.groupby(transactions.dt.dt.day).txn_dollars.sum().cumsum().plot(ax=axes[1], title='Cumulative Transactions')
fig.tight_layout()
sns.despine();
positions.index = positions.index.date
fig, ax = plt.subplots(figsize=(15, 8))
sns.heatmap(positions.replace(0, np.nan).dropna(how='all', axis=1).T,
cmap=sns.diverging_palette(h_neg=20, h_pos=200), ax=ax, center=0);
positions.head()
sid | Equity(0 [A]) | Equity(1 [AA]) | Equity(2 [AAL]) | Equity(7 [AAP]) | Equity(8 [AAPL]) | Equity(12 [ABBV]) | Equity(13 [ABC]) | Equity(20 [ABT]) | Equity(21 [ACAD]) | Equity(27 [ACE]) | ... | Equity(3156 [XOM]) | Equity(3162 [XPO]) | Equity(3165 [XRX]) | Equity(3171 [YELP]) | Equity(3172 [YHOO]) | Equity(3175 [YUM]) | Equity(3180 [ZBH]) | Equity(3194 [ZNGA]) | Equity(3197 [ZTS]) | cash |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2013-01-08 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 8.441446e+06 |
2013-01-09 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 8.379079e+06 |
2013-01-10 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 8.379079e+06 |
2013-01-11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 8.379079e+06 |
2013-01-14 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 8.379079e+06 |
5 rows × 750 columns
transactions.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 6505 entries, 2013-01-08 21:00:00+00:00 to 2016-12-30 21:00:00+00:00 Data columns (total 8 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 sid 6505 non-null object 1 symbol 6505 non-null object 2 price 6505 non-null float64 3 order_id 6502 non-null object 4 amount 6505 non-null int64 5 commission 0 non-null object 6 dt 6505 non-null datetime64[ns, UTC] 7 txn_dollars 6505 non-null float64 dtypes: datetime64[ns, UTC](1), float64(2), int64(1), object(4) memory usage: 457.4+ KB
평균 분산 최적화의 대안¶
- 평균 분산 최적화 문제에 대한 정확한 입력의 이슈로 평균, 분산, 또는 둘 모두를 제약하거나 혹은 리스크 패리티 접근법과 같이 더 도전적으로 수익률 추정을 생략하는 여러 실용적인 대안을 채택함
1/n 포트폴리오¶
- 투자 시 자산을 균등하게 분배하여 구성하는 포트폴리오 전략
- n은 포트폴리오에 포함되는 자산의 수를 의미
최소 분산 포트폴리오¶
- 글로벌 최소 분산(GMV) 포트폴리오: 리스크 최소화를 우선시한다. 평균 분산을 사용한 포트폴리오 표준 편차를 최소화해 계산한다.
글로벌 포트폴리오 최적화: 블랙-리터만 접근법¶
- 경제 모델과 통계적 학습을 결합
- CAPM에 의해 정의된 대로 균형에 충분히 가깝다는 가정에서 관측할 수 없는 미래 기대 수익률을 리버스 엔지니어링할 수 있다.
베팅의 크기를 정하는 방법: 켈리 공식¶
05_kelly_rule.ipynb¶
How to size your bets - The Kelly Rule¶
켈리 규칙(Kelly rule)은 도박에서 오랜 역사를 가지고 있는데, 이 규칙은 다양한 (그러나 유리한) 배당률을 가진 (무한한) 일련의 베팅에 대해 각각 얼마씩 거는 것이 종합 자본을 극대화하는지에 대한 지침을 제공합니다. 이 규칙은 1956년에 클로드 샤넌과 벨 연구소에서 동료였던 존 켈리(John Kelly)가 "A New Interpretation of the Information Rate"라는 논문으로 발표되었습니다. 그는 새로운 퀴즈 쇼인 "The $64,000 Question"에서 후보들에 대한 베팅에 흥미를 느껴, 서부 해안의 시청자들이 우승자에 대한 내부 정보를 얻기 위해 3시간의 지연을 이용한다는 사실을 알게 되었습니다.
켈리는 샤넌의 정보 이론과 연결을 찾아서, 유리한 배당률을 가지지만 불확실성이 여전히 남아있는 경우 장기적인 자본 성장을 극대화하기 위한 최적의 베팅을 구하는 데 사용되었습니다. 그의 규칙은 각 게임의 성공 확률에 대한 로그형태의 재산을 최대화하며, log(0)이 음의 무한대이기 때문에 켈리 도박자는 모든 것을 잃는 것을 자연스럽게 피하게 됩니다.
Imports¶
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from pathlib import Path
import numpy as np
from numpy.linalg import inv
from numpy.random import dirichlet
import pandas as pd
from sympy import symbols, solve, log, diff
from scipy.optimize import minimize_scalar, newton, minimize
from scipy.integrate import quad
from scipy.stats import norm
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
np.random.seed(42)
DATA_STORE = Path('assets.h5')
The optimal size of a bet¶
Kelly는 이기거나 지는 이진 결과를 가지는 게임을 분석하는 것으로 시작했습니다. 주요 변수는 다음과 같습니다:
- b: 배당률은 \$1 베팅에 대해 얻는 금액을 정의합니다. 배당률이 5/1이라면 베팅이 이기면 \\$5의 이익과 \$1의 자본 회수를 의미합니다.
- p: 확률은 유리한 결과의 가능성을 정의합니다.
- f: 현재 자본에서 베팅하는 비율을 의미합니다.
- V: 베팅 결과로 얻은 자본의 가치입니다.
Kelly 규칙은 무한히 반복되는 베팅의 가치 성장률 G를 극대화하는 것을 목표로 합니다 (배경은 5장을 참조하세요). $$G=\lim_{N\rightarrow\infty}=\frac{1}{N}\log\frac{V_N}{V_0}$$
우리는 sympy를 사용하여 다음과 같이 f에 대해 G를 극대화함으로써 성장률 G를 최대화할 수 있습니다:
share, odds, probability = symbols('share odds probability')
Value = probability * log(1 + odds * share) + (1 - probability) * log(1 - share)
solve(diff(Value, share), share)
[(odds*probability + probability - 1)/odds]
f, p = symbols('f p')
y = p * log(1 + f) + (1 - p) * log(1 - f)
solve(diff(y, f), f)
[2*p - 1]
Get S&P 500 Data¶
from google.colab import drive
import pandas_datareader.data as web
drive.mount('/content/drive') # 구글 드라이브를 사용하는 경우
df = web.DataReader(name='SP500', data_source='fred', start=2009).squeeze().to_frame('close')
print(df.info())
with pd.HDFStore(DATA_STORE) as store:
store.put('sp500/fred', df)
sp500_stooq = (pd.read_csv('/content/drive/MyDrive/스터디/금융공학_퀀트_스터디/data/^spx_d.csv', index_col=0,
parse_dates=True).loc['1950':'2019'].rename(columns=str.lower))
print(sp500_stooq.info())
with pd.HDFStore(DATA_STORE) as store:
store.put('sp500/stooq', sp500_stooq)
url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
df = pd.read_html(url, header=0)[0]
df.columns = ['ticker', 'name', 'gics_sector', 'gics_sub_industry',
'location', 'first_added', 'cik', 'founded']
df = df.set_index('ticker')
# sec_filings이 사라짐
with pd.HDFStore(DATA_STORE) as store:
store.put('sp500/stocks', df)
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True). <class 'pandas.core.frame.DataFrame'> DatetimeIndex: 2610 entries, 2013-06-17 to 2023-06-16 Data columns (total 1 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 close 2519 non-null float64 dtypes: float64(1) memory usage: 40.8 KB None <class 'pandas.core.frame.DataFrame'> DatetimeIndex: 17700 entries, 1950-01-03 to 2019-12-31 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 open 17700 non-null float64 1 high 17700 non-null float64 2 low 17700 non-null float64 3 close 17700 non-null float64 4 volume 17700 non-null float64 dtypes: float64(5) memory usage: 829.7 KB None
with pd.HDFStore(DATA_STORE) as store:
sp500 = store['sp500/stooq'].close
Compute Returns & Standard Deviation¶
annual_returns = sp500.resample('A').last().pct_change().dropna().to_frame('sp500')
return_params = annual_returns.sp500.rolling(25).agg(['mean', 'std']).dropna()
return_ci = (return_params[['mean']]
.assign(lower=return_params['mean'].sub(return_params['std'].mul(2)))
.assign(upper=return_params['mean'].add(return_params['std'].mul(2))))
return_ci.plot(lw=2, figsize=(14, 8))
plt.tight_layout()
sns.despine();
Kelly Rule for a Single Asset - Index Returns¶
금융 시장의 맥락에서는 결과와 대안이 더 복잡하지만, 켈리 규칙의 논리는 여전히 적용됩니다. 켈리 규칙은 처음으로 도박에 성공적으로 적용한 에드 토프(Ed Thorp)에 의해 널리 알려졌으며, 그는 이를 "Beat the Dealer"에서 설명한 후 투자 헤지펀드인 프린스턴/뉴포트 파트너스(Princeton/Newport Partners)를 시작했습니다.
연속적인 결과의 경우, 자본의 성장률은 수치적으로 최적화할 수 있는 다양한 수익률의 확률 분포를 통해 정의됩니다. 우리는 이 식(책 참조)을 사용하여 최적의 f*를 구하기 위해 scipy.optimize
모듈을 사용할 수 있습니다.
def norm_integral(f, mean, std):
val, er = quad(lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std),
mean - 3 * std,
mean + 3 * std)
return -val
def norm_dev_integral(f, mean, std):
val, er = quad(lambda s: (s / (1 + f * s)) * norm.pdf(s, mean, std), m-3*std, mean+3*std)
return val
def get_kelly_share(data):
solution = minimize_scalar(norm_integral,
args=(data['mean'], data['std']),
bounds=[0, 2],
method='bounded')
return solution.x
annual_returns['f'] = return_params.apply(get_kelly_share, axis=1)
return_params.plot(subplots=True, lw=2, figsize=(14, 8));
annual_returns.tail()
sp500 | f | |
---|---|---|
Date | ||
2015-12-31 | -0.007266 | 1.999996 |
2016-12-31 | 0.095350 | 1.999996 |
2017-12-31 | 0.194200 | 1.999996 |
2018-12-31 | -0.062373 | 1.999996 |
2019-12-31 | 0.288781 | 1.999996 |
Performance Evaluation¶
(annual_returns[['sp500']]
.assign(kelly=annual_returns.sp500.mul(annual_returns.f.shift()))
.dropna()
.loc['1900':]
.add(1)
.cumprod()
.sub(1)
.plot(lw=2));
annual_returns.f.describe()
count 45.000000 mean 1.979025 std 0.062282 min 1.708206 25% 1.999996 50% 1.999996 75% 1.999996 max 1.999996 Name: f, dtype: float64
return_ci.head()
mean | lower | upper | |
---|---|---|---|
Date | |||
1975-12-31 | 0.076574 | -0.289442 | 0.442591 |
1976-12-31 | 0.077649 | -0.289600 | 0.444897 |
1977-12-31 | 0.068336 | -0.306402 | 0.443074 |
1978-12-31 | 0.071410 | -0.299973 | 0.442794 |
1979-12-31 | 0.058325 | -0.278930 | 0.395581 |
Compute Kelly Fraction¶
m = .058
s = .216
# Option 1: minimize the expectation integral
sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0., 2.], method='bounded')
print('Optimal Kelly fraction: {:.4f}'.format(sol.x))
Optimal Kelly fraction: 1.1974
# Option 2: take the derivative of the expectation and make it null
x0 = newton(norm_dev_integral, .1, args=(m, s))
print('Optimal Kelly fraction: {:.4f}'.format(x0))
Optimal Kelly fraction: 1.1974
Kelly Rule for Multiple Assets¶
우리는 여러 주식을 사용한 예시를 사용할 것입니다. E. Chan (2008)은 켈리 규칙의 다중 자산 응용법을 어떻게 도출하는지 설명하며, 그 결과가 평균-분산 최적화의 (잠재적으로 레버리지된) 최대 샤프 비율 포트폴리오와 동등하다고 보여줍니다.
계산에는 정밀도 행렬, 즉 공분산 행렬의 역행렬과 수익률 행렬의 내적이 포함됩니다.
!unzip /content/drive/MyDrive/스터디/금융공학_퀀트_스터디/data/WIKI_PRICES.zip
!mv WIKI_PRICES_212b326a081eacca455e13140d7bb9db.csv wiki_prices.csv
Archive: /content/drive/MyDrive/스터디/금융공학_퀀트_스터디/data/WIKI_PRICES.zip inflating: WIKI_PRICES_212b326a081eacca455e13140d7bb9db.csv
df = (pd.read_csv('wiki_prices.csv',
parse_dates=['date'],
index_col=['date', 'ticker'],
infer_datetime_format=True)
.sort_index())
print(df.info(null_counts=True))
with pd.HDFStore(DATA_STORE) as store:
store.put('quandl/wiki/prices', df)
<class 'pandas.core.frame.DataFrame'> MultiIndex: 15389314 entries, (Timestamp('1962-01-02 00:00:00'), 'ARNC') to (Timestamp('2018-03-27 00:00:00'), 'ZUMZ') Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 open 15388776 non-null float64 1 high 15389259 non-null float64 2 low 15389259 non-null float64 3 close 15389313 non-null float64 4 volume 15389314 non-null float64 5 ex-dividend 15389314 non-null float64 6 split_ratio 15389313 non-null float64 7 adj_open 15388776 non-null float64 8 adj_high 15389259 non-null float64 9 adj_low 15389259 non-null float64 10 adj_close 15389313 non-null float64 11 adj_volume 15389314 non-null float64 dtypes: float64(12) memory usage: 1.4+ GB None
with pd.HDFStore(DATA_STORE) as store:
sp500_stocks = store['sp500/stocks'].index
prices = store['quandl/wiki/prices'].adj_close.unstack('ticker').filter(sp500_stocks)
prices.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 14277 entries, 1962-01-02 to 2018-03-27 Columns: 455 entries, MMM to ZTS dtypes: float64(455) memory usage: 49.7 MB
monthly_returns = prices.loc['1988':'2017'].resample('M').last().pct_change().dropna(how='all').dropna(axis=1)
stocks = monthly_returns.columns
monthly_returns.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 359 entries, 1988-02-29 to 2017-12-31 Freq: M Columns: 192 entries, MMM to XEL dtypes: float64(192) memory usage: 541.3 KB
Compute Precision Matrix¶
cov = monthly_returns.cov()
precision_matrix = pd.DataFrame(inv(cov), index=stocks, columns=stocks)
kelly_allocation = monthly_returns.mean().dot(precision_matrix)
kelly_allocation.describe()
count 192.000000 mean 0.254534 std 3.377155 min -9.604965 25% -1.724042 50% 0.215364 75% 2.220286 max 9.393917 dtype: float64
kelly_allocation.sum()
48.87044647781847
Largest Portfolio Allocation¶
The plot shows the tickers that receive an allocation weight > 5x their value:
kelly_allocation[kelly_allocation.abs()>5].sort_values(ascending=False).plot.barh(figsize=(8, 10))
plt.yticks(fontsize=12)
sns.despine()
plt.tight_layout();
Performance vs SP500¶
The Kelly rule does really well. But it has also been computed from historical data..
ax = monthly_returns.loc['2010':].mul(kelly_allocation.div(kelly_allocation.sum())).sum(1).to_frame('Kelly').add(1).cumprod().sub(1).plot(figsize=(14,4));
sp500.filter(monthly_returns.loc['2010':].index).pct_change().add(1).cumprod().sub(1).to_frame('SP500').plot(ax=ax, legend=True)
plt.tight_layout()
sns.despine();
리스크 패리티¶
- 리스크 패리티를 간단하게 구현하면 상관관계를 무시하고, 특히 수익률 예측을 무시하고 분산의 역수에 따라 자산을 할당한다.
리스크 팩터 투자¶
팩터 투자의 개념은 포트폴리오 분산의 편익을 극대화하고자 자산군의 범위를 넘어 기본적 팩터 리스크에 레이블을 붙이는 것이다.
계층적 리스크 패리티¶
집라인으로 트레이딩과 포트폴리오 관리¶
신호 생성과 거래 실행 계획 수립¶
평균 분산 포트폴리오 최적화 구현¶
- 01_backtest_with_trades.ipynb 참고
02_backtest_with_pf_optimization.ipynb¶
MeanReversion backtest with Portfolio Optimization¶
4장에서는 군집 시장, 기본적인 데이터 및 대체 데이터로부터 알파 팩터의 계산을 시뮬레이션하기 위해 zipline
을 소개했습니다.
이제 우리는 알파 팩터를 활용하여 이전 장에서 개발한 사용자 정의 MeanReversion 팩터를 기반으로 매수 및 매도 신호를 도출하고 실행할 것입니다.
Imports¶
import warnings
warnings.filterwarnings('ignore')
!pip install PyPortfolioOpt
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting PyPortfolioOpt Downloading pyportfolioopt-1.5.5-py3-none-any.whl (61 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.9/61.9 kB 6.0 MB/s eta 0:00:00 Requirement already satisfied: cvxpy<2.0.0,>=1.1.19 in /usr/local/lib/python3.10/dist-packages (from PyPortfolioOpt) (1.3.1) Requirement already satisfied: numpy<2.0.0,>=1.22.4 in /usr/local/lib/python3.10/dist-packages (from PyPortfolioOpt) (1.22.4) Requirement already satisfied: pandas>=0.19 in /usr/local/lib/python3.10/dist-packages (from PyPortfolioOpt) (1.5.3) Requirement already satisfied: scipy<2.0,>=1.3 in /usr/local/lib/python3.10/dist-packages (from PyPortfolioOpt) (1.10.1) Requirement already satisfied: osqp>=0.4.1 in /usr/local/lib/python3.10/dist-packages (from cvxpy<2.0.0,>=1.1.19->PyPortfolioOpt) (0.6.2.post8) Requirement already satisfied: ecos>=2 in /usr/local/lib/python3.10/dist-packages (from cvxpy<2.0.0,>=1.1.19->PyPortfolioOpt) (2.0.12) Requirement already satisfied: scs>=1.1.6 in /usr/local/lib/python3.10/dist-packages (from cvxpy<2.0.0,>=1.1.19->PyPortfolioOpt) (3.2.3) Requirement already satisfied: setuptools>65.5.1 in /usr/local/lib/python3.10/dist-packages (from cvxpy<2.0.0,>=1.1.19->PyPortfolioOpt) (67.7.2) Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=0.19->PyPortfolioOpt) (2.8.2) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=0.19->PyPortfolioOpt) (2022.7.1) Requirement already satisfied: qdldl in /usr/local/lib/python3.10/dist-packages (from osqp>=0.4.1->cvxpy<2.0.0,>=1.1.19->PyPortfolioOpt) (0.1.7) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.1->pandas>=0.19->PyPortfolioOpt) (1.16.0) Installing collected packages: PyPortfolioOpt Successfully installed PyPortfolioOpt-1.5.5
import sys
from pytz import UTC
import logbook
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from logbook import (NestedSetup, NullHandler, Logger,
StreamHandler, StderrHandler,
INFO, WARNING, DEBUG, ERROR)
from zipline import run_algorithm
from zipline.api import (attach_pipeline,
date_rules,
time_rules,
get_datetime,
order_target_percent,
pipeline_output,
record, schedule_function,
get_open_orders,
calendars,
set_commission,
set_slippage)
from zipline.finance import commission, slippage
from zipline.pipeline import Pipeline, CustomFactor
from zipline.pipeline.factors import Returns, AverageDollarVolume
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models, objective_functions
from pypfopt import expected_returns
from pypfopt.exceptions import OptimizationError
from pyfolio.utils import extract_rets_pos_txn_from_zipline
sns.set_style('whitegrid')
Logging Setup¶
# setup stdout logging
format_string = '[{record.time: %H:%M:%S.%f}]: {record.level_name}: {record.message}'
zipline_logging = NestedSetup([NullHandler(level=DEBUG),
StreamHandler(sys.stdout, format_string=format_string, level=INFO),
StreamHandler(sys.stdout, format_string=format_string, level=WARNING),
StreamHandler(sys.stderr, level=ERROR)])
zipline_logging.push_application()
log = Logger('Algorithm')
Algo Settings¶
# Settings
MONTH = 21
YEAR = 12 * MONTH
N_LONGS = 50
N_SHORTS = 50
MIN_POS = 5
VOL_SCREEN = 1000
import pytz
# start = pd.Timestamp('2013-01-01', tz=UTC)
# end = pd.Timestamp('2017-01-01', tz=UTC)
start = pd.Timestamp('2013-01-01', tz=pytz.UTC).replace(tzinfo=None)
end = pd.Timestamp('2017-01-01', tz=pytz.UTC).replace(tzinfo=None)
capital_base = 1e7
Mean Reversion Factor¶
class MeanReversion(CustomFactor):
"""Compute ratio of latest monthly return to 12m average,
normalized by std dev of monthly returns"""
inputs = [Returns(window_length=MONTH)]
window_length = YEAR
def compute(self, today, assets, out, monthly_returns):
df = pd.DataFrame(monthly_returns)
factor = df.iloc[-1].sub(df.mean()).div(df.std())
out[:] = factor
Create Pipeline¶
compute_factors()
메서드로 생성된 파이프라인은 마지막 한 달 수익률과 연간 평균의 표준 편차로 정규화한 값에서 가장 큰 음수와 양수 편차를 가진 25개 주식에 대한 long과 short 열이 포함된 테이블을 반환합니다. 또한, 지난 30개 거래일 동안의 평균 거래량이 가장 높은 500개 주식으로 유니버스를 제한합니다.
def compute_factors():
"""Create factor pipeline incl. mean reversion,
filtered by 30d Dollar Volume; capture factor ranks"""
mean_reversion = MeanReversion()
dollar_volume = AverageDollarVolume(window_length=30)
return Pipeline(columns={'longs' : mean_reversion.bottom(N_LONGS),
'shorts' : mean_reversion.top(N_SHORTS),
'ranking': mean_reversion.rank(ascending=False)},
screen=dollar_volume.top(VOL_SCREEN))
Before_trading_start()
ensures the daily execution of the pipeline and the recording of the results, including the current prices.
def before_trading_start(context, data):
"""Run factor pipeline"""
context.factor_data = pipeline_output('factor_pipeline')
record(factor_data=context.factor_data.ranking)
assets = context.factor_data.index
record(prices=data.current(assets, 'price'))
Set up Rebalancing¶
새로운 rebalance()
메서드는 파이프라인에서 롱 포지션과 숏 포지션에 플래그가 지정된 자산에 대한 거래 주문을 exec_trades()
메서드에 제출합니다. 이때 양의 가중치와 음의 가중치가 동일합니다.
또한, 팩터 신호에 더 이상 포함되지 않은 현재 보유 자산은 매각합니다.
def exec_trades(data, positions):
"""Place orders for assets using target portfolio percentage"""
for asset, target_percent in positions.items():
if data.can_trade(asset) and not get_open_orders(asset):
order_target_percent(asset, target_percent)
def rebalance(context, data):
"""Compute long, short and obsolete holdings; place orders"""
factor_data = context.factor_data
assets = factor_data.index
longs = assets[factor_data.longs]
shorts = assets[factor_data.shorts]
divest = context.portfolio.positions.keys() - longs.union(shorts)
exec_trades(data, positions={asset: 0 for asset in divest})
log.info('{} | {:11,.0f}'.format(get_datetime().date(),
context.portfolio.portfolio_value))
# get price history
prices = data.history(assets, fields='price',
bar_count=252+1, # for 1 year of returns
frequency='1d')
# get optimal weights if sufficient candidates
if len(longs) > MIN_POS and len(shorts) > MIN_POS:
try:
long_weights = optimize_weights(prices.loc[:, longs])
short_weights = optimize_weights(prices.loc[:, shorts], short=True)
exec_trades(data, positions=long_weights)
exec_trades(data, positions=short_weights)
except Exception as e:
log.warn('{} {}'.format(get_datetime().date(), e))
# exit remaining positions
divest_pf = {asset: 0 for asset in context.portfolio.positions.keys()}
exec_trades(data, positions=divest_pf)
Optimize Portfolio Weights¶
def optimize_weights(prices, short=False):
returns = expected_returns.mean_historical_return(
prices=prices, frequency=252)
cov = risk_models.sample_cov(prices=prices, frequency=252)
# get weights that maximize the Sharpe ratio
ef = EfficientFrontier(expected_returns=returns,
cov_matrix=cov,
weight_bounds=(0, 1),
solver='SCS')
ef.max_sharpe()
if short:
return {asset: -weight for asset, weight in ef.clean_weights().items()}
else:
return ef.clean_weights()
Initialize Backtest¶
rebalance()
메서드는 schedule_function()
유틸리티에서 설정한 date_rules
및 time_rules
에 따라 실행됩니다. 이는 내장된 US_EQUITIES
캘린더에 따라 시장 오픈 직후 주간 시작 시점에 실행됩니다 (자세한 규칙은 문서를 참조하세요).
또한, 상대적인 용어와 최소 금액으로 거래 수수료를 지정할 수 있습니다. 슬리피지를 정의할 수 있는 옵션도 있습니다. 슬리피지란 거래 결정과 실행 사이에서 가격이 불리한 방향으로 변동되는 비용을 나타냅니다.
def initialize(context):
"""Setup: register pipeline, schedule rebalancing,
and set trading params"""
attach_pipeline(compute_factors(), 'factor_pipeline')
schedule_function(rebalance,
date_rules.week_start(),
time_rules.market_open(),
calendar=calendars.US_EQUITIES)
set_commission(us_equities=commission.PerShare(cost=0.00075, min_trade_cost=.01))
set_slippage(us_equities=slippage.VolumeShareSlippage(volume_limit=0.0025, price_impact=0.01))
Run Algorithm¶
The algorithm executes upon calling the run_algorithm()
function and returns the backtest performance DataFrame
.
backtest = run_algorithm(start=start,
end=end,
initialize=initialize,
before_trading_start=before_trading_start,
bundle='quandl',
capital_base=capital_base)
[ 15:28:05.209882]: INFO: 2013-01-07 | 10,000,000 [ 15:28:12.336842]: INFO: 2013-01-14 | 9,922,439 [ 15:28:16.205004]: INFO: 2013-01-22 | 9,913,618 [ 15:28:20.252310]: INFO: 2013-01-28 | 9,968,673 [ 15:28:22.931514]: INFO: 2013-02-04 | 10,079,413 [ 15:28:27.774310]: INFO: 2013-02-11 | 10,103,568 [ 15:28:31.879102]: INFO: 2013-02-19 | 9,771,114 [ 15:28:34.686773]: INFO: 2013-02-25 | 10,061,852 [ 15:28:37.697476]: INFO: 2013-03-04 | 10,162,347 [ 15:28:40.485743]: INFO: 2013-03-11 | 9,563,307 [ 15:28:47.060076]: INFO: 2013-03-18 | 9,311,865 [ 15:28:51.193686]: INFO: 2013-03-25 | 9,064,145 [ 15:28:53.863917]: INFO: 2013-04-01 | 8,643,987 [ 15:28:58.232033]: INFO: 2013-04-08 | 8,722,025 [ 15:29:01.241592]: INFO: 2013-04-15 | 8,552,162 [ 15:29:04.436453]: INFO: 2013-04-22 | 8,186,980 [ 15:29:07.504017]: INFO: 2013-04-29 | 9,080,224 [ 15:29:12.948123]: INFO: 2013-05-06 | 8,277,438 [ 15:29:18.551368]: INFO: 2013-05-13 | 8,018,017 [ 15:29:24.600471]: INFO: 2013-05-20 | 7,805,300 [ 15:29:28.301099]: INFO: 2013-05-28 | 6,806,387 [ 15:29:31.569817]: INFO: 2013-06-03 | 7,034,401 [ 15:29:43.733812]: INFO: 2013-06-10 | 7,315,183 [ 15:29:48.610463]: INFO: 2013-06-17 | 7,620,282 [ 15:29:53.385141]: INFO: 2013-06-24 | 6,403,048 [ 15:30:00.608025]: INFO: 2013-07-01 | 5,958,425 [ 15:30:04.067293]: INFO: 2013-07-08 | 5,427,617 [ 15:30:10.774300]: INFO: 2013-07-15 | 5,956,522 [ 15:30:18.970851]: INFO: 2013-07-22 | 6,237,724 [ 15:30:24.019201]: INFO: 2013-07-29 | 6,059,631 [ 15:30:27.380578]: INFO: 2013-08-05 | 6,453,880 [ 15:30:30.861626]: INFO: 2013-08-12 | 6,967,990 [ 15:30:34.902838]: INFO: 2013-08-19 | 5,543,081 [ 15:30:40.907582]: INFO: 2013-08-26 | 6,376,669 [ 15:30:45.018568]: INFO: 2013-09-03 | 6,426,317 [ 15:30:51.588521]: INFO: 2013-09-09 | 7,392,377 [ 15:30:56.017063]: INFO: 2013-09-16 | 7,314,860 [ 15:31:00.480904]: INFO: 2013-09-23 | 7,812,678 [ 15:31:06.218358]: INFO: 2013-09-30 | 6,989,440 [ 15:31:11.516907]: INFO: 2013-10-07 | 6,107,873 [ 15:31:15.025632]: INFO: 2013-10-14 | 6,255,658 [ 15:31:19.183465]: INFO: 2013-10-21 | 7,069,490 [ 15:31:21.975849]: INFO: 2013-10-28 | 7,059,297 [ 15:31:25.404589]: INFO: 2013-11-04 | 7,314,789 [ 15:31:30.012332]: INFO: 2013-11-11 | 6,430,068 [ 15:31:33.431306]: INFO: 2013-11-18 | 6,851,297 [ 15:31:36.805003]: INFO: 2013-11-25 | 6,796,007 [ 15:31:39.338423]: INFO: 2013-12-02 | 7,138,129 [ 15:31:42.152451]: INFO: 2013-12-09 | 6,885,838 [ 15:31:46.055818]: INFO: 2013-12-16 | 6,547,864 [ 15:31:48.933954]: INFO: 2013-12-23 | 7,444,150 [ 15:31:51.629436]: INFO: 2013-12-30 | 7,447,193 [ 15:31:54.475776]: INFO: 2014-01-06 | 7,374,757 [ 15:31:58.862744]: INFO: 2014-01-13 | 7,084,666 [ 15:32:05.139646]: INFO: 2014-01-21 | 6,751,980 [ 15:32:10.022481]: INFO: 2014-01-27 | 6,524,959 [ 15:32:15.647568]: INFO: 2014-02-03 | 6,588,644 [ 15:32:18.441150]: INFO: 2014-02-10 | 7,065,852 [ 15:32:21.074141]: INFO: 2014-02-18 | 7,890,301 [ 15:32:24.462675]: INFO: 2014-02-24 | 7,673,460 [ 15:32:27.186672]: INFO: 2014-03-03 | 8,763,786 [ 15:32:29.381468]: INFO: 2014-03-10 | 8,840,749 [ 15:32:31.416219]: INFO: 2014-03-17 | 8,784,623 [ 15:32:33.709665]: INFO: 2014-03-24 | 8,376,470 [ 15:32:37.649800]: INFO: 2014-03-31 | 8,447,350 [ 15:32:40.546692]: INFO: 2014-04-07 | 7,680,084 [ 15:32:43.208716]: INFO: 2014-04-14 | 7,083,656 [ 15:32:45.610758]: INFO: 2014-04-21 | 7,696,488 [ 15:32:48.150929]: INFO: 2014-04-28 | 7,575,256 [ 15:32:53.490089]: INFO: 2014-05-05 | 7,953,879 [ 15:32:55.965383]: INFO: 2014-05-12 | 8,167,123 [ 15:32:58.393784]: INFO: 2014-05-19 | 7,087,391 [ 15:33:00.823891]: INFO: 2014-05-27 | 7,551,585 [ 15:33:04.417942]: INFO: 2014-06-02 | 7,381,840 [ 15:33:07.273265]: INFO: 2014-06-09 | 7,705,943 [ 15:33:09.674062]: INFO: 2014-06-16 | 7,454,974 [ 15:33:12.180455]: INFO: 2014-06-23 | 7,465,700 [ 15:33:14.745672]: INFO: 2014-06-30 | 7,801,411 [ 15:33:17.973700]: INFO: 2014-07-07 | 8,037,769 [ 15:33:21.136466]: INFO: 2014-07-14 | 7,956,137 [ 15:33:26.785734]: INFO: 2014-07-21 | 8,362,074 [ 15:33:29.563808]: INFO: 2014-07-28 | 8,230,713 [ 15:33:33.954937]: INFO: 2014-08-04 | 8,328,381 [ 15:33:36.293655]: INFO: 2014-08-11 | 8,847,871 [ 15:33:38.375549]: INFO: 2014-08-18 | 9,353,642 [ 15:33:41.504057]: INFO: 2014-08-25 | 9,398,638 [ 15:33:44.658331]: INFO: 2014-09-02 | 9,512,592 [ 15:33:47.804178]: INFO: 2014-09-08 | 8,988,873 [ 15:33:50.143344]: INFO: 2014-09-15 | 9,256,590 [ 15:33:52.486770]: INFO: 2014-09-22 | 9,217,425 [ 15:33:54.781218]: INFO: 2014-09-29 | 8,063,351 [ 15:34:01.093699]: INFO: 2014-10-06 | 6,260,688 [ 15:34:05.911104]: INFO: 2014-10-13 | 4,449,537 [ 15:34:08.326840]: INFO: 2014-10-20 | 5,543,004 [ 15:34:12.663757]: INFO: 2014-10-27 | 6,187,244 [ 15:34:16.624589]: INFO: 2014-11-03 | 7,417,382 [ 15:34:18.790211]: INFO: 2014-11-10 | 7,315,291 [ 15:34:20.913935]: INFO: 2014-11-17 | 7,709,553 [ 15:34:22.933637]: INFO: 2014-11-24 | 8,268,947 [ 15:34:25.119647]: INFO: 2014-12-01 | 7,409,529 [ 15:34:30.775352]: INFO: 2014-12-08 | 7,795,740 [ 15:34:32.703330]: INFO: 2014-12-15 | 7,712,443 [ 15:34:37.279809]: INFO: 2014-12-22 | 8,441,904 [ 15:34:39.999221]: INFO: 2014-12-29 | 8,346,494 [ 15:34:42.242581]: INFO: 2015-01-05 | 7,411,232 [ 15:34:44.408465]: INFO: 2015-01-12 | 7,603,805 [ 15:34:48.162381]: INFO: 2015-01-20 | 7,948,620 [ 15:34:49.924870]: INFO: 2015-01-26 | 8,293,004 [ 15:34:53.140219]: INFO: 2015-02-02 | 7,393,902 [ 15:34:56.438178]: INFO: 2015-02-09 | 7,566,054 [ 15:34:58.444139]: INFO: 2015-02-17 | 7,795,055 [ 15:35:00.247841]: INFO: 2015-02-23 | 7,529,032 [ 15:35:03.898662]: INFO: 2015-03-02 | 6,894,230 [ 15:35:07.362731]: INFO: 2015-03-09 | 6,131,957 [ 15:35:09.680907]: INFO: 2015-03-16 | 6,023,140 [ 15:35:11.476945]: INFO: 2015-03-23 | 6,126,662 [ 15:35:13.254684]: INFO: 2015-03-30 | 5,877,084 [ 15:35:14.748410]: INFO: 2015-04-06 | 5,866,191 [ 15:35:16.899832]: INFO: 2015-04-13 | 6,030,063 [ 15:35:18.792724]: INFO: 2015-04-20 | 5,885,831 [ 15:35:22.695884]: INFO: 2015-04-27 | 5,604,592 [ 15:35:24.567804]: INFO: 2015-05-04 | 5,754,288 [ 15:35:26.141357]: INFO: 2015-05-11 | 5,905,318 [ 15:35:27.784957]: INFO: 2015-05-18 | 5,954,238 [ 15:35:29.395914]: INFO: 2015-05-26 | 5,897,359 [ 15:35:30.695304]: INFO: 2015-06-01 | 6,386,742 [ 15:35:32.738817]: INFO: 2015-06-08 | 6,592,452 [ 15:35:35.680432]: INFO: 2015-06-15 | 6,383,981 [ 15:35:37.236717]: INFO: 2015-06-22 | 7,172,716 [ 15:35:38.774235]: INFO: 2015-06-29 | 6,328,620 [ 15:35:40.000858]: INFO: 2015-07-06 | 6,512,688 [ 15:35:41.527338]: INFO: 2015-07-13 | 6,956,107 [ 15:35:44.704083]: INFO: 2015-07-20 | 7,081,029 [ 15:35:49.459907]: INFO: 2015-07-27 | 7,070,884 [ 15:35:51.448172]: INFO: 2015-08-03 | 7,351,940 [ 15:35:53.446183]: INFO: 2015-08-10 | 7,596,236 [ 15:35:55.925017]: INFO: 2015-08-17 | 7,686,878 [ 15:35:57.930008]: INFO: 2015-08-24 | 5,994,274 [ 15:36:02.350416]: INFO: 2015-08-31 | 6,661,726 [ 15:36:05.417795]: INFO: 2015-09-08 | 7,122,140 [ 15:36:09.475706]: INFO: 2015-09-14 | 7,103,529 [ 15:36:11.433908]: INFO: 2015-09-21 | 7,132,189 [ 15:36:14.621798]: INFO: 2015-09-28 | 6,914,915 [ 15:36:17.004874]: INFO: 2015-10-05 | 7,456,965 [ 15:36:18.996549]: INFO: 2015-10-12 | 8,136,041 [ 15:36:21.870902]: INFO: 2015-10-19 | 8,191,121 [ 15:36:25.796111]: INFO: 2015-10-26 | 8,475,349 [ 15:36:29.028917]: INFO: 2015-11-02 | 9,425,105 [ 15:36:30.995983]: INFO: 2015-11-09 | 8,860,147 [ 15:36:33.048024]: INFO: 2015-11-16 | 8,465,235 [ 15:36:35.125967]: INFO: 2015-11-23 | 9,239,240 [ 15:36:37.047748]: INFO: 2015-11-30 | 9,592,592 [ 15:36:41.061672]: INFO: 2015-12-07 | 9,188,250 [ 15:36:44.342028]: INFO: 2015-12-14 | 8,833,537 [ 15:36:47.403673]: INFO: 2015-12-21 | 9,217,688 [ 15:36:49.347337]: INFO: 2015-12-28 | 9,202,850 [ 15:36:51.289309]: INFO: 2016-01-04 | 8,926,314 [ 15:36:54.139061]: INFO: 2016-01-11 | 7,904,791 [ 15:36:57.002495]: INFO: 2016-01-19 | 7,599,211 [ 15:37:00.593619]: INFO: 2016-01-25 | 7,293,390 [ 15:37:01.618650]: WARNING: 2016-01-25 at least one of the assets must have an expected return exceeding the risk-free rate [ 15:37:02.758765]: INFO: 2016-02-01 | 7,395,410 [ 15:37:05.698102]: INFO: 2016-02-08 | 4,671,150 [ 15:37:09.987328]: INFO: 2016-02-16 | 5,283,421 [ 15:37:12.468647]: INFO: 2016-02-22 | 7,174,463 [ 15:37:14.655291]: INFO: 2016-02-29 | 6,527,776 [ 15:37:16.838204]: INFO: 2016-03-07 | 7,512,732 [ 15:37:18.980366]: INFO: 2016-03-14 | 9,004,996 [ 15:37:23.862928]: INFO: 2016-03-21 | 9,524,368 [ 15:37:24.934573]: WARNING: 2016-03-21 at least one of the assets must have an expected return exceeding the risk-free rate [ 15:37:26.196454]: INFO: 2016-03-28 | 9,379,762 [ 15:37:29.867153]: INFO: 2016-04-04 | 9,795,179 [ 15:37:31.966842]: INFO: 2016-04-11 | 10,143,231 [ 15:37:34.980399]: INFO: 2016-04-18 | 10,814,989 [ 15:37:38.620581]: INFO: 2016-04-25 | 10,868,277 [ 15:37:41.986006]: INFO: 2016-05-02 | 11,943,524 [ 15:37:44.825666]: INFO: 2016-05-09 | 12,976,861 [ 15:37:48.200783]: INFO: 2016-05-16 | 12,189,811 [ 15:37:51.359250]: INFO: 2016-05-23 | 10,248,097 [ 15:37:52.616261]: WARNING: 2016-05-23 at least one of the assets must have an expected return exceeding the risk-free rate [ 15:37:53.714245]: INFO: 2016-05-31 | 11,122,834 [ 15:37:55.940707]: INFO: 2016-06-06 | 11,335,116 [ 15:37:58.365237]: INFO: 2016-06-13 | 11,150,315 [ 15:38:01.529020]: INFO: 2016-06-20 | 11,488,019 [ 15:38:04.163019]: WARNING: 2016-06-20 at least one of the assets must have an expected return exceeding the risk-free rate [ 15:38:05.251714]: INFO: 2016-06-27 | 10,458,929 [ 15:38:09.723148]: INFO: 2016-07-05 | 10,983,445 [ 15:38:11.564999]: INFO: 2016-07-11 | 11,262,451 [ 15:38:14.162070]: INFO: 2016-07-18 | 10,950,302 [ 15:38:21.043706]: INFO: 2016-07-25 | 10,723,369 [ 15:38:23.133483]: INFO: 2016-08-01 | 11,298,769 [ 15:38:26.502634]: INFO: 2016-08-08 | 11,024,343 [ 15:38:31.728101]: INFO: 2016-08-15 | 10,550,368 [ 15:38:33.863244]: INFO: 2016-08-22 | 10,153,961 [ 15:38:36.430050]: INFO: 2016-08-29 | 10,043,630 [ 15:38:38.545744]: INFO: 2016-09-06 | 9,762,406 [ 15:38:40.392330]: INFO: 2016-09-12 | 10,184,908 [ 15:38:43.962184]: INFO: 2016-09-19 | 9,924,590 [ 15:38:46.070265]: INFO: 2016-09-26 | 10,410,476 [ 15:38:48.160371]: INFO: 2016-10-03 | 9,895,028 [ 15:38:50.221854]: INFO: 2016-10-10 | 9,017,541 [ 15:38:52.351918]: INFO: 2016-10-17 | 8,658,364 [ 15:38:55.853564]: INFO: 2016-10-24 | 8,716,966 [ 15:39:00.411377]: INFO: 2016-10-31 | 9,849,416 [ 15:39:03.566881]: INFO: 2016-11-07 | 10,509,631 [ 15:39:07.957534]: INFO: 2016-11-14 | 9,787,448 [ 15:39:11.759641]: INFO: 2016-11-21 | 7,318,758 [ 15:39:13.601311]: INFO: 2016-11-28 | 8,182,983 [ 15:39:15.597691]: INFO: 2016-12-05 | 6,721,020 [ 15:39:17.565273]: INFO: 2016-12-12 | 6,420,622 [ 15:39:20.926512]: INFO: 2016-12-19 | 4,309,854 [ 15:39:25.896338]: INFO: 2016-12-27 | 3,487,382
Extract pyfolio Inputs¶
The extract_rets_pos_txn_from_zipline
utility provided by pyfolio
extracts the data used to compute performance metrics.
returns, positions, transactions = extract_rets_pos_txn_from_zipline(backtest)
Persist Results for use with pyfolio
¶
with pd.HDFStore('backtests.h5') as store:
store.put('returns/pf_opt', returns)
store.put('transactions/pf_opt', transactions)
with pd.HDFStore('backtests.h5') as store:
returns_pf = store['returns/pf_opt']
tx_pf = store['transactions/pf_opt']
returns_ew = store['returns/equal_weight']
tx_ew = store['transactions/equal_weight']
Plot Results¶
fig, axes= plt.subplots(nrows=2, figsize=(14,6))
returns.add(1).cumprod().sub(1).plot(ax=axes[0], title='Cumulative Returns')
transactions.groupby(transactions.dt.dt.day).txn_dollars.sum().cumsum().plot(ax=axes[1], title='Cumulative Transactions')
sns.despine()
fig.tight_layout();
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(16, 8), sharey='col')
returns_ew.add(1).cumprod().sub(1).plot(ax=axes[0][0],
title='Cumulative Returns - Equal Weight')
returns_pf.add(1).cumprod().sub(1).plot(ax=axes[1][0],
title='Cumulative Returns - Mean-Variance Optimization')
tx_ew.groupby(tx_ew.dt.dt.day).txn_dollars.sum().cumsum().plot(ax=axes[0][1],
title='Cumulative Transactions - Equal Weight')
tx_pf.groupby(tx_pf.dt.dt.day).txn_dollars.sum().cumsum().plot(ax=axes[1][1],
title='Cumulative Transactions - Mean-Variance Optimization')
fig.suptitle('Equal Weight vs Mean-Variance Optimization', fontsize=16)
sns.despine()
fig.tight_layout()
fig.subplots_adjust(top=.9)
파이폴리오를 이용한 백테스팅 성과 측정¶
- 파이폴리오는 풍부한 메트릭과 시각화 집한을 사용해 표본 내 및 표본 외 모두에서 포트폴리오 성과 분석을 용이하게 함
수익률과 벤치마크 입력 생성¶
03_pyfolio_demo.ipynb¶
From zipline
to pyfolio
¶
Pyfolio는 많은 표준 지표를 사용하여 샘플 내 및 샘플 외에서 포트폴리오의 성과와 리스크를 분석하는 데 도움을 줍니다. 이는 수익, 포지션 및 거래 분석을 다루는 티어 시트를 생성하며, 시장 스트레스 기간 동안의 이벤트 리스크를 다양한 내장된 시나리오를 사용하여 분석하며, 베이지안 샘플 외 성과 분석도 포함됩니다.
- Quantopian Inc.의 오픈 소스 백테스터
- Quantopian.com을 구동합니다.
- 최신 포트폴리오 및 리스크 분석 도구
- 거래 비용 및 슬리피지에 대한 다양한 모델
- 오픈 소스이며 무료로 사용 가능 (Apache v2 라이선스)
- 다음과 같이 사용할 수 있습니다:
- 독립적으로 사용
- Zipline과 함께 사용
- Quantopian에서 사용
Imports & Settings¶
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from pathlib import Path
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from pyfolio.utils import extract_rets_pos_txn_from_zipline
from pyfolio.plotting import (plot_perf_stats,
show_perf_stats,
plot_rolling_beta,
plot_rolling_returns,
plot_rolling_sharpe,
plot_drawdown_periods,
plot_drawdown_underwater)
from pyfolio.timeseries import perf_stats, extract_interesting_date_ranges
sns.set_style('whitegrid')
Converting data from zipline to pyfolio¶
with pd.HDFStore('backtests.h5') as store:
backtest = store['backtest/equal_weight']
backtest.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 1008 entries, 2013-01-02 00:00:00+00:00 to 2016-12-30 00:00:00+00:00 Data columns (total 39 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 period_open 1008 non-null datetime64[ns, UTC] 1 period_close 1008 non-null datetime64[ns, UTC] 2 longs_count 1008 non-null int64 3 shorts_count 1008 non-null int64 4 long_value 1008 non-null float64 5 short_value 1008 non-null float64 6 long_exposure 1008 non-null float64 7 pnl 1008 non-null float64 8 short_exposure 1008 non-null float64 9 capital_used 1008 non-null float64 10 orders 1008 non-null object 11 transactions 1008 non-null object 12 gross_leverage 1008 non-null float64 13 positions 1008 non-null object 14 net_leverage 1008 non-null float64 15 starting_exposure 1008 non-null float64 16 returns 1008 non-null float64 17 ending_exposure 1008 non-null float64 18 starting_value 1008 non-null float64 19 ending_value 1008 non-null float64 20 starting_cash 1008 non-null float64 21 ending_cash 1008 non-null float64 22 portfolio_value 1008 non-null float64 23 factor_data 1008 non-null object 24 prices 1008 non-null object 25 algo_volatility 1007 non-null float64 26 benchmark_period_return 1008 non-null float64 27 benchmark_volatility 1007 non-null float64 28 algorithm_period_return 1008 non-null float64 29 alpha 0 non-null object 30 beta 0 non-null object 31 sharpe 1004 non-null float64 32 sortino 1004 non-null float64 33 max_drawdown 1008 non-null float64 34 max_leverage 1008 non-null float64 35 excess_return 1008 non-null float64 36 treasury_period_return 1008 non-null float64 37 trading_days 1008 non-null int64 38 period_label 1008 non-null object dtypes: datetime64[ns, UTC](2), float64(26), int64(3), object(8) memory usage: 315.0+ KB
pyfolio
는 포트폴리오 수익 및 포지션 데이터에 의존하며, 거래 활동의 거래 비용과 슬리피지 손실도 고려할 수 있습니다. 지표는 독립적으로 사용할 수 있는 empyrical 라이브러리를 사용하여 계산됩니다. zipline 백테스팅 엔진에서 생성된 성과 DataFrame은 필요한 pyfolio 입력으로 변환될 수 있습니다.
returns, positions, transactions = extract_rets_pos_txn_from_zipline(backtest)
returns.head().append(returns.tail())
2013-01-02 00:00:00+00:00 0.000000 2013-01-03 00:00:00+00:00 0.000000 2013-01-04 00:00:00+00:00 0.000000 2013-01-07 00:00:00+00:00 0.000000 2013-01-08 00:00:00+00:00 -0.000005 2016-12-23 00:00:00+00:00 -0.000233 2016-12-27 00:00:00+00:00 0.000160 2016-12-28 00:00:00+00:00 -0.000847 2016-12-29 00:00:00+00:00 0.000735 2016-12-30 00:00:00+00:00 -0.000606 Name: returns, dtype: float64
positions.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 1004 entries, 2013-01-08 00:00:00+00:00 to 2016-12-30 00:00:00+00:00 Columns: 750 entries, Equity(0 [A]) to cash dtypes: float64(750) memory usage: 5.8 MB
positions.columns = [c for c in positions.columns[:-1]] + ['cash']
positions.index = positions.index.normalize()
positions.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 1004 entries, 2013-01-08 00:00:00+00:00 to 2016-12-30 00:00:00+00:00 Columns: 750 entries, Equity(0 [A]) to cash dtypes: float64(750) memory usage: 5.8 MB
transactions.symbol = transactions.symbol.apply(lambda x: x.symbol)
transactions.head().append(transactions.tail())
sid | symbol | price | order_id | amount | commission | dt | txn_dollars | |
---|---|---|---|---|---|---|---|---|
2013-01-08 21:00:00+00:00 | Equity(85 [AGN]) | AGN | 86.680005 | 39f2523e32b9401ea1b75d590597c881 | 2334 | None | 2013-01-08 21:00:00+00:00 | -202311.131306 |
2013-01-08 21:00:00+00:00 | Equity(213 [ARIA]) | ARIA | 19.651001 | 7147cb58a92e4269ae32e97550303b1d | 7590 | None | 2013-01-08 21:00:00+00:00 | -149151.099321 |
2013-01-08 21:00:00+00:00 | Equity(367 [BIIB]) | BIIB | 144.390001 | 0e8ca4b6f68a4b8d9b3418c65231a3a5 | 1365 | None | 2013-01-08 21:00:00+00:00 | -197092.351185 |
2013-01-08 21:00:00+00:00 | Equity(811 [DFS]) | DFS | 40.090001 | c1fae142af7c45d29bd9e95bc591bf9f | 5073 | None | 2013-01-08 21:00:00+00:00 | -203376.572741 |
2013-01-08 21:00:00+00:00 | Equity(1059 [FDO]) | FDO | 57.320002 | 0b81f1ebe781419bbc4868e677a61942 | 3496 | None | 2013-01-08 21:00:00+00:00 | -200390.728571 |
2016-12-29 21:00:00+00:00 | Equity(66 [AEO]) | AEO | 15.270000 | 0c9269cc45b8490bbc7c101be8901554 | 3461 | None | 2016-12-29 21:00:00+00:00 | -52849.470534 |
2016-12-29 21:00:00+00:00 | Equity(833 [DKS]) | DKS | 52.380000 | 8c2cf6487754440cb112b990c9f80f38 | 1063 | None | 2016-12-29 21:00:00+00:00 | -55679.940343 |
2016-12-29 21:00:00+00:00 | Equity(1757 [MAT]) | MAT | 27.630000 | defbda2590cb49e595778107d0f1b4f4 | 1183 | None | 2016-12-29 21:00:00+00:00 | -32686.290035 |
2016-12-30 21:00:00+00:00 | Equity(2181 [PDCO]) | PDCO | 41.030000 | aa3ce589f8dd4000992de4fbcfdb7034 | -119 | None | 2016-12-30 21:00:00+00:00 | 4882.569999 |
2016-12-30 21:00:00+00:00 | Equity(2953 [URBN]) | URBN | 28.480000 | 297bfa24b05d48c9896e4f0e3d5745f8 | -1006 | None | 2016-12-30 21:00:00+00:00 | 28650.879685 |
HDF_PATH = Path('assets.h5')
df = pd.read_csv('https://raw.githubusercontent.com/FE-Quant-Study/Machine-Learning-for-Algorithmic-Trading-Second-Edition/master/data/us_equities_meta_data.csv')
df.info()
with pd.HDFStore(DATA_STORE) as store:
store.put('us_equities/stocks', df.set_index('ticker'))
<class 'pandas.core.frame.DataFrame'> RangeIndex: 6834 entries, 0 to 6833 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ticker 6834 non-null object 1 name 6834 non-null object 2 lastsale 6718 non-null float64 3 marketcap 5766 non-null float64 4 ipoyear 3038 non-null float64 5 sector 5288 non-null object 6 industry 5288 non-null object dtypes: float64(3), object(4) memory usage: 373.9+ KB
Sector Map¶
assets = positions.columns[:-1]
with pd.HDFStore(HDF_PATH) as store:
df = store.get('us_equities/stocks')['sector'].dropna()
df = df[~df.index.duplicated()]
sector_map = df.reindex(assets).fillna('Unknown').to_dict()
Benchmark¶
with pd.HDFStore(HDF_PATH) as store:
benchmark_rets = store['sp500/fred'].close.pct_change()
benchmark_rets.name = 'S&P500'
benchmark_rets = benchmark_rets.tz_localize('UTC').filter(returns.index)
benchmark_rets.tail()
DATE 2016-12-23 00:00:00+00:00 0.001252 2016-12-27 00:00:00+00:00 0.002248 2016-12-28 00:00:00+00:00 -0.008357 2016-12-29 00:00:00+00:00 -0.000293 2016-12-30 00:00:00+00:00 -0.004637 Name: S&P500, dtype: float64
perf_stats(returns=returns,
factor_returns=benchmark_rets)
# positions=positions,
# transactions=transactions)
Annual return 0.019619 Cumulative returns 0.080817 Annual volatility 0.047487 Sharpe ratio 0.432879 Calmar ratio 0.336024 Stability 0.555919 Max drawdown -0.058387 Omega ratio 1.085094 Sortino ratio 0.630497 Skew 0.223701 Kurtosis 6.125539 Tail ratio 0.988875 Daily value at risk -0.005901 Alpha 0.010635 Beta 0.136577 dtype: float64
# benchmark_rets의 시작 날짜와 종료 날짜 가져오기
start_date = benchmark_rets.index[0]
end_date = benchmark_rets.index[-1]
fig, ax = plt.subplots(figsize=(14, 5))
plot_perf_stats(returns=returns[start_date:end_date],
factor_returns=benchmark_rets,
ax=ax)
sns.despine()
fig.tight_layout();
Returns Analysis¶
거래 전략을 테스트하는 것은 히스토리컬 데이터를 대상으로 백테스팅을 수행하여 알파 팩터 매개변수를 세밀하게 조정하는 것과 함께, 새로운 시장 데이터에 대한 포워드 테스트를 수행하여 전략이 샘플 외에서도 잘 수행되는지 또는 매개변수가 특정 히스토리컬 상황에 지나치게 맞춰져 있는지를 검증하는 것을 포함합니다.
Pyfolio는 워크포워드 테스트를 시뮬레이션하기 위해 샘플 외 기간을 지정할 수 있도록 합니다. 전략을 테스트할 때 통계적으로 신뢰할 수 있는 결과를 얻기 위해 고려해야 할 다양한 측면이 있으며, 여기에서 다루도록 하겠습니다.
benchmark_rets
DATE 2013-06-17 00:00:00+00:00 NaN 2013-06-18 00:00:00+00:00 0.007791 2013-06-19 00:00:00+00:00 -0.013851 2013-06-20 00:00:00+00:00 -0.025010 2013-06-21 00:00:00+00:00 0.002670 ... 2016-12-23 00:00:00+00:00 0.001252 2016-12-27 00:00:00+00:00 0.002248 2016-12-28 00:00:00+00:00 -0.008357 2016-12-29 00:00:00+00:00 -0.000293 2016-12-30 00:00:00+00:00 -0.004637 Name: S&P500, Length: 894, dtype: float64
oos_date = '2016-01-01'
show_perf_stats(returns=returns,
factor_returns=benchmark_rets,
positions=positions,
transactions=transactions,
live_start_date=oos_date)
Start date | 2013-01-02 | |||
---|---|---|---|---|
End date | 2016-12-30 | |||
In-sample months | 36 | |||
Out-of-sample months | 12 | |||
In-sample | Out-of-sample | All | ||
Annual return | 1.3% | 4.0% | 2.0% | |
Cumulative returns | 4.0% | 4.0% | 8.1% | |
Annual volatility | 4.7% | 4.9% | 4.7% | |
Sharpe ratio | 0.30 | 0.82 | 0.43 | |
Calmar ratio | 0.22 | 1.28 | 0.34 | |
Stability | 0.15 | 0.76 | 0.56 | |
Max drawdown | -5.8% | -3.1% | -5.8% | |
Omega ratio | 1.06 | 1.17 | 1.09 | |
Sortino ratio | 0.43 | 1.21 | 0.63 | |
Skew | 0.16 | 0.40 | 0.22 | |
Kurtosis | 5.47 | 7.74 | 6.13 | |
Tail ratio | 1.00 | 0.92 | 0.99 | |
Daily value at risk | -0.6% | -0.6% | -0.6% | |
Gross leverage | 0.35 | 0.36 | 0.35 | |
Daily turnover | 24.4% | 23.4% | 24.1% | |
Alpha | 0.00 | 0.03 | 0.01 | |
Beta | 0.16 | 0.09 | 0.14 |
표본 외 수익률로 전진 분석¶
Rolling Returns OOS¶
plot_rolling_returns
함수는 사용자가 정의한 벤치마크 (우리는 S&P 500을 사용하고 있습니다)에 대한 누적 샘플 내 및 샘플 외 수익을 표시합니다.
plot_rolling_returns(returns=returns[start_date:end_date],
factor_returns=benchmark_rets,
live_start_date=oos_date,
cone_std=(1.0, 1.5, 2.0))
plt.gcf().set_size_inches(14, 8)
sns.despine()
plt.tight_layout();
플롯에는 확장되는 신뢰 구간을 나타내는 콘(cone)이 포함되어 있어, 무작위 보폭 가정을 기준으로 샘플 외 수익이 불가능해 보일 때를 나타냅니다. 여기서, 우리의 전략은 시뮬레이션된 2017년 샘플 외 기간에 대해 벤치마크에 대해 잘 수행하지 못했습니다.
Summary Performance Statistics¶
pyfolio는 여러 가지 분석 기능과 플롯을 제공합니다. perf_stats 요약에서는 연간 및 누적 수익률, 변동성, 왜도, 첨도, SR 등이 표시됩니다. 다음은 추가로 계산할 수 있는 몇 가지 중요한 지표입니다:
- 최대 손실률(Max drawdown): 이전 최고점으로부터의 최대 손실 비율
- Calmar 비율: 연간 포트폴리오 수익과 최대 손실 사이의 상대적인 비율
- Omega 비율: 리턴 목표에 대한 이익과 손실의 확률 가중 평균 비율 (기본값은 0)
- Sortino 비율: 하향 표준 편차에 대한 초과 수익
- Tail 비율: 오른쪽 꼬리(이익, 95번째 백분위수의 절댓값)의 크기와 왼쪽 꼬리(손실, 5번째 백분위수의 절댓값)의 크기의 비율
- 일일 Value at Risk (VaR): 일일 평균보다 2 표준 편차 아래에 있는 손실
- Alpha: 벤치마크 수익으로 설명되지 않은 포트폴리오 수익
- Beta: 벤치마크와의 노출 정도
Rolling Sharpe¶
plot_rolling_sharpe(returns=returns)
plt.gcf().set_size_inches(14, 8)
sns.despine()
plt.tight_layout();
Rolling Beta¶
plot_rolling_beta(returns=returns, factor_returns=benchmark_rets)
plt.gcf().set_size_inches(14, 6)
sns.despine()
plt.tight_layout();
Drawdown Periods¶
plot_drawdown_periods(returns)
함수는 포트폴리오의 주요 drawdown 기간을 플롯으로 나타내며, 다른 몇 가지 플로팅 함수는 rolling SR 및 rolling 요소 노출도를 시장 베타 또는 Fama French의 사이즈, 성장 및 모멘텀 요소에 대해 표시합니다.
# benchmark_rets의 시작 날짜와 종료 날짜를 인덱스 값으로 가져오기
start_idx = benchmark_rets.index.get_loc(start_date)
end_idx = benchmark_rets.index.get_loc(end_date)
fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(16, 10))
axes = ax.flatten()
plot_drawdown_periods(returns=returns, ax=axes[0])
plot_rolling_beta(returns=returns, factor_returns=benchmark_rets, ax=axes[1])
plot_drawdown_underwater(returns=returns, ax=axes[2])
plot_rolling_sharpe(returns=returns)
sns.despine()
plt.tight_layout();
이 플롯은 다양한 티어 시트에 포함된 시각화의 일부를 강조하며, pyfolio를 통해 우리는 성과 특성 및 리스크와 수익의 기본적인 동력에 대한 노출을 자세히 살펴볼 수 있음을 보여줍니다.
Modeling Event Risk¶
Pyfolio에는 다양한 이벤트에 대한 타임라인이 포함되어 있으며, 이를 사용하여 포트폴리오의 성과를 해당 기간 동안 벤치마크와 비교할 수 있습니다. 예를 들어, Brexit 투표 후 2015년 가을 판매 과정 동안의 성과를 비교할 수 있습니다.
interesting_times = extract_interesting_date_ranges(returns=returns)
(interesting_times['Fall2015']
.to_frame('momentum_equal_weights').join(benchmark_rets)
.add(1).cumprod().sub(1)
.plot(lw=2, figsize=(14, 6), title='Post-Brexit Turmoil'))
sns.despine()
plt.tight_layout();
'공부 > ML4T' 카테고리의 다른 글
CH07 선형 모델: 리스크 팩터에서 수익률 예측까지 (0) | 2023.06.30 |
---|---|
CH06 머신러닝 프로세스 (0) | 2023.06.30 |
CH04 금융 특성 공학: 알파 팩터 리서치 (0) | 2023.06.24 |
CH03 금융을 위한 대체 데이터: 범주와 사용 사례 (0) | 2023.06.17 |
CH02 시장 데이터와 기본 데이터: 소스와 기법 (0) | 2023.06.17 |