├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── data ├── options.csv └── yields.csv ├── notebooks └── Replicate_VIXwite.ipynb ├── pyproject.toml ├── src └── vix │ ├── __init__.py │ └── reproduce_vix.py └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | !.isort.cfg 7 | !setup.cfg 8 | *.orig 9 | *.log 10 | *.pot 11 | __pycache__/* 12 | .cache/* 13 | .*.swp 14 | .ipynb_checkpoints 15 | .DS_Store 16 | 17 | # Project files 18 | .ropeproject 19 | .project 20 | .pydevproject 21 | .settings 22 | .idea 23 | tags 24 | 25 | # Package files 26 | *.egg 27 | *.eggs/ 28 | .installed.cfg 29 | *.egg-info 30 | 31 | # Unittest and coverage 32 | htmlcov/* 33 | .coverage 34 | .tox 35 | junit.xml 36 | coverage.xml 37 | .pytest_cache/ 38 | 39 | # Build and docs folder/files 40 | docs/build/* 41 | build/* 42 | dist/* 43 | sdist/* 44 | docs/api/* 45 | docs/_rst/* 46 | docs/_build/* 47 | cover/* 48 | MANIFEST 49 | 50 | # Per-project virtualenvs 51 | .venv*/ 52 | 53 | # big files 54 | *.pdf 55 | *.pkl 56 | 57 | # - files created during tests 58 | *-metadata.yml 59 | src/tme_api/weekly_delivery/config.yml 60 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | 9 | - repo: https://github.com/pre-commit/mirrors-mypy 10 | rev: v1.13.0 11 | hooks: 12 | - id: mypy 13 | args: [ --config-file, pyproject.toml ] 14 | pass_filenames: true 15 | additional_dependencies: [toml] 16 | 17 | - repo: https://github.com/charliermarsh/ruff-pre-commit 18 | rev: v0.8.1 19 | hooks: 20 | - id: ruff 21 | args: [ --config, pyproject.toml, --fix, --exit-non-zero-on-fix ] 22 | types_or: [ python, jupyter ] 23 | - id: ruff-format 24 | args: [ --config, pyproject.toml ] 25 | types_or: [ python, jupyter ] 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VIX and related volatility indices 2 | 3 | [Jupyter notebook](notebooks/Replicate_VIXwite.ipynb) 4 | 5 | This notebook shows how to reproduce the VIX given the data in CBOE White Paper (http://www.cboe.com/micro/vix/vixwhite.pdf). The code works for any option data set, not only one day as in the White Paper. The option data for this example is exactly the same as in the Appendix 1 of the White Paper. 6 | 7 | Given are the prices $C_{i}$, $i\in \lbrace 0,\ldots,n \rbrace $, of a series of European call options on the index with fixed maturity date $T$ and exercise prices $K_{i}$, $i\in\lbrace 0,\ldots,n\rbrace$, as well as the prices $P_{i}$, $i\in\lbrace 0,\ldots,n\rbrace$, of a series of European put options on the index with the same maturity date $T$ and exercise prices $K_{i}$. Let further hold $K_{i} K_{\star} 37 | \end{cases}$$ 38 | 39 | at-the-money strike price is 40 | 41 | $$K_{\star} = \max\lbrace K_{i} < F\rbrace ,$$ 42 | 43 | forward price extracted from put-call parity: 44 | 45 | $$F = K_{j}+e^{rT}\left|C_{j}-P_{j}\right|,$$ 46 | 47 | with 48 | 49 | $$j=\min\lbrace \left|C_{i}-P_{i}\right|\rbrace ,$$ 50 | 51 | and finally $r$ is the constant risk-free short rate appropriate for maturity $T$. 52 | 53 | ## Installation 54 | 55 | ```shell 56 | pip install . 57 | ``` 58 | 59 | ## Contribute 60 | 61 | Create a virtual environment and activate it: 62 | ```shell 63 | python -m venv venv 64 | source venv/bin/activate 65 | ``` 66 | Install development dependencies: 67 | ```shell 68 | pip install -e . 69 | ``` 70 | -------------------------------------------------------------------------------- /data/options.csv: -------------------------------------------------------------------------------- 1 | Expiration,Days,Strike,Call Bid,Call Ask,Put Bid,Put Ask 2 | 20090110,9,200,717.6,722.8,0,0.05 3 | 20090110,9,250,667.6,672.9,0,0.05 4 | 20090110,9,300,617.9,622.9,0,0.05 5 | 20090110,9,350,567.9,572.9,0,0.05 6 | 20090110,9,375,542.9,547.9,0,0.1 7 | 20090110,9,400,517.7,523.2,0.05,0.2 8 | 20090110,9,425,493.5,498.7,0.05,0.2 9 | 20090110,9,450,468,473,0.05,0.2 10 | 20090110,9,470,448,453,0.05,0.25 11 | 20090110,9,475,443,448,0.05,0.25 12 | 20090110,9,480,438,443,0.05,0.3 13 | 20090110,9,490,428,433,0.05,0.8 14 | 20090110,9,500,418,423,0.05,0.3 15 | 20090110,9,510,407.8,413.3,0.05,0.4 16 | 20090110,9,520,397.8,403.3,0.05,0.75 17 | 20090110,9,525,392.8,398.3,0.05,0.8 18 | 20090110,9,530,388.6,393.8,0.05,0.8 19 | 20090110,9,540,378.1,383.1,0.05,0.75 20 | 20090110,9,550,368.1,373.1,0.1,0.2 21 | 20090110,9,560,357.9,363.4,0.05,0.7 22 | 20090110,9,570,347.9,353.4,0.05,0.75 23 | 20090110,9,575,343.2,348.2,0.05,0.4 24 | 20090110,9,580,338.2,343.2,0.05,0.8 25 | 20090110,9,585,333,338.7,0.1,0.75 26 | 20090110,9,590,328,333.5,0.05,0.75 27 | 20090110,9,595,323.5,329,0.05,0.45 28 | 20090110,9,600,318.3,323.3,0.25,0.5 29 | 20090110,9,605,313.1,318.8,0.1,0.8 30 | 20090110,9,610,308.1,313.6,0.1,0.8 31 | 20090110,9,615,303.4,308.4,0.1,0.85 32 | 20090110,9,620,298.2,303.7,0.1,0.85 33 | 20090110,9,625,293.7,299.2,0.4,0.9 34 | 20090110,9,630,288.5,293.5,0.1,0.9 35 | 20090110,9,635,283.3,288.8,0.1,0.95 36 | 20090110,9,640,278.6,283.6,0.3,1.05 37 | 20090110,9,645,273.4,278.9,0.1,1.1 38 | 20090110,9,650,268.5,274.2,0.5,1.1 39 | 20090110,9,655,263.8,268.8,0.2,1.25 40 | 20090110,9,660,258.6,264.1,0.3,1.3 41 | 20090110,9,665,253.7,259.4,0.2,1.3 42 | 20090110,9,670,249,254,0.4,1.45 43 | 20090110,9,675,244.1,249.1,0.5,1.5 44 | 20090110,9,680,239.2,244.2,0.55,1.55 45 | 20090110,9,690,229.4,234.4,0.7,1.85 46 | 20090110,9,700,219.4,225.1,1.3,1.7 47 | 20090110,9,710,209.9,214.9,1.1,2.4 48 | 20090110,9,720,200,205.7,1.1,3 49 | 20090110,9,725,195.2,200.9,2,3 50 | 20090110,9,730,190.6,195.6,1.4,3.5 51 | 20090110,9,740,180.8,186.5,1.8,4 52 | 20090110,9,750,171.3,177,3.2,3.9 53 | 20090110,9,760,162.1,167.1,2.85,5 54 | 20090110,9,770,152.8,157.8,4,5.6 55 | 20090110,9,775,148.2,153.2,3.9,5.9 56 | 20090110,9,780,143.6,148.6,4.1,6.7 57 | 20090110,9,790,134.6,140.1,4.9,7.5 58 | 20090110,9,800,125.6,131.1,6.1,7.5 59 | 20090110,9,805,121,126,6.4,9.1 60 | 20090110,9,810,116.5,121.5,7.1,9.5 61 | 20090110,9,815,112.2,117.2,7.5,10.3 62 | 20090110,9,820,107.6,113.2,8.2,10.8 63 | 20090110,9,825,103.7,108.9,8.9,11.5 64 | 20090110,9,830,99.1,104.3,9,13 65 | 20090110,9,835,95.3,100.3,9.8,13.7 66 | 20090110,9,840,90.9,95.9,10.6,14.3 67 | 20090110,9,845,86.7,91.9,11.5,15.2 68 | 20090110,9,850,82.6,88,13.5,16 69 | 20090110,9,855,78.7,83.9,13.5,17.5 70 | 20090110,9,860,74.9,79.9,14.5,18.7 71 | 20090110,9,865,70.9,76.1,15.5,19.9 72 | 20090110,9,870,67.2,72.3,17.1,20.9 73 | 20090110,9,875,63.8,68.6,18.2,22.2 74 | 20090110,9,880,59.8,65.3,19.2,24 75 | 20090110,9,885,56.6,61.6,20.3,25.4 76 | 20090110,9,890,53.7,58.4,22.1,26.8 77 | 20090110,9,895,50.3,55,23.9,29 78 | 20090110,9,900,46.2,51.7,25.5,29 79 | 20090110,9,905,43.4,48.9,27.2,32.3 80 | 20090110,9,910,40,45.1,29.2,34.2 81 | 20090110,9,915,37.3,42.8,30.8,36.3 82 | 20090110,9,920,35.2,39.1,35.2,38.1 83 | 20090110,9,925,31.4,35.2,35.1,40.3 84 | 20090110,9,930,31,33.9,37.4,42.9 85 | 20090110,9,935,26,31.5,40.3,45.1 86 | 20090110,9,940,26,29,42.5,48.1 87 | 20090110,9,945,21.6,26.4,45.3,50.6 88 | 20090110,9,950,21.6,24.4,47.6,53.1 89 | 20090110,9,955,17.2,22.5,51.3,56.5 90 | 20090110,9,960,15.5,19.6,54.4,59.4 91 | 20090110,9,965,13.9,18,57.5,62.5 92 | 20090110,9,970,12,16.4,60.8,66 93 | 20090110,9,975,12.5,14.8,64.3,69.5 94 | 20090110,9,980,9.3,13.1,67.8,73 95 | 20090110,9,985,8,11.9,71.5,76.7 96 | 20090110,9,990,7.4,9.9,75.1,80.5 97 | 20090110,9,995,6.4,8.7,79.2,84.2 98 | 20090110,9,1000,6.5,7.5,82.5,88 99 | 20090110,9,1005,4.4,6.8,86.9,92.5 100 | 20090110,9,1010,3.6,6,91.1,96.5 101 | 20090110,9,1015,3.1,5.1,95.4,101.1 102 | 20090110,9,1020,3.5,4.6,99.8,105.4 103 | 20090110,9,1025,2,4.1,104.7,109.7 104 | 20090110,9,1030,1.6,3.5,108.5,114 105 | 20090110,9,1035,1.2,3.1,113.6,119.1 106 | 20090110,9,1040,1.25,2.5,118.5,123.5 107 | 20090110,9,1045,0.85,2.1,123.2,128.2 108 | 20090110,9,1050,1.3,1.8,127.7,133.2 109 | 20090110,9,1055,0.4,1.65,132.5,138 110 | 20090110,9,1060,0.9,1.4,137.6,142.6 111 | 20090110,9,1065,0.2,1.25,142.2,147.7 112 | 20090110,9,1070,0.15,1.15,147.3,152.3 113 | 20090110,9,1075,0.5,1,152.2,157.2 114 | 20090110,9,1080,0.4,0.95,156.9,162.4 115 | 20090110,9,1085,0.2,0.9,161.8,167.3 116 | 20090110,9,1090,0.3,0.85,167,172 117 | 20090110,9,1095,0.2,0.75,171.7,177.2 118 | 20090110,9,1100,0.3,0.45,176.9,181.9 119 | 20090110,9,1105,0.2,0.75,181.6,187.1 120 | 20090110,9,1110,0.2,0.35,186.8,191.8 121 | 20090110,9,1115,0.05,0.8,191.8,196.8 122 | 20090110,9,1120,0.05,0.5,196.8,201.8 123 | 20090110,9,1125,0.15,0.5,201.5,207 124 | 20090110,9,1130,0.05,0.9,206.7,211.7 125 | 20090110,9,1135,0.05,0.9,211.7,216.7 126 | 20090110,9,1140,0.05,0.7,216.7,221.7 127 | 20090110,9,1145,0.05,0.95,221.7,226.7 128 | 20090110,9,1150,0.05,0.35,226.7,231.7 129 | 20090110,9,1155,0.05,0.95,231.7,236.7 130 | 20090110,9,1160,0.05,0.5,236.7,241.7 131 | 20090110,9,1165,0.05,0.35,241.7,246.7 132 | 20090110,9,1170,0.05,0.45,246.4,251.9 133 | 20090110,9,1175,0.05,0.15,251.4,256.9 134 | 20090110,9,1180,0.05,0.8,256.4,261.9 135 | 20090110,9,1185,0.05,0.25,260.9,266.1 136 | 20090110,9,1190,0.05,0.5,266.6,271.6 137 | 20090110,9,1195,0.05,1,271.6,276.6 138 | 20090110,9,1200,0.05,0.15,276.6,281.6 139 | 20090110,9,1205,0.05,1,281.6,286.6 140 | 20090110,9,1210,0.05,0.5,286.6,291.6 141 | 20090110,9,1215,0.05,0.5,291.6,296.6 142 | 20090110,9,1220,0.05,1,296.6,301.6 143 | 20090110,9,1225,0,1,301.6,306.6 144 | 20090110,9,1230,0,1,306.6,311.6 145 | 20090110,9,1235,0,0.75,311.6,316.6 146 | 20090110,9,1240,0,0.5,316.6,321.6 147 | 20090110,9,1245,0,0.15,321.6,326.6 148 | 20090110,9,1250,0.05,0.1,326.6,331.6 149 | 20090110,9,1255,0,1,331.6,336.6 150 | 20090110,9,1260,0,1,336.6,341.6 151 | 20090110,9,1265,0,1,341.6,346.6 152 | 20090110,9,1270,0,0.3,346.6,351.6 153 | 20090110,9,1275,0,0.1,351.6,356.6 154 | 20090110,9,1280,0,0.15,356.6,361.6 155 | 20090110,9,1285,0,0.15,361.6,366.6 156 | 20090110,9,1290,0,0.15,366.6,371.6 157 | 20090110,9,1295,0,0.15,371.6,376.6 158 | 20090110,9,1300,0,0.1,376.6,381.6 159 | 20090110,9,1305,0,0.1,381.6,386.6 160 | 20090110,9,1310,0,0.1,386.6,391.6 161 | 20090110,9,1315,0,0.1,391.6,396.6 162 | 20090110,9,1320,0,0.1,396.1,401.8 163 | 20090110,9,1325,0,0.1,401.3,406.8 164 | 20090110,9,1330,0,0.1,406.3,411.8 165 | 20090110,9,1335,0,0.1,411.3,416.8 166 | 20090110,9,1340,0,0.1,416.3,421.8 167 | 20090110,9,1345,0,0.1,421.3,426.8 168 | 20090110,9,1350,0,0.05,425.8,431 169 | 20090110,9,1355,0,0.1,430.8,436 170 | 20090110,9,1360,0,0.1,435.8,441 171 | 20090110,9,1365,0,0.1,441.5,446.5 172 | 20090110,9,1370,0,0.1,446.5,451.5 173 | 20090110,9,1375,0,0.1,451.5,456.5 174 | 20090110,9,1380,0,0.1,456.5,461.5 175 | 20090110,9,1385,0,0.1,461.5,466.5 176 | 20090110,9,1390,0,0.1,466.5,471.5 177 | 20090110,9,1395,0,0.1,471.5,476.5 178 | 20090110,9,1400,0,0.05,476.5,481.5 179 | 20090110,9,1405,0,0.1,481.5,486.5 180 | 20090110,9,1410,0,0.1,486.5,491.5 181 | 20090110,9,1415,0,0.1,491.5,496.5 182 | 20090110,9,1420,0,0.1,496.5,501.5 183 | 20090110,9,1425,0,0.1,501.5,506.5 184 | 20090110,9,1430,0,0.1,506.5,511.5 185 | 20090110,9,1435,0,0.1,511.5,516.5 186 | 20090110,9,1440,0,0.1,516.5,521.5 187 | 20090110,9,1445,0,0.1,521.5,526.5 188 | 20090110,9,1450,0,0.1,526.5,531.5 189 | 20090110,9,1460,0,0.1,536.5,541.5 190 | 20090110,9,1470,0,0.1,546.5,551.5 191 | 20090110,9,1475,0,0.1,551.5,556.5 192 | 20090110,9,1480,0,0.1,556.5,561.5 193 | 20090110,9,1490,0,1,566.5,571.5 194 | 20090110,9,1500,0,0.05,576.5,581.5 195 | 20090110,9,1650,0,1,726.4,731.4 196 | 20090110,9,1700,0,1,776.1,781.6 197 | 20090207,37,200,716.4,721.4,0.05,0.6 198 | 20090207,37,300,616.6,621.6,0.2,0.4 199 | 20090207,37,350,566.8,571.8,0.15,0.85 200 | 20090207,37,375,541.7,547.4,0.2,0.5 201 | 20090207,37,400,517.1,522.1,0.2,0.85 202 | 20090207,37,425,492.3,497.3,0,1 203 | 20090207,37,450,467.3,472.8,0.2,1.2 204 | 20090207,37,475,442.9,447.9,0.5,1.5 205 | 20090207,37,500,418.1,423.6,1.35,2 206 | 20090207,37,525,393.9,398.9,1.25,2.5 207 | 20090207,37,550,369.6,374.6,1.55,2.75 208 | 20090207,37,575,345.2,350.9,2.1,4.3 209 | 20090207,37,600,321.7,327.2,3.4,5.4 210 | 20090207,37,610,312.2,317.7,3.5,5.7 211 | 20090207,37,615,307.2,312.2,3.9,6.1 212 | 20090207,37,620,302.7,308.2,4,6.4 213 | 20090207,37,625,297.8,302.8,4.2,6.9 214 | 20090207,37,630,292.8,298.5,4.6,6.9 215 | 20090207,37,635,288.1,293.8,4.7,7.3 216 | 20090207,37,640,283.5,289.1,5.1,7.5 217 | 20090207,37,650,274.1,279.6,5.8,8.1 218 | 20090207,37,660,265,270,6.3,9 219 | 20090207,37,670,256,261.5,7.2,9.7 220 | 20090207,37,675,251.4,256.2,7.5,9.8 221 | 20090207,37,680,246.4,251.6,8.1,10.4 222 | 20090207,37,690,237.7,243.2,8.8,11.4 223 | 20090207,37,700,228.7,233.7,9.4,12.4 224 | 20090207,37,710,219.3,224.9,10,13.6 225 | 20090207,37,720,210.4,216,11,14.7 226 | 20090207,37,725,206.4,211.2,11.7,15.9 227 | 20090207,37,730,201.8,206.8,12.1,16.2 228 | 20090207,37,740,192.9,198.5,13.2,17.6 229 | 20090207,37,750,184.7,189.9,15,18.8 230 | 20090207,37,760,176,181.4,16.2,19.9 231 | 20090207,37,770,167.4,172.5,17.9,22 232 | 20090207,37,775,163.2,168.4,18.3,22.9 233 | 20090207,37,780,159,164.2,19.2,24 234 | 20090207,37,790,150.9,156.1,20.8,26 235 | 20090207,37,800,142.8,146,22.7,28 236 | 20090207,37,805,138.8,144,23.7,28.9 237 | 20090207,37,810,134.9,140,25,29.7 238 | 20090207,37,815,130.9,136.1,25.9,30.5 239 | 20090207,37,820,127.1,132.2,26.9,31.9 240 | 20090207,37,825,123.2,128.4,27.8,33.3 241 | 20090207,37,830,119.4,124.6,29.3,34.5 242 | 20090207,37,835,115.6,120.8,30.3,35.4 243 | 20090207,37,840,111.9,117.1,31.8,36.9 244 | 20090207,37,845,108.2,113.4,32.9,38 245 | 20090207,37,850,107,109.7,34.1,39.6 246 | 20090207,37,855,101.3,106.3,35.9,39 247 | 20090207,37,860,97.4,103,37.4,42.5 248 | 20090207,37,865,94.8,99.5,38.6,43.7 249 | 20090207,37,870,91.3,96,40,45.1 250 | 20090207,37,875,87.9,92.6,41.7,47.1 251 | 20090207,37,880,83.6,88.9,43.3,48.7 252 | 20090207,37,885,80.3,85.8,44.8,50.3 253 | 20090207,37,890,77.5,83,47,52.1 254 | 20090207,37,895,74,79.5,48.6,53.8 255 | 20090207,37,900,70.8,76.4,50.2,55.4 256 | 20090207,37,905,67.6,73.1,52.2,57.2 257 | 20090207,37,910,64.6,70.1,54,59.5 258 | 20090207,37,915,62.4,67.1,56.3,61.5 259 | 20090207,37,920,59.1,64,57.8,63.3 260 | 20090207,37,925,56.2,61.7,60.3,65.8 261 | 20090207,37,930,53,58.5,62.9,67.9 262 | 20090207,37,935,50.3,55.8,64.6,70.1 263 | 20090207,37,940,47.6,52.7,67,72.6 264 | 20090207,37,945,45.7,50.4,69.5,75 265 | 20090207,37,950,44.8,47.7,72.2,76.6 266 | 20090207,37,955,40.4,45.3,74.7,79.7 267 | 20090207,37,960,38.1,43.2,77.3,82.3 268 | 20090207,37,965,35.6,40.7,79.8,85.3 269 | 20090207,37,970,33.4,38.9,82.6,87.9 270 | 20090207,37,975,32.4,36.1,85.7,90.7 271 | 20090207,37,980,28.9,34,88.6,93.4 272 | 20090207,37,985,27,32,91.6,96.8 273 | 20090207,37,990,25.4,30.3,94.7,99.8 274 | 20090207,37,995,23.2,28.6,97.8,103 275 | 20090207,37,1000,23,26.4,101,106.2 276 | 20090207,37,1005,20,24.7,104.3,109.5 277 | 20090207,37,1010,18.4,23.3,107.7,112.9 278 | 20090207,37,1015,17.1,21.2,111.1,116.3 279 | 20090207,37,1020,18,19.8,114.6,119.8 280 | 20090207,37,1025,14.4,18.2,118.2,123.4 281 | 20090207,37,1030,13.1,16.9,121.9,127.1 282 | 20090207,37,1035,11.8,15.8,125.6,130.6 283 | 20090207,37,1040,10.7,14.7,128.8,134.3 284 | 20090207,37,1045,9.3,13.6,133.1,138.6 285 | 20090207,37,1050,10.8,12,137.3,142.3 286 | 20090207,37,1055,8.1,10.6,140.9,146.6 287 | 20090207,37,1060,8.2,9.8,145.2,150.7 288 | 20090207,37,1065,6.5,9,148.9,154.4 289 | 20090207,37,1070,5.6,8.1,153.4,158.6 290 | 20090207,37,1075,5.1,7.6,157.4,162.9 291 | 20090207,37,1080,4.4,6.8,162.4,167.4 292 | 20090207,37,1085,3.8,6.2,166.4,172.1 293 | 20090207,37,1090,3.3,5.5,171.3,176.3 294 | 20090207,37,1095,2.9,4.9,175.8,180.8 295 | 20090207,37,1100,3.3,4.5,180.1,185.6 296 | 20090207,37,1105,2,4.1,184.2,189.7 297 | 20090207,37,1110,2,3.6,189.3,194.8 298 | 20090207,37,1115,1.65,3.3,194.2,199.2 299 | 20090207,37,1120,1.45,3,198.9,203.9 300 | 20090207,37,1125,2,2.4,202.9,208.4 301 | 20090207,37,1130,0.9,2.2,208.4,213.4 302 | 20090207,37,1135,0.7,1.9,212.7,218.4 303 | 20090207,37,1140,1,1.75,218,223 304 | 20090207,37,1145,0.5,1.5,222.8,227.8 305 | 20090207,37,1150,0.35,1.3,227.4,232.9 306 | 20090207,37,1155,0.25,1.2,232.3,237.8 307 | 20090207,37,1160,0.1,1.1,236.7,242.2 308 | 20090207,37,1165,0,1,241.6,247.1 309 | 20090207,37,1170,0,0.9,247,252.5 310 | 20090207,37,1175,0.3,0.85,251.9,257.4 311 | 20090207,37,1180,0,0.85,257.1,262.1 312 | 20090207,37,1185,0,0.8,261.3,266.5 313 | 20090207,37,1190,0,0.5,267,272 314 | 20090207,37,1195,0,0.75,271.2,276.4 315 | 20090207,37,1200,0.3,0.6,276.9,281.9 316 | 20090207,37,1205,0,0.75,281.6,287.1 317 | 20090207,37,1210,0,0.6,286.8,291.8 318 | 20090207,37,1215,0,0.85,291.8,296.8 319 | 20090207,37,1220,0,0.75,296.8,301.8 320 | 20090207,37,1225,0,0.8,301.5,307 321 | 20090207,37,1230,0,0.8,306.7,311.7 322 | 20090207,37,1235,0,0.8,311.7,316.7 323 | 20090207,37,1240,0.1,0.75,316.7,321.7 324 | 20090207,37,1245,0,0.75,321.7,326.7 325 | 20090207,37,1250,0,1,326.7,331.2 326 | 20090207,37,1255,0,0.75,331.4,336.9 327 | 20090207,37,1260,0,0.7,335.9,341.1 328 | 20090207,37,1265,0,0.7,341.6,346.6 329 | 20090207,37,1270,0,0.7,346.6,351.6 330 | 20090207,37,1275,0.05,0.2,351.6,356.6 331 | 20090207,37,1280,0,0.75,356.6,361.6 332 | 20090207,37,1290,0,0.75,366.6,371.6 333 | 20090207,37,1300,0.05,0.45,375.7,381 334 | 20090207,37,1315,0,0.5,390.7,395.9 335 | 20090207,37,1320,0,0.75,396.5,401.5 336 | 20090207,37,1325,0,0.5,399.9,405.9 337 | 20090207,37,1335,0,0.75,411.5,416.5 338 | 20090207,37,1340,0,0.75,416.5,421.5 339 | 20090207,37,1345,0,0.75,421.5,426.5 340 | 20090207,37,1350,0,0.5,425.6,430.8 341 | 20090207,37,1360,0,0.75,435.7,440.9 342 | 20090207,37,1375,0,0.55,451.4,456.4 343 | 20090207,37,1380,0,0.75,456.4,461.4 344 | 20090207,37,1395,0,0.5,471.4,476.4 345 | 20090207,37,1400,0,0.5,475.5,480.7 346 | 20090207,37,1420,0,0.75,496.3,501.3 347 | 20090207,37,1425,0,0.75,501.3,506.3 348 | 20090207,37,1440,0,0.75,516.3,521.3 349 | 20090207,37,1450,0,0.5,525.3,530.6 350 | 20090207,37,1475,0,0.75,551.2,556.2 351 | 20090207,37,1480,0,0.75,556.2,561.2 352 | 20090207,37,1500,0,0.5,575.2,580.5 353 | 20090207,37,1525,0,0.75,601.1,606.1 354 | 20090207,37,1550,0,0.5,625.1,630.9 355 | 20090207,37,1575,0,0.75,650.8,656.3 356 | 20090207,37,1600,0,0.5,675,680.2 357 | 20090207,37,1625,0,0.75,700.7,706.2 358 | 20090207,37,1650,0,0.5,724.1,730.1 359 | 20090207,37,1680,0,0.75,755.6,761.1 360 | 20090207,37,1690,0,0.75,765.8,770.8 361 | 20090207,37,1700,0,0.5,775.8,780.8 362 | 20090207,37,1735,0,0.75,810.5,816 363 | 20090207,37,1750,0,0.75,825.7,830.7 364 | 20090207,37,1775,0,0.2,850.7,855.7 365 | 20090207,37,1800,0,0.75,875.6,880.6 366 | 20090207,37,1850,0,0.75,925.5,930.5 367 | 20090207,37,1900,0,0.2,974.7,979.9 368 | 20090207,37,1950,0,0.75,1025.1,1030.6 369 | 20090207,37,2000,0,0.2,1074.8,1079.8 370 | -------------------------------------------------------------------------------- /data/yields.csv: -------------------------------------------------------------------------------- 1 | Date,Days,Rate 2 | 20090101,9,0.38 3 | 20090101,37,0.38 4 | -------------------------------------------------------------------------------- /notebooks/Replicate_VIXwite.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Replication of VIX" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "This notebook shows how to reproduce the VIX given the data in CBOE White Paper (http://www.cboe.com/micro/vix/vixwhite.pdf). The code works for any option data set, not only one day as in the White Paper. The option data for this example is exactly the same as in the Appendix 1 of the White Paper." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "Given are the prices $C_{i}$, $i\\in\\left\\{ 0,\\ldots,n\\right\\}$, of a series of European call options on the index with fixed maturity date $T$ and exercise prices $K_{i}$, $i\\in\\left\\{ 0,\\ldots,n\\right\\}$, as well as the prices $P_{i}$, $i\\in\\left\\{ 0,\\ldots,n\\right\\}$, of a series of European put options on the index with the same maturity date $T$ and exercise prices $K_{i}$. Let further hold $K_{i}K_{*}\n", 42 | "\\end{cases}$$\n", 43 | "at-the-money strike price is\n", 44 | "$$K_{*}\t=\t\\max\\left\\{ K_{i}\n", 102 | "\n", 115 | "\n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | "
Rate
DateDays
2009-01-0190.38
370.38
\n", 140 | "" 141 | ], 142 | "text/plain": [ 143 | " Rate\n", 144 | "Date Days \n", 145 | "2009-01-01 9 0.38\n", 146 | " 37 0.38" 147 | ] 148 | }, 149 | "execution_count": 2, 150 | "metadata": {}, 151 | "output_type": "execute_result" 152 | } 153 | ], 154 | "source": [ 155 | "yields = pd.read_csv(\"../data/yields.csv\", converters={\"Date\": lambda x: dt.datetime.strptime(x, \"%Y%m%d\")})\n", 156 | "yields = yields.set_index([\"Date\", \"Days\"])\n", 157 | "\n", 158 | "yields" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "## Import options" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 3, 171 | "metadata": { 172 | "pycharm": { 173 | "is_executing": false 174 | } 175 | }, 176 | "outputs": [ 177 | { 178 | "data": { 179 | "text/html": [ 180 | "
\n", 181 | "\n", 194 | "\n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | "
ExpirationDaysStrikeCall BidCall AskPut BidPut AskDate
02009-01-109200.0717.6722.800.00.052009-01-01
12009-01-109250.0667.6672.900.00.052009-01-01
22009-01-109300.0617.9622.900.00.052009-01-01
32009-01-109350.0567.9572.900.00.052009-01-01
42009-01-109375.0542.9547.900.00.102009-01-01
...........................
3632009-02-07371800.00.00.75875.6880.602009-01-01
3642009-02-07371850.00.00.75925.5930.502009-01-01
3652009-02-07371900.00.00.20974.7979.902009-01-01
3662009-02-07371950.00.00.751025.11030.602009-01-01
3672009-02-07372000.00.00.201074.81079.802009-01-01
\n", 332 | "

368 rows × 8 columns

\n", 333 | "
" 334 | ], 335 | "text/plain": [ 336 | " Expiration Days Strike Call Bid Call Ask Put Bid Put Ask Date\n", 337 | "0 2009-01-10 9 200.0 717.6 722.80 0.0 0.05 2009-01-01\n", 338 | "1 2009-01-10 9 250.0 667.6 672.90 0.0 0.05 2009-01-01\n", 339 | "2 2009-01-10 9 300.0 617.9 622.90 0.0 0.05 2009-01-01\n", 340 | "3 2009-01-10 9 350.0 567.9 572.90 0.0 0.05 2009-01-01\n", 341 | "4 2009-01-10 9 375.0 542.9 547.90 0.0 0.10 2009-01-01\n", 342 | ".. ... ... ... ... ... ... ... ...\n", 343 | "363 2009-02-07 37 1800.0 0.0 0.75 875.6 880.60 2009-01-01\n", 344 | "364 2009-02-07 37 1850.0 0.0 0.75 925.5 930.50 2009-01-01\n", 345 | "365 2009-02-07 37 1900.0 0.0 0.20 974.7 979.90 2009-01-01\n", 346 | "366 2009-02-07 37 1950.0 0.0 0.75 1025.1 1030.60 2009-01-01\n", 347 | "367 2009-02-07 37 2000.0 0.0 0.20 1074.8 1079.80 2009-01-01\n", 348 | "\n", 349 | "[368 rows x 8 columns]" 350 | ] 351 | }, 352 | "execution_count": 3, 353 | "metadata": {}, 354 | "output_type": "execute_result" 355 | } 356 | ], 357 | "source": [ 358 | "# Function to parse dates of '20090101' format\n", 359 | "raw_options = pd.read_csv(\"../data/options.csv\", converters={\"Expiration\": lambda x: dt.datetime.strptime(x, \"%Y%m%d\")})\n", 360 | "\n", 361 | "# Function to convert days to internal timedelta format\n", 362 | "raw_options[\"Date\"] = raw_options[\"Expiration\"] - raw_options[\"Days\"].map(lambda x: dt.timedelta(days=int(x)))\n", 363 | "# Convert integer strikes to float! Otherwise it may lead to accumulation of errors.\n", 364 | "raw_options[\"Strike\"] = raw_options[\"Strike\"].astype(float)\n", 365 | "\n", 366 | "raw_options" 367 | ] 368 | }, 369 | { 370 | "cell_type": "markdown", 371 | "metadata": {}, 372 | "source": [ 373 | "## Do some cleaning and indexing" 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": 4, 379 | "metadata": { 380 | "pycharm": { 381 | "is_executing": false 382 | } 383 | }, 384 | "outputs": [ 385 | { 386 | "data": { 387 | "text/html": [ 388 | "
\n", 389 | "\n", 402 | "\n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | "
BidAsk
DateDaysCPStrike
2009-01-019C200.0717.6722.8
250.0667.6672.9
300.0617.9622.9
350.0567.9572.9
375.0542.9547.9
\n", 452 | "
" 453 | ], 454 | "text/plain": [ 455 | " Bid Ask\n", 456 | "Date Days CP Strike \n", 457 | "2009-01-01 9 C 200.0 717.6 722.8\n", 458 | " 250.0 667.6 672.9\n", 459 | " 300.0 617.9 622.9\n", 460 | " 350.0 567.9 572.9\n", 461 | " 375.0 542.9 547.9" 462 | ] 463 | }, 464 | "execution_count": 4, 465 | "metadata": {}, 466 | "output_type": "execute_result" 467 | } 468 | ], 469 | "source": [ 470 | "# Since VIX is computed for the date of option quotations, we do not really need Expiration\n", 471 | "options = raw_options.set_index([\"Date\", \"Days\", \"Strike\"]).drop(\"Expiration\", axis=1)\n", 472 | "\n", 473 | "# Do some renaming and separate calls from puts\n", 474 | "calls = options[[\"Call Bid\", \"Call Ask\"]].rename(columns={\"Call Bid\": \"Bid\", \"Call Ask\": \"Ask\"})\n", 475 | "puts = options[[\"Put Bid\", \"Put Ask\"]].rename(columns={\"Put Bid\": \"Bid\", \"Put Ask\": \"Ask\"})\n", 476 | "\n", 477 | "# Add a column indicating the type of the option\n", 478 | "calls[\"CP\"], puts[\"CP\"] = \"C\", \"P\"\n", 479 | "\n", 480 | "# Merge calls and puts\n", 481 | "options = pd.concat([calls, puts])\n", 482 | "\n", 483 | "# Reindex and sort\n", 484 | "options = options.reset_index().set_index([\"Date\", \"Days\", \"CP\", \"Strike\"]).sort_index()\n", 485 | "\n", 486 | "options.head()" 487 | ] 488 | }, 489 | { 490 | "cell_type": "markdown", 491 | "metadata": {}, 492 | "source": [ 493 | "## Compute bid/ask average" 494 | ] 495 | }, 496 | { 497 | "cell_type": "markdown", 498 | "metadata": {}, 499 | "source": [ 500 | "This step is used further to filter out in-the-money options." 501 | ] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": 5, 506 | "metadata": { 507 | "pycharm": { 508 | "is_executing": false 509 | } 510 | }, 511 | "outputs": [ 512 | { 513 | "data": { 514 | "text/html": [ 515 | "
\n", 516 | "\n", 529 | "\n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | "
CPCP
DateDaysStrike
2009-01-019400.0520.450.125
425.0496.100.125
450.0470.500.125
470.0450.500.150
475.0445.500.150
\n", 576 | "
" 577 | ], 578 | "text/plain": [ 579 | "CP C P\n", 580 | "Date Days Strike \n", 581 | "2009-01-01 9 400.0 520.45 0.125\n", 582 | " 425.0 496.10 0.125\n", 583 | " 450.0 470.50 0.125\n", 584 | " 470.0 450.50 0.150\n", 585 | " 475.0 445.50 0.150" 586 | ] 587 | }, 588 | "execution_count": 5, 589 | "metadata": {}, 590 | "output_type": "execute_result" 591 | } 592 | ], 593 | "source": [ 594 | "options[\"Premium\"] = (options[\"Bid\"] + options[\"Ask\"]) / 2\n", 595 | "options2 = options[options[\"Bid\"] > 0][\"Premium\"].unstack(\"CP\")\n", 596 | "\n", 597 | "options2.dropna().head()" 598 | ] 599 | }, 600 | { 601 | "cell_type": "markdown", 602 | "metadata": {}, 603 | "source": [ 604 | "## Determine minimum difference" 605 | ] 606 | }, 607 | { 608 | "cell_type": "code", 609 | "execution_count": 6, 610 | "metadata": { 611 | "pycharm": { 612 | "is_executing": false 613 | } 614 | }, 615 | "outputs": [ 616 | { 617 | "data": { 618 | "text/html": [ 619 | "
\n", 620 | "\n", 633 | "\n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | "
CPCPCPdiffmin
DateDaysStrike
2009-01-019400.0520.450.125520.325False
425.0496.100.125495.975False
450.0470.500.125470.375False
470.0450.500.150450.350False
475.0445.500.150445.350False
\n", 694 | "
" 695 | ], 696 | "text/plain": [ 697 | "CP C P CPdiff min\n", 698 | "Date Days Strike \n", 699 | "2009-01-01 9 400.0 520.45 0.125 520.325 False\n", 700 | " 425.0 496.10 0.125 495.975 False\n", 701 | " 450.0 470.50 0.125 470.375 False\n", 702 | " 470.0 450.50 0.150 450.350 False\n", 703 | " 475.0 445.50 0.150 445.350 False" 704 | ] 705 | }, 706 | "execution_count": 6, 707 | "metadata": {}, 708 | "output_type": "execute_result" 709 | } 710 | ], 711 | "source": [ 712 | "# Find the absolute difference\n", 713 | "options2[\"CPdiff\"] = (options2[\"C\"] - options2[\"P\"]).abs()\n", 714 | "# Mark the minimum for each date/term\n", 715 | "options2[\"min\"] = options2[\"CPdiff\"].groupby(level=[\"Date\", \"Days\"]).transform(lambda x: x == x.min())\n", 716 | "\n", 717 | "options2.dropna().head()" 718 | ] 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "metadata": {}, 723 | "source": [ 724 | "## Compute forward price" 725 | ] 726 | }, 727 | { 728 | "cell_type": "code", 729 | "execution_count": 7, 730 | "metadata": { 731 | "pycharm": { 732 | "is_executing": false 733 | } 734 | }, 735 | "outputs": [ 736 | { 737 | "data": { 738 | "text/html": [ 739 | "
\n", 740 | "\n", 753 | "\n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | "
Forward
DateDays
2009-01-019920.500047
37921.000385
\n", 778 | "
" 779 | ], 780 | "text/plain": [ 781 | " Forward\n", 782 | "Date Days \n", 783 | "2009-01-01 9 920.500047\n", 784 | " 37 921.000385" 785 | ] 786 | }, 787 | "execution_count": 7, 788 | "metadata": {}, 789 | "output_type": "execute_result" 790 | } 791 | ], 792 | "source": [ 793 | "# Leave only at-the-money optons\n", 794 | "df = options2[options2[\"min\"] == 1].reset_index()\n", 795 | "# Merge with risk-free rate\n", 796 | "df = pd.merge(df, yields.reset_index(), how=\"left\")\n", 797 | "\n", 798 | "# Compute the implied forward\n", 799 | "df[\"Forward\"] = df[\"CPdiff\"] * np.exp(df[\"Rate\"] * df[\"Days\"] / 36500)\n", 800 | "df[\"Forward\"] += df[\"Strike\"]\n", 801 | "forward = df.set_index([\"Date\", \"Days\"])[[\"Forward\"]]\n", 802 | "\n", 803 | "forward.head()" 804 | ] 805 | }, 806 | { 807 | "cell_type": "markdown", 808 | "metadata": {}, 809 | "source": [ 810 | "## Compute at-the-money strike" 811 | ] 812 | }, 813 | { 814 | "cell_type": "code", 815 | "execution_count": 8, 816 | "metadata": { 817 | "pycharm": { 818 | "is_executing": false 819 | } 820 | }, 821 | "outputs": [ 822 | { 823 | "data": { 824 | "text/html": [ 825 | "
\n", 826 | "\n", 839 | "\n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | "
Mid Strike
DateDays
2009-01-019920.0
37920.0
\n", 864 | "
" 865 | ], 866 | "text/plain": [ 867 | " Mid Strike\n", 868 | "Date Days \n", 869 | "2009-01-01 9 920.0\n", 870 | " 37 920.0" 871 | ] 872 | }, 873 | "execution_count": 8, 874 | "metadata": {}, 875 | "output_type": "execute_result" 876 | } 877 | ], 878 | "source": [ 879 | "# Merge options with implied forward price\n", 880 | "left = options2.reset_index().set_index([\"Date\", \"Days\"])\n", 881 | "df = pd.merge(left, forward, left_index=True, right_index=True)\n", 882 | "# Compute at-the-money strike\n", 883 | "mid_strike = df[df[\"Strike\"] < df[\"Forward\"]][\"Strike\"].groupby(level=[\"Date\", \"Days\"]).max()\n", 884 | "mid_strike = pd.DataFrame({\"Mid Strike\": mid_strike})\n", 885 | "\n", 886 | "mid_strike.head()" 887 | ] 888 | }, 889 | { 890 | "cell_type": "markdown", 891 | "metadata": {}, 892 | "source": [ 893 | "## Separate out-of-the-money calls and puts" 894 | ] 895 | }, 896 | { 897 | "cell_type": "code", 898 | "execution_count": 9, 899 | "metadata": { 900 | "pycharm": { 901 | "is_executing": false 902 | } 903 | }, 904 | "outputs": [ 905 | { 906 | "data": { 907 | "text/html": [ 908 | "
\n", 909 | "\n", 922 | "\n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | "
CPStrikeBidAskMid Strike
DateDays
2009-01-0137P900.050.255.4920.0
37P905.052.257.2920.0
37P910.054.059.5920.0
37P915.056.361.5920.0
37P920.057.863.3920.0
\n", 987 | "
" 988 | ], 989 | "text/plain": [ 990 | " CP Strike Bid Ask Mid Strike\n", 991 | "Date Days \n", 992 | "2009-01-01 37 P 900.0 50.2 55.4 920.0\n", 993 | " 37 P 905.0 52.2 57.2 920.0\n", 994 | " 37 P 910.0 54.0 59.5 920.0\n", 995 | " 37 P 915.0 56.3 61.5 920.0\n", 996 | " 37 P 920.0 57.8 63.3 920.0" 997 | ] 998 | }, 999 | "execution_count": 9, 1000 | "metadata": {}, 1001 | "output_type": "execute_result" 1002 | } 1003 | ], 1004 | "source": [ 1005 | "# Go back to original data and reindex it\n", 1006 | "left = options.reset_index().set_index([\"Date\", \"Days\"]).drop(\"Premium\", axis=1)\n", 1007 | "# Merge with at-the-money strike\n", 1008 | "df = pd.merge(left, mid_strike, left_index=True, right_index=True)\n", 1009 | "# Separate out-of-the-money calls and puts\n", 1010 | "P = (df[\"Strike\"] <= df[\"Mid Strike\"]) & (df[\"CP\"] == \"P\")\n", 1011 | "C = (df[\"Strike\"] >= df[\"Mid Strike\"]) & (df[\"CP\"] == \"C\")\n", 1012 | "puts, calls = df[P], df[C]\n", 1013 | "\n", 1014 | "puts.tail()" 1015 | ] 1016 | }, 1017 | { 1018 | "cell_type": "code", 1019 | "execution_count": 10, 1020 | "metadata": { 1021 | "collapsed": false, 1022 | "jupyter": { 1023 | "outputs_hidden": false 1024 | }, 1025 | "pycharm": { 1026 | "name": "#%%\n" 1027 | } 1028 | }, 1029 | "outputs": [ 1030 | { 1031 | "data": { 1032 | "text/html": [ 1033 | "
\n", 1034 | "\n", 1047 | "\n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | " \n", 1064 | " \n", 1065 | " \n", 1066 | " \n", 1067 | " \n", 1068 | " \n", 1069 | " \n", 1070 | " \n", 1071 | " \n", 1072 | " \n", 1073 | " \n", 1074 | " \n", 1075 | " \n", 1076 | " \n", 1077 | " \n", 1078 | " \n", 1079 | " \n", 1080 | " \n", 1081 | " \n", 1082 | " \n", 1083 | " \n", 1084 | " \n", 1085 | " \n", 1086 | " \n", 1087 | " \n", 1088 | " \n", 1089 | " \n", 1090 | " \n", 1091 | " \n", 1092 | " \n", 1093 | " \n", 1094 | " \n", 1095 | " \n", 1096 | " \n", 1097 | " \n", 1098 | " \n", 1099 | " \n", 1100 | " \n", 1101 | " \n", 1102 | " \n", 1103 | " \n", 1104 | " \n", 1105 | " \n", 1106 | " \n", 1107 | " \n", 1108 | " \n", 1109 | " \n", 1110 | " \n", 1111 | "
CPStrikeBidAskMid Strike
DateDays
2009-01-019C920.035.239.1920.0
9C925.031.435.2920.0
9C930.031.033.9920.0
9C935.026.031.5920.0
9C940.026.029.0920.0
\n", 1112 | "
" 1113 | ], 1114 | "text/plain": [ 1115 | " CP Strike Bid Ask Mid Strike\n", 1116 | "Date Days \n", 1117 | "2009-01-01 9 C 920.0 35.2 39.1 920.0\n", 1118 | " 9 C 925.0 31.4 35.2 920.0\n", 1119 | " 9 C 930.0 31.0 33.9 920.0\n", 1120 | " 9 C 935.0 26.0 31.5 920.0\n", 1121 | " 9 C 940.0 26.0 29.0 920.0" 1122 | ] 1123 | }, 1124 | "execution_count": 10, 1125 | "metadata": {}, 1126 | "output_type": "execute_result" 1127 | } 1128 | ], 1129 | "source": [ 1130 | "calls.head()" 1131 | ] 1132 | }, 1133 | { 1134 | "cell_type": "markdown", 1135 | "metadata": {}, 1136 | "source": [ 1137 | "## Remove all quotes after two consecutive zero bids" 1138 | ] 1139 | }, 1140 | { 1141 | "cell_type": "code", 1142 | "execution_count": 11, 1143 | "metadata": { 1144 | "pycharm": { 1145 | "is_executing": false 1146 | } 1147 | }, 1148 | "outputs": [ 1149 | { 1150 | "data": { 1151 | "text/html": [ 1152 | "
\n", 1153 | "\n", 1166 | "\n", 1167 | " \n", 1168 | " \n", 1169 | " \n", 1170 | " \n", 1171 | " \n", 1172 | " \n", 1173 | " \n", 1174 | " \n", 1175 | " \n", 1176 | " \n", 1177 | " \n", 1178 | " \n", 1179 | " \n", 1180 | " \n", 1181 | " \n", 1182 | " \n", 1183 | " \n", 1184 | " \n", 1185 | " \n", 1186 | " \n", 1187 | " \n", 1188 | " \n", 1189 | " \n", 1190 | " \n", 1191 | " \n", 1192 | " \n", 1193 | " \n", 1194 | " \n", 1195 | " \n", 1196 | " \n", 1197 | " \n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | " \n", 1202 | " \n", 1203 | " \n", 1204 | " \n", 1205 | " \n", 1206 | " \n", 1207 | " \n", 1208 | " \n", 1209 | " \n", 1210 | " \n", 1211 | " \n", 1212 | " \n", 1213 | " \n", 1214 | " \n", 1215 | " \n", 1216 | " \n", 1217 | " \n", 1218 | " \n", 1219 | " \n", 1220 | " \n", 1221 | " \n", 1222 | " \n", 1223 | " \n", 1224 | " \n", 1225 | " \n", 1226 | " \n", 1227 | " \n", 1228 | " \n", 1229 | " \n", 1230 | " \n", 1231 | " \n", 1232 | " \n", 1233 | " \n", 1234 | " \n", 1235 | " \n", 1236 | " \n", 1237 | " \n", 1238 | " \n", 1239 | " \n", 1240 | " \n", 1241 | " \n", 1242 | " \n", 1243 | " \n", 1244 | "
CPStrikeBidAskMid Strikezero_bidzero_bid_accum
DateDays
2009-01-019C1210.00.050.5920.000
9C1215.00.050.5920.000
9C1220.00.051.0920.000
9C1225.00.001.0920.011
9C1230.00.001.0920.012
\n", 1245 | "
" 1246 | ], 1247 | "text/plain": [ 1248 | " CP Strike Bid Ask Mid Strike zero_bid zero_bid_accum\n", 1249 | "Date Days \n", 1250 | "2009-01-01 9 C 1210.0 0.05 0.5 920.0 0 0\n", 1251 | " 9 C 1215.0 0.05 0.5 920.0 0 0\n", 1252 | " 9 C 1220.0 0.05 1.0 920.0 0 0\n", 1253 | " 9 C 1225.0 0.00 1.0 920.0 1 1\n", 1254 | " 9 C 1230.0 0.00 1.0 920.0 1 2" 1255 | ] 1256 | }, 1257 | "execution_count": 11, 1258 | "metadata": {}, 1259 | "output_type": "execute_result" 1260 | } 1261 | ], 1262 | "source": [ 1263 | "# Indicator of zero bid\n", 1264 | "calls = calls.assign(zero_bid=lambda df: (df[\"Bid\"] == 0).astype(int))\n", 1265 | "# Accumulate number of zero bids starting at-the-money\n", 1266 | "calls[\"zero_bid_accum\"] = calls.groupby(level=[\"Date\", \"Days\"])[\"zero_bid\"].cumsum()\n", 1267 | "\n", 1268 | "# Sort puts in reverse order inside date/term\n", 1269 | "puts = puts.groupby(level=[\"Date\", \"Days\"]).apply(lambda x: x.sort_values([\"Strike\"], ascending=False))\n", 1270 | "# # Indicator of zero bid\n", 1271 | "puts = puts.assign(zero_bid=lambda df: (df[\"Bid\"] == 0).astype(int))\n", 1272 | "# # Accumulate number of zero bids starting at-the-money\n", 1273 | "puts[\"zero_bid_accum\"] = puts.groupby(level=[\"Date\", \"Days\"])[\"zero_bid\"].cumsum()\n", 1274 | "#\n", 1275 | "calls[(calls[\"Strike\"] >= 1210) & (calls[\"Strike\"] <= 1240)].head()" 1276 | ] 1277 | }, 1278 | { 1279 | "cell_type": "code", 1280 | "execution_count": 12, 1281 | "metadata": { 1282 | "pycharm": { 1283 | "is_executing": false 1284 | } 1285 | }, 1286 | "outputs": [ 1287 | { 1288 | "data": { 1289 | "text/html": [ 1290 | "
\n", 1291 | "\n", 1304 | "\n", 1305 | " \n", 1306 | " \n", 1307 | " \n", 1308 | " \n", 1309 | " \n", 1310 | " \n", 1311 | " \n", 1312 | " \n", 1313 | " \n", 1314 | " \n", 1315 | " \n", 1316 | " \n", 1317 | " \n", 1318 | " \n", 1319 | " \n", 1320 | " \n", 1321 | " \n", 1322 | " \n", 1323 | " \n", 1324 | " \n", 1325 | " \n", 1326 | " \n", 1327 | " \n", 1328 | " \n", 1329 | " \n", 1330 | " \n", 1331 | " \n", 1332 | " \n", 1333 | " \n", 1334 | " \n", 1335 | " \n", 1336 | "
CPCP
DateDaysStrike
2009-01-019920.037.1536.65
37920.061.5560.55
\n", 1337 | "
" 1338 | ], 1339 | "text/plain": [ 1340 | "CP C P\n", 1341 | "Date Days Strike \n", 1342 | "2009-01-01 9 920.0 37.15 36.65\n", 1343 | " 37 920.0 61.55 60.55" 1344 | ] 1345 | }, 1346 | "execution_count": 12, 1347 | "metadata": {}, 1348 | "output_type": "execute_result" 1349 | } 1350 | ], 1351 | "source": [ 1352 | "# Merge puts and cals\n", 1353 | "options3 = pd.concat([calls, puts]).reset_index()\n", 1354 | "# Throw away bad stuff\n", 1355 | "options3 = options3[(options3[\"zero_bid_accum\"] < 2) & (options3[\"Bid\"] > 0)]\n", 1356 | "\n", 1357 | "# Compute option premium as bid/ask average\n", 1358 | "options3[\"Premium\"] = (options3[\"Bid\"] + options3[\"Ask\"]) / 2\n", 1359 | "options3 = options3.set_index([\"Date\", \"Days\", \"CP\", \"Strike\"])[\"Premium\"].unstack(\"CP\")\n", 1360 | "\n", 1361 | "options3.dropna().head()" 1362 | ] 1363 | }, 1364 | { 1365 | "cell_type": "markdown", 1366 | "metadata": {}, 1367 | "source": [ 1368 | "## Compute out-of-the-money option price" 1369 | ] 1370 | }, 1371 | { 1372 | "cell_type": "code", 1373 | "execution_count": 13, 1374 | "metadata": { 1375 | "pycharm": { 1376 | "is_executing": false 1377 | } 1378 | }, 1379 | "outputs": [ 1380 | { 1381 | "data": { 1382 | "text/html": [ 1383 | "
\n", 1384 | "\n", 1397 | "\n", 1398 | " \n", 1399 | " \n", 1400 | " \n", 1401 | " \n", 1402 | " \n", 1403 | " \n", 1404 | " \n", 1405 | " \n", 1406 | " \n", 1407 | " \n", 1408 | " \n", 1409 | " \n", 1410 | " \n", 1411 | " \n", 1412 | " \n", 1413 | " \n", 1414 | " \n", 1415 | " \n", 1416 | " \n", 1417 | " \n", 1418 | " \n", 1419 | " \n", 1420 | " \n", 1421 | " \n", 1422 | " \n", 1423 | " \n", 1424 | " \n", 1425 | " \n", 1426 | " \n", 1427 | " \n", 1428 | " \n", 1429 | " \n", 1430 | " \n", 1431 | " \n", 1432 | " \n", 1433 | " \n", 1434 | " \n", 1435 | " \n", 1436 | " \n", 1437 | " \n", 1438 | " \n", 1439 | " \n", 1440 | " \n", 1441 | " \n", 1442 | " \n", 1443 | " \n", 1444 | " \n", 1445 | " \n", 1446 | " \n", 1447 | "
StrikeMid StrikePremium
DateDays
2009-01-019910.0920.031.70
9915.0920.033.55
9920.0920.036.90
9925.0920.033.30
9930.0920.032.45
\n", 1448 | "
" 1449 | ], 1450 | "text/plain": [ 1451 | " Strike Mid Strike Premium\n", 1452 | "Date Days \n", 1453 | "2009-01-01 9 910.0 920.0 31.70\n", 1454 | " 9 915.0 920.0 33.55\n", 1455 | " 9 920.0 920.0 36.90\n", 1456 | " 9 925.0 920.0 33.30\n", 1457 | " 9 930.0 920.0 32.45" 1458 | ] 1459 | }, 1460 | "execution_count": 13, 1461 | "metadata": {}, 1462 | "output_type": "execute_result" 1463 | } 1464 | ], 1465 | "source": [ 1466 | "# Merge wth at-the-money strike price\n", 1467 | "left = options3.reset_index().set_index([\"Date\", \"Days\"])\n", 1468 | "df = pd.merge(left, mid_strike, left_index=True, right_index=True)\n", 1469 | "\n", 1470 | "# Conditions to separate out-of-the-money puts and calls\n", 1471 | "condition1 = df[\"Strike\"] < df[\"Mid Strike\"]\n", 1472 | "condition2 = df[\"Strike\"] > df[\"Mid Strike\"]\n", 1473 | "# At-the-money we have two quotes, so take the average\n", 1474 | "df[\"Premium\"] = (df[\"P\"] + df[\"C\"]) / 2\n", 1475 | "# Remove in-the-money options\n", 1476 | "df.loc[condition1, \"Premium\"] = df.loc[condition1, \"P\"]\n", 1477 | "df.loc[condition2, \"Premium\"] = df.loc[condition2, \"C\"]\n", 1478 | "\n", 1479 | "options4 = df[[\"Strike\", \"Mid Strike\", \"Premium\"]].copy()\n", 1480 | "\n", 1481 | "options4[(options4[\"Strike\"] >= 910) & (options4[\"Strike\"] <= 930)].head()" 1482 | ] 1483 | }, 1484 | { 1485 | "cell_type": "markdown", 1486 | "metadata": {}, 1487 | "source": [ 1488 | "## Compute difference between adjoining strikes" 1489 | ] 1490 | }, 1491 | { 1492 | "cell_type": "code", 1493 | "execution_count": 14, 1494 | "metadata": { 1495 | "pycharm": { 1496 | "is_executing": false 1497 | } 1498 | }, 1499 | "outputs": [ 1500 | { 1501 | "data": { 1502 | "text/html": [ 1503 | "
\n", 1504 | "\n", 1517 | "\n", 1518 | " \n", 1519 | " \n", 1520 | " \n", 1521 | " \n", 1522 | " \n", 1523 | " \n", 1524 | " \n", 1525 | " \n", 1526 | " \n", 1527 | " \n", 1528 | " \n", 1529 | " \n", 1530 | " \n", 1531 | " \n", 1532 | " \n", 1533 | " \n", 1534 | " \n", 1535 | " \n", 1536 | " \n", 1537 | " \n", 1538 | " \n", 1539 | " \n", 1540 | " \n", 1541 | " \n", 1542 | " \n", 1543 | " \n", 1544 | " \n", 1545 | " \n", 1546 | " \n", 1547 | " \n", 1548 | " \n", 1549 | " \n", 1550 | " \n", 1551 | " \n", 1552 | " \n", 1553 | " \n", 1554 | " \n", 1555 | " \n", 1556 | " \n", 1557 | " \n", 1558 | " \n", 1559 | " \n", 1560 | " \n", 1561 | " \n", 1562 | " \n", 1563 | " \n", 1564 | " \n", 1565 | " \n", 1566 | " \n", 1567 | " \n", 1568 | " \n", 1569 | " \n", 1570 | " \n", 1571 | " \n", 1572 | " \n", 1573 | " \n", 1574 | "
StrikeMid StrikePremiumdK
DateDays
2009-01-019400.0920.00.12525.0
9425.0920.00.12525.0
9450.0920.00.12522.5
9470.0920.00.15012.5
9475.0920.00.1505.0
\n", 1575 | "
" 1576 | ], 1577 | "text/plain": [ 1578 | " Strike Mid Strike Premium dK\n", 1579 | "Date Days \n", 1580 | "2009-01-01 9 400.0 920.0 0.125 25.0\n", 1581 | " 9 425.0 920.0 0.125 25.0\n", 1582 | " 9 450.0 920.0 0.125 22.5\n", 1583 | " 9 470.0 920.0 0.150 12.5\n", 1584 | " 9 475.0 920.0 0.150 5.0" 1585 | ] 1586 | }, 1587 | "execution_count": 14, 1588 | "metadata": {}, 1589 | "output_type": "execute_result" 1590 | } 1591 | ], 1592 | "source": [ 1593 | "def compute_adjoining_strikes_diff(group):\n", 1594 | " new = group.copy()\n", 1595 | " new.iloc[1:-1] = np.array((group.values[2:] - group.values[:-2]) / 2)\n", 1596 | " new.iloc[0] = group.values[1] - group.values[0]\n", 1597 | " new.iloc[-1] = group.values[-1] - group.values[-2]\n", 1598 | " return new\n", 1599 | "\n", 1600 | "\n", 1601 | "options4[\"dK\"] = options4.groupby([\"Date\", \"Days\"])[\"Strike\"].transform(compute_adjoining_strikes_diff)\n", 1602 | "\n", 1603 | "options4.head()" 1604 | ] 1605 | }, 1606 | { 1607 | "cell_type": "markdown", 1608 | "metadata": {}, 1609 | "source": [ 1610 | "## Compute contribution of each strike" 1611 | ] 1612 | }, 1613 | { 1614 | "cell_type": "code", 1615 | "execution_count": 15, 1616 | "metadata": { 1617 | "pycharm": { 1618 | "is_executing": false 1619 | } 1620 | }, 1621 | "outputs": [ 1622 | { 1623 | "data": { 1624 | "text/html": [ 1625 | "
\n", 1626 | "\n", 1639 | "\n", 1640 | " \n", 1641 | " \n", 1642 | " \n", 1643 | " \n", 1644 | " \n", 1645 | " \n", 1646 | " \n", 1647 | " \n", 1648 | " \n", 1649 | " \n", 1650 | " \n", 1651 | " \n", 1652 | " \n", 1653 | " \n", 1654 | " \n", 1655 | " \n", 1656 | " \n", 1657 | " \n", 1658 | " \n", 1659 | " \n", 1660 | " \n", 1661 | " \n", 1662 | " \n", 1663 | " \n", 1664 | " \n", 1665 | " \n", 1666 | " \n", 1667 | " \n", 1668 | " \n", 1669 | " \n", 1670 | " \n", 1671 | " \n", 1672 | " \n", 1673 | " \n", 1674 | " \n", 1675 | " \n", 1676 | " \n", 1677 | " \n", 1678 | " \n", 1679 | " \n", 1680 | " \n", 1681 | " \n", 1682 | " \n", 1683 | " \n", 1684 | " \n", 1685 | " \n", 1686 | " \n", 1687 | " \n", 1688 | " \n", 1689 | " \n", 1690 | " \n", 1691 | " \n", 1692 | " \n", 1693 | " \n", 1694 | " \n", 1695 | " \n", 1696 | " \n", 1697 | " \n", 1698 | " \n", 1699 | " \n", 1700 | " \n", 1701 | " \n", 1702 | " \n", 1703 | " \n", 1704 | " \n", 1705 | " \n", 1706 | " \n", 1707 | " \n", 1708 | " \n", 1709 | " \n", 1710 | "
DateDaysStrikeMid StrikePremiumdKRatesigma2
02009-01-019400.0920.00.12525.00.380.000020
12009-01-019425.0920.00.12525.00.380.000017
22009-01-019450.0920.00.12522.50.380.000014
32009-01-019470.0920.00.15012.50.380.000008
42009-01-019475.0920.00.1505.00.380.000003
\n", 1711 | "
" 1712 | ], 1713 | "text/plain": [ 1714 | " Date Days Strike Mid Strike Premium dK Rate sigma2\n", 1715 | "0 2009-01-01 9 400.0 920.0 0.125 25.0 0.38 0.000020\n", 1716 | "1 2009-01-01 9 425.0 920.0 0.125 25.0 0.38 0.000017\n", 1717 | "2 2009-01-01 9 450.0 920.0 0.125 22.5 0.38 0.000014\n", 1718 | "3 2009-01-01 9 470.0 920.0 0.150 12.5 0.38 0.000008\n", 1719 | "4 2009-01-01 9 475.0 920.0 0.150 5.0 0.38 0.000003" 1720 | ] 1721 | }, 1722 | "execution_count": 15, 1723 | "metadata": {}, 1724 | "output_type": "execute_result" 1725 | } 1726 | ], 1727 | "source": [ 1728 | "# Merge with risk-free rate\n", 1729 | "contrib = pd.merge(options4, yields, left_index=True, right_index=True).reset_index()\n", 1730 | "\n", 1731 | "contrib[\"sigma2\"] = contrib[\"dK\"] / contrib[\"Strike\"] ** 2\n", 1732 | "contrib[\"sigma2\"] *= contrib[\"Premium\"] * np.exp(contrib[\"Rate\"] * contrib[\"Days\"] / 36500)\n", 1733 | "\n", 1734 | "contrib.head()" 1735 | ] 1736 | }, 1737 | { 1738 | "cell_type": "markdown", 1739 | "metadata": {}, 1740 | "source": [ 1741 | "## Compute each preiod index" 1742 | ] 1743 | }, 1744 | { 1745 | "cell_type": "code", 1746 | "execution_count": 16, 1747 | "metadata": { 1748 | "pycharm": { 1749 | "is_executing": false 1750 | } 1751 | }, 1752 | "outputs": [ 1753 | { 1754 | "data": { 1755 | "text/html": [ 1756 | "
\n", 1757 | "\n", 1770 | "\n", 1771 | " \n", 1772 | " \n", 1773 | " \n", 1774 | " \n", 1775 | " \n", 1776 | " \n", 1777 | " \n", 1778 | " \n", 1779 | " \n", 1780 | " \n", 1781 | " \n", 1782 | " \n", 1783 | " \n", 1784 | " \n", 1785 | " \n", 1786 | " \n", 1787 | " \n", 1788 | " \n", 1789 | " \n", 1790 | " \n", 1791 | " \n", 1792 | " \n", 1793 | " \n", 1794 | "
sigma2
DateDays
2009-01-0190.472767
370.366818
\n", 1795 | "
" 1796 | ], 1797 | "text/plain": [ 1798 | " sigma2\n", 1799 | "Date Days \n", 1800 | "2009-01-01 9 0.472767\n", 1801 | " 37 0.366818" 1802 | ] 1803 | }, 1804 | "execution_count": 16, 1805 | "metadata": {}, 1806 | "output_type": "execute_result" 1807 | } 1808 | ], 1809 | "source": [ 1810 | "# Sum up contributions from all strikes\n", 1811 | "sigma2 = contrib.groupby([\"Date\", \"Days\"])[[\"sigma2\"]].sum() * 2\n", 1812 | "\n", 1813 | "# Merge at-the-money strike and implied forward\n", 1814 | "sigma2[\"Mid Strike\"] = mid_strike\n", 1815 | "sigma2[\"Forward\"] = forward\n", 1816 | "\n", 1817 | "# Compute variance for each term\n", 1818 | "sigma2[\"sigma2\"] -= (sigma2[\"Forward\"] / sigma2[\"Mid Strike\"] - 1) ** 2\n", 1819 | "sigma2[\"sigma2\"] /= sigma2.index.get_level_values(1).astype(float) / 365\n", 1820 | "sigma2 = sigma2[[\"sigma2\"]]\n", 1821 | "\n", 1822 | "sigma2.head()" 1823 | ] 1824 | }, 1825 | { 1826 | "cell_type": "markdown", 1827 | "metadata": {}, 1828 | "source": [ 1829 | "## Compute interpolated index" 1830 | ] 1831 | }, 1832 | { 1833 | "cell_type": "code", 1834 | "execution_count": 17, 1835 | "metadata": { 1836 | "pycharm": { 1837 | "is_executing": false 1838 | } 1839 | }, 1840 | "outputs": [ 1841 | { 1842 | "data": { 1843 | "text/html": [ 1844 | "
\n", 1845 | "\n", 1858 | "\n", 1859 | " \n", 1860 | " \n", 1861 | " \n", 1862 | " \n", 1863 | " \n", 1864 | " \n", 1865 | " \n", 1866 | " \n", 1867 | " \n", 1868 | " \n", 1869 | " \n", 1870 | " \n", 1871 | " \n", 1872 | " \n", 1873 | " \n", 1874 | " \n", 1875 | " \n", 1876 | " \n", 1877 | " \n", 1878 | " \n", 1879 | " \n", 1880 | " \n", 1881 | " \n", 1882 | " \n", 1883 | " \n", 1884 | "
T1T2sigma2_T1sigma2_T2
Date
2009-01-019370.4727670.366818
\n", 1885 | "
" 1886 | ], 1887 | "text/plain": [ 1888 | " T1 T2 sigma2_T1 sigma2_T2\n", 1889 | "Date \n", 1890 | "2009-01-01 9 37 0.472767 0.366818" 1891 | ] 1892 | }, 1893 | "execution_count": 17, 1894 | "metadata": {}, 1895 | "output_type": "execute_result" 1896 | } 1897 | ], 1898 | "source": [ 1899 | "# This function determines near- and next-term if there are several maturities in the data\n", 1900 | "def f(group):\n", 1901 | " days = np.array(group[\"Days\"])\n", 1902 | " sigma2 = np.array(group[\"sigma2\"])\n", 1903 | "\n", 1904 | " if days.min() <= 30:\n", 1905 | " T1 = days[days <= 30].max()\n", 1906 | " else:\n", 1907 | " T1 = days.min()\n", 1908 | "\n", 1909 | " T2 = days[days > T1].min()\n", 1910 | "\n", 1911 | " sigma_T1 = sigma2[days == T1][0]\n", 1912 | " sigma_T2 = sigma2[days == T2][0]\n", 1913 | "\n", 1914 | " return pd.DataFrame([{\"T1\": T1, \"T2\": T2, \"sigma2_T1\": sigma_T1, \"sigma2_T2\": sigma_T2}])\n", 1915 | "\n", 1916 | "\n", 1917 | "two_sigmas = sigma2.reset_index().groupby(\"Date\").apply(f, include_groups=False).groupby(level=\"Date\").first()\n", 1918 | "\n", 1919 | "two_sigmas.head()" 1920 | ] 1921 | }, 1922 | { 1923 | "cell_type": "markdown", 1924 | "metadata": {}, 1925 | "source": [ 1926 | "## Interpolate the VIX" 1927 | ] 1928 | }, 1929 | { 1930 | "cell_type": "code", 1931 | "execution_count": 18, 1932 | "metadata": { 1933 | "pycharm": { 1934 | "is_executing": false 1935 | } 1936 | }, 1937 | "outputs": [ 1938 | { 1939 | "data": { 1940 | "text/html": [ 1941 | "
\n", 1942 | "\n", 1955 | "\n", 1956 | " \n", 1957 | " \n", 1958 | " \n", 1959 | " \n", 1960 | " \n", 1961 | " \n", 1962 | " \n", 1963 | " \n", 1964 | " \n", 1965 | " \n", 1966 | " \n", 1967 | " \n", 1968 | " \n", 1969 | " \n", 1970 | " \n", 1971 | " \n", 1972 | "
VIX
Date
2009-01-0161.217999
\n", 1973 | "
" 1974 | ], 1975 | "text/plain": [ 1976 | " VIX\n", 1977 | "Date \n", 1978 | "2009-01-01 61.217999" 1979 | ] 1980 | }, 1981 | "execution_count": 18, 1982 | "metadata": {}, 1983 | "output_type": "execute_result" 1984 | } 1985 | ], 1986 | "source": [ 1987 | "df = two_sigmas.copy()\n", 1988 | "\n", 1989 | "for t in [\"T1\", \"T2\"]:\n", 1990 | " # Convert to fraction of the year\n", 1991 | " df[\"days_\" + t] = df[t].astype(float) / 365\n", 1992 | " # Convert to miutes\n", 1993 | " df[t] = (df[t] - 1) * 1440.0 + 510 + 930\n", 1994 | "\n", 1995 | "df[\"sigma2_T1\"] = df[\"sigma2_T1\"] * df[\"days_T1\"] * (df[\"T2\"] - 30.0 * 1440.0)\n", 1996 | "df[\"sigma2_T2\"] = df[\"sigma2_T2\"] * df[\"days_T2\"] * (30.0 * 1440.0 - df[\"T1\"])\n", 1997 | "df[\"VIX\"] = ((df[\"sigma2_T1\"] + df[\"sigma2_T2\"]) / (df[\"T2\"] - df[\"T1\"]) * 365.0 / 30.0) ** 0.5 * 100\n", 1998 | "\n", 1999 | "VIX = df[[\"VIX\"]]\n", 2000 | "\n", 2001 | "VIX.head()" 2002 | ] 2003 | } 2004 | ], 2005 | "metadata": { 2006 | "kernelspec": { 2007 | "display_name": "Python 3 (ipykernel)", 2008 | "language": "python", 2009 | "name": "python3" 2010 | }, 2011 | "language_info": { 2012 | "codemirror_mode": { 2013 | "name": "ipython", 2014 | "version": 3 2015 | }, 2016 | "file_extension": ".py", 2017 | "mimetype": "text/x-python", 2018 | "name": "python", 2019 | "nbconvert_exporter": "python", 2020 | "pygments_lexer": "ipython3", 2021 | "version": "3.13.0" 2022 | }, 2023 | "pycharm": { 2024 | "stem_cell": { 2025 | "cell_type": "raw", 2026 | "metadata": { 2027 | "collapsed": false 2028 | }, 2029 | "source": [] 2030 | } 2031 | } 2032 | }, 2033 | "nbformat": 4, 2034 | "nbformat_minor": 4 2035 | } 2036 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vix" 3 | description = "VIX and related volatility indices" 4 | authors = [{ name = "Stanislav Khrapov", email = "khrapovs@gmail.com" }] 5 | readme = "README.md" 6 | classifiers = [ 7 | "Programming Language :: Python", 8 | "Programming Language :: Python :: 3.10", 9 | "Programming Language :: Python :: 3.11", 10 | "Programming Language :: Python :: 3.12", 11 | "Programming Language :: Python :: 3.13", 12 | ] 13 | requires-python = ">=3.10" 14 | dependencies = ["pandas", "jupyterlab", "pre-commit>=4.0.1"] 15 | dynamic = ["version"] 16 | 17 | [project.urls] 18 | Source = "https://github.com/khrapovs/vix" 19 | 20 | [build-system] 21 | requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] 22 | build-backend = "setuptools.build_meta" 23 | 24 | [tool.setuptools_scm] 25 | 26 | [tool.ruff] 27 | line-length = 120 28 | src = ["notebooks"] 29 | 30 | [tool.ruff.lint] 31 | select = ["E", "F", "D", "B", "I", "ARG"] 32 | ignore = [ 33 | "D100", 34 | "D101", 35 | "D102", 36 | "D103", 37 | "D104", 38 | "D105", 39 | "D106", 40 | "D107", 41 | "D213", 42 | "D417", 43 | ] 44 | 45 | [tool.mypy] 46 | ignore_missing_imports = true 47 | disallow_untyped_calls = true 48 | disallow_untyped_defs = true 49 | disallow_incomplete_defs = true 50 | -------------------------------------------------------------------------------- /src/vix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khrapovs/vix/0f6511a9e71cdc2f8901b3b43499a0aec366d0b5/src/vix/__init__.py -------------------------------------------------------------------------------- /src/vix/reproduce_vix.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | from pathlib import Path 3 | 4 | import numpy as np 5 | import pandas as pd 6 | 7 | 8 | def import_yields() -> pd.DataFrame: 9 | """Import yields. 10 | 11 | Date,Days,Rate 12 | 20090101,9,0.38 13 | 20090101,37,0.38 14 | 15 | """ 16 | yields = pd.read_csv( 17 | Path(__file__).parents[2] / "data" / "yields.csv", 18 | converters={"Date": lambda x: dt.datetime.strptime(x, "%Y%m%d")}, 19 | ) 20 | return yields.set_index(["Date", "Days"]) 21 | 22 | 23 | def import_options() -> pd.DataFrame: 24 | """Import options. 25 | 26 | Expiration,Days,Strike,Call Bid,Call Ask,Put Bid,Put Ask 27 | 20090110,9,200,717.6,722.8,0,0.05 28 | 20090110,9,250,667.6,672.9,0,0.05 29 | 20090110,9,300,617.9,622.9,0,0.05 30 | 20090110,9,350,567.9,572.9,0,0.05 31 | 20090110,9,375,542.9,547.9,0,0.1 32 | 33 | """ 34 | # Function to parse dates of '20090101' format 35 | raw_options = pd.read_csv( 36 | Path(__file__).parents[2] / "data" / "options.csv", 37 | converters={"Expiration": lambda x: dt.datetime.strptime(x, "%Y%m%d")}, 38 | ) 39 | 40 | # Function to convert days to internal timedelta format 41 | raw_options["Date"] = raw_options["Expiration"] - raw_options["Days"].map(lambda x: dt.timedelta(days=int(x))) 42 | # Convert integer strikes to float! 43 | # Otherwise it may lead to accumulation of errors. 44 | raw_options["Strike"] = raw_options["Strike"].astype(float) 45 | 46 | return raw_options 47 | 48 | 49 | def clean_options(options: pd.DataFrame) -> pd.DataFrame: 50 | """Clean and index option data set.""" 51 | # Since VIX is computed for the date of option quotations, 52 | # we do not really need Expiration 53 | options.set_index(["Date", "Days", "Strike"], inplace=True) 54 | options.drop("Expiration", axis=1, inplace=True) 55 | 56 | # Do some renaming and separate calls from puts 57 | calls = options[["Call Bid", "Call Ask"]].rename(columns={"Call Bid": "Bid", "Call Ask": "Ask"}) 58 | puts = options[["Put Bid", "Put Ask"]].rename(columns={"Put Bid": "Bid", "Put Ask": "Ask"}) 59 | 60 | # Add a column indicating the type of the option 61 | calls["CP"], puts["CP"] = "C", "P" 62 | 63 | # Merge calls and puts 64 | options = pd.concat([calls, puts]) 65 | 66 | # Reindex and sort 67 | options.reset_index(inplace=True) 68 | options.set_index(["Date", "Days", "CP", "Strike"], inplace=True) 69 | options.sort_index(inplace=True) 70 | 71 | return options 72 | 73 | 74 | def bid_ask_average(options: pd.DataFrame) -> pd.DataFrame: 75 | """Compute bid/ask average.""" 76 | # This step is used further to filter out in-the-money options. 77 | 78 | options["Premium"] = (options["Bid"] + options["Ask"]) / 2 79 | options = options[options["Bid"] > 0]["Premium"].unstack("CP") 80 | return options 81 | 82 | 83 | def put_call_parity(options: pd.DataFrame) -> pd.DataFrame: 84 | """Find put-call parity.""" 85 | # Find the absolute difference 86 | options["CPdiff"] = (options["C"] - options["P"]).abs() 87 | # Mark the minimum for each date/term 88 | grouped = options["CPdiff"].groupby(level=["Date", "Days"]) 89 | options["min"] = grouped.transform(lambda x: x == x.min()) 90 | return options 91 | 92 | 93 | def forward_price(options: pd.DataFrame, yields: pd.DataFrame) -> pd.DataFrame: 94 | """Compute forward price.""" 95 | # Leave only at-the-money options 96 | df = options[options["min"] == 1].reset_index("Strike") 97 | df = df.groupby(level=["Date", "Days"]).first().reset_index() 98 | # Merge with risk-free rate 99 | df = pd.merge(df, yields.reset_index(), how="left") 100 | 101 | # Compute the implied forward 102 | df["Forward"] = df["CPdiff"] * np.exp(df["Rate"] * df["Days"] / 36500) 103 | df["Forward"] += df["Strike"] 104 | forward = df.set_index(["Date", "Days"])[["Forward"]] 105 | return forward 106 | 107 | 108 | def at_the_money_strike(options: pd.DataFrame, forward: pd.DataFrame) -> pd.DataFrame: 109 | """Compute at-the-money strike.""" 110 | # Merge options with implied forward price 111 | left = options.reset_index().set_index(["Date", "Days"]) 112 | df = pd.merge(left, forward, left_index=True, right_index=True) 113 | # Compute at-the-money strike 114 | df = df[df["Strike"] < df["Forward"]]["Strike"] 115 | mid_strike = df.groupby(level=["Date", "Days"]).max() 116 | mid_strike = pd.DataFrame({"Mid Strike": mid_strike}) 117 | return mid_strike 118 | 119 | 120 | def leave_out_of_the_money(options: pd.DataFrame, mid_strike: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]: 121 | """Separate out-of-the-money calls and puts.""" 122 | # Go back to original data and reindex it 123 | left = options.reset_index() 124 | left.set_index(["Date", "Days"], inplace=True) 125 | left.drop("Premium", axis=1, inplace=True) 126 | # Merge with at-the-money strike 127 | df = pd.merge(left, mid_strike, left_index=True, right_index=True) 128 | # Separate out-of-the-money calls and puts 129 | P = (df["Strike"] <= df["Mid Strike"]) & (df["CP"] == "P") 130 | C = (df["Strike"] >= df["Mid Strike"]) & (df["CP"] == "C") 131 | puts, calls = df[P], df[C] 132 | return puts, calls 133 | 134 | 135 | def remove_crazy_quotes(calls: pd.DataFrame, puts: pd.DataFrame) -> pd.DataFrame: 136 | """Remove all quotes after two consequtive zero bids.""" 137 | # Indicator of zero bid 138 | calls["zero_bid"] = (calls["Bid"] == 0).astype(int) 139 | # Accumulate number of zero bids starting at-the-money 140 | grouped = calls.groupby(level=["Date", "Days"])["zero_bid"] 141 | calls["zero_bid_accum"] = grouped.cumsum() 142 | 143 | # Sort puts in reverse order inside date/term 144 | grouped = puts.groupby(level=["Date", "Days"]) 145 | puts = grouped.apply(lambda x: x.sort_values(by="Strike", ascending=False)) 146 | # Indicator of zero bid 147 | puts["zero_bid"] = (puts["Bid"] == 0).astype(int) 148 | # Accumulate number of zero bids starting at-the-money 149 | grouped = puts.groupby(level=["Date", "Days"])["zero_bid"] 150 | puts["zero_bid_accum"] = grouped.cumsum() 151 | 152 | # Merge puts and calls 153 | options3 = pd.concat([calls, puts]).reset_index() 154 | # Throw away bad stuff 155 | options3 = options3[(options3["zero_bid_accum"] < 2) & (options3["Bid"] > 0)] 156 | # Compute option premium as bid/ask average 157 | options3["Premium"] = (options3["Bid"] + options3["Ask"]) / 2 158 | options3.set_index(["Date", "Days", "CP", "Strike"], inplace=True) 159 | options3 = options3["Premium"].unstack("CP") 160 | 161 | return options3 162 | 163 | 164 | def out_of_the_money_options(options3: pd.DataFrame, mid_strike: pd.DataFrame) -> pd.DataFrame: 165 | """Compute out-of-the-money option price.""" 166 | # Merge wth at-the-money strike price 167 | left = options3.reset_index().set_index(["Date", "Days"]) 168 | df = pd.merge(left, mid_strike, left_index=True, right_index=True) 169 | 170 | # Conditions to separate out-of-the-money puts and calls 171 | condition1 = df["Strike"] < df["Mid Strike"] 172 | condition2 = df["Strike"] > df["Mid Strike"] 173 | # At-the-money we have two quotes, so take the average 174 | df["Premium"] = (df["P"] + df["C"]) / 2 175 | # Remove in-the-money options 176 | df.loc[condition1, "Premium"] = df.loc[condition1, "P"] 177 | df.loc[condition2, "Premium"] = df.loc[condition2, "C"] 178 | 179 | options4 = df[["Strike", "Mid Strike", "Premium"]].copy() 180 | return options4 181 | 182 | 183 | def compute_adjoining_strikes_diff(group: pd.DataFrame) -> pd.DataFrame: 184 | new = group.copy() 185 | new.iloc[1:-1] = np.array((group.values[2:] - group.values[:-2]) / 2) 186 | new.iloc[0] = group.values[1] - group.values[0] 187 | new.iloc[-1] = group.values[-1] - group.values[-2] 188 | return new 189 | 190 | 191 | def strike_diff(options: pd.DataFrame) -> pd.DataFrame: 192 | """Compute difference between adjoining strikes.""" 193 | options["dK"] = options.groupby(level=["Date", "Days"])["Strike"].transform(compute_adjoining_strikes_diff) 194 | return options 195 | 196 | 197 | def strike_contribution(options4: pd.DataFrame, yields: pd.DataFrame) -> pd.DataFrame: 198 | """Compute contribution of each strike.""" 199 | # Merge with risk-free rate 200 | contrib = pd.merge(options4, yields, left_index=True, right_index=True) 201 | contrib.reset_index(inplace=True) 202 | 203 | contrib["sigma2"] = contrib["dK"] / contrib["Strike"] ** 2 204 | contrib["sigma2"] *= contrib["Premium"] * np.exp(contrib["Rate"] * contrib["Days"] / 36500) 205 | 206 | return contrib 207 | 208 | 209 | def each_period_vol(contrib: pd.DataFrame, mid_strike: pd.DataFrame, forward: pd.DataFrame) -> pd.DataFrame: 210 | """Compute each preiod index.""" 211 | # Sum up contributions from all strikes 212 | sigma2 = contrib.groupby(["Date", "Days"])[["sigma2"]].sum() * 2 213 | 214 | # Merge at-the-money strike and implied forward 215 | sigma2["Mid Strike"] = mid_strike 216 | sigma2["Forward"] = forward 217 | 218 | # Compute variance for each term 219 | sigma2["sigma2"] -= (sigma2["Forward"] / sigma2["Mid Strike"] - 1) ** 2 220 | sigma2["sigma2"] /= sigma2.index.get_level_values(1).astype(float) / 365 221 | sigma2 = sigma2[["sigma2"]] 222 | 223 | return sigma2 224 | 225 | 226 | def near_next_term(group: pd.DataFrame) -> pd.DataFrame: 227 | """Determine near- and next-term if there are several maturities in the data.""" 228 | days = np.array(group["Days"]) 229 | sigma2 = np.array(group["sigma2"]) 230 | 231 | if days.min() < 30: 232 | T1 = days[days < 30].max() 233 | T2 = days[days > T1].min() 234 | elif (days.min() == 30) or (days.max() == 30): 235 | T1 = T2 = 30 236 | else: 237 | T1 = days.min() 238 | T2 = days[days > T1].min() 239 | 240 | sigma_T1 = sigma2[days == T1][0] 241 | sigma_T2 = sigma2[days == T2][0] 242 | data = [{"T1": T1, "T2": T2, "sigma2_T1": sigma_T1, "sigma2_T2": sigma_T2}] 243 | 244 | return pd.DataFrame(data) 245 | 246 | 247 | def interpolate_vol(sigma2: pd.DataFrame) -> pd.DataFrame: 248 | """Compute interpolated index.""" 249 | grouped = sigma2.reset_index().groupby("Date") 250 | two_sigmas = grouped.apply(near_next_term, include_groups=False).groupby(level="Date").first() 251 | return two_sigmas 252 | 253 | 254 | def interpolate_vix(two_sigmas: pd.DataFrame) -> pd.DataFrame: 255 | """Interpolate the VIX.""" 256 | df = two_sigmas.copy() 257 | 258 | for t in ["T1", "T2"]: 259 | # Convert to fraction of the year 260 | df["days_" + t] = df[t].astype(float) / 365 261 | # Convert to miutes 262 | df[t] = (df[t] - 1) * 1440 + 510 + 930 263 | 264 | denom = df["T2"] - df["T1"] 265 | if denom.iloc[0] > 0: 266 | coef1 = df["days_T1"] * (df["T2"] - 30 * 1440) / denom 267 | coef2 = df["days_T2"] * (30 * 1440 - df["T1"]) / denom 268 | else: 269 | coef1 = coef2 = df["days_T1"] 270 | df["sigma2_T1"] = df["sigma2_T1"] * coef1 271 | df["sigma2_T2"] = df["sigma2_T2"] * coef2 272 | df["VIX"] = ((df["sigma2_T1"] + df["sigma2_T2"]) * 365 / 30) ** 0.5 * 100 273 | 274 | return df 275 | 276 | 277 | def whitepaper() -> pd.DataFrame: 278 | ### Import yields 279 | yields = import_yields() 280 | 281 | ### Import options 282 | raw_options = import_options() 283 | 284 | # Uncomment this block to check that VIX is computed, 285 | # when there are options with exactly 30 days to expire. 286 | # yields.reset_index('Days', inplace=True) 287 | # yields['Days'] += 21 288 | # yields.set_index('Days', inplace=True, append=True) 289 | # raw_options['Days'] += 21 290 | 291 | ### Do some cleaning and indexing 292 | options = clean_options(raw_options) 293 | 294 | ### Compute bid/ask average 295 | options2 = bid_ask_average(options) 296 | 297 | ### Put-call parity 298 | options2 = put_call_parity(options2) 299 | 300 | ### Compute forward price 301 | forward = forward_price(options2, yields) 302 | 303 | ### Compute at-the-money strike 304 | mid_strike = at_the_money_strike(options2, forward) 305 | 306 | ### Separate out-of-the-money calls and puts 307 | puts, calls = leave_out_of_the_money(options, mid_strike) 308 | 309 | ### Remove all quotes after two consequtive zero bids 310 | options3 = remove_crazy_quotes(calls, puts) 311 | 312 | ### Compute out-of-the-money option price 313 | options4 = out_of_the_money_options(options3, mid_strike) 314 | 315 | ### Compute difference between adjoining strikes 316 | options4 = strike_diff(options4) 317 | 318 | ### Compute contribution of each strike 319 | contrib = strike_contribution(options4, yields) 320 | 321 | ### Compute each preiod index 322 | sigma2 = each_period_vol(contrib, mid_strike, forward) 323 | 324 | ### Compute interpolated index 325 | two_sigmas = interpolate_vol(sigma2) 326 | 327 | ### Interpolate the VIX 328 | df = interpolate_vix(two_sigmas) 329 | 330 | return df[["VIX"]] 331 | 332 | 333 | if __name__ == "__main__": 334 | vixvalue = whitepaper() 335 | print(vixvalue) 336 | --------------------------------------------------------------------------------