├── .gitignore ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE.md ├── Pipfile ├── Pipfile.lock ├── README.md ├── docs ├── Makefile ├── about.rst ├── conf.py └── index.rst ├── evkit ├── __init__.py ├── capital.py ├── config.json ├── financials.py ├── launcher.py ├── plot.py ├── scrapper.py ├── sec_datareader.py ├── urldata.py └── utils.py ├── media ├── graphs_dcf_wacc.png └── report_csv.png ├── setup.py └── tests ├── __init__.py └── test_sec_datareader.py /.gitignore: -------------------------------------------------------------------------------- 1 | # System artifacts 2 | .DS_Store 3 | .vscode 4 | __pycache__ 5 | *.egg-info 6 | 7 | # Sphinx artifacts 8 | _build 9 | _static 10 | _temlates/ 11 | 12 | *.bin 13 | *.csv 14 | *.png 15 | *.htm 16 | *.txt 17 | *.zip -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | python: 9 | version: 3 10 | 11 | # Build documentation in the docs/ directory with Sphinx 12 | sphinx: 13 | configuration: docs/conf.py 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## [Unreleased] 9 | ## [0.10.0] - 2020-MM-DD 10 | ### Added 11 | - New structure of the project 12 | - downloader and parser of financial statements from SEC EDGAR database 13 | 14 | ### Removed 15 | - DCF-WACC functionality 16 | - Cost of capital functionality 17 | - financial statement analysis functionality 18 | 19 | 20 | ## [0.8.2] - 2019-07-02 21 | ### Fixed 22 | - YahooFinance tokens for ticker pools 23 | - shares outstanding value id 24 | 25 | 26 | ## [0.8.1] - 2019-06-19 27 | ### Changed 28 | - typos in README 29 | - result table screenshot 30 | 31 | ### Fixed 32 | - get_value validity condition prevented extracting interest for IS 33 | 34 | 35 | ## [0.8.0] - 2019-06-19 36 | ### Added 37 | - version tags 38 | - installation and users guide to README 39 | - plots for beta-wacc and dcf-mkt quote 40 | 41 | ### Changed 42 | - license, social and build badges in README.md 43 | - renamed modules/ dir into evkit/ 44 | - renamed main file into launcher.py 45 | - prettify README 46 | 47 | 48 | ## [0.7.0] - 2019-06-16 49 | ### Added 50 | - automated ticker list extraction with html page offset 51 | - missing data exceptions handling 52 | - writer of valuation results to csv report 53 | - collection IDs of sector/industry securities 54 | - mkt share price getter 55 | - basic interface for selecting stocks pool 56 | - performance and stability improvements 57 | - package files: setup.py, environment.yaml 58 | - Semantic Versioning structure to CHANGELOG.md 59 | - license and build badges to README.md 60 | - sample reports from test-runs 61 | 62 | ### Changed 63 | - LT growth rate estimate as fraction of WACC 64 | - rd estimate as interest/total debt 65 | - EQ as EV + cash - total debt 66 | - GitHub repo name to https://github.com/lialkaas/evkit 67 | - moved capital, financials, utils into modules dir 68 | - renamed launcher into evkit.py 69 | 70 | 71 | ## [0.6.0] - 2019-06-10 72 | ### Added 73 | - complete rebase of the project 74 | - Web data extraction: financial statements, rf, market return, stock stats and summary 75 | - Financials: pro-forma Income statement, Balance Sheet, CF Statement 76 | - Cost of Capital: re, rd, betas, wacc 77 | - appropriate CHANGELOG.md 78 | - MIT license to modules 79 | 80 | ### Changed 81 | - name of the project to Equity valuation kit 82 | 83 | ### Removed 84 | - sensitivity reports 85 | - outdated tutorials (all of them) 86 | - irrelevant utils 87 | - all functionality, except bare minimum for DCF-WACC valuation 88 | 89 | 90 | ## [0.5.0] - 2018-11-19 91 | ### Added 92 | - modular design of the program 93 | - source of cost of capital assumptions from YahooFinance 94 | - source of financials for DCF from YahooFinance 95 | - no-API design 96 | 97 | ### Changed 98 | - reduced number of user assumptions to ticker 99 | - optimization tweaks 100 | 101 | 102 | ## [0.4.0] - 2018-09-24 103 | ### Added 104 | - Multiples module 105 | - basic public comparables ratios: EPS, P/E, EV/EBITDA, Debt/EBITDA 106 | 107 | ### Changed 108 | - optimized accuracy and precision of capital structure sensitivity analysis report 109 | - deploying reports into "reports" directory with a company name 110 | - compressed unnecessary functions into nested functions 111 | 112 | 113 | ## [0.3.0] - 2018-09-23 114 | ### Added 115 | - sensitivity analysis of share price to LT growth rate 116 | - detailed capital structure report, incl. credit metrics 117 | - detailed report of optimal capital structure 118 | - EXACT method for estimating rd and credit rating from interest coverage ratio 119 | - source of credit ratings from Prof. Damodaran database (NYU Stern) 120 | 121 | ### Changed 122 | - reduced number of key assumptions 123 | - optimized betas' unlevering/relevering algorithm 124 | 125 | 126 | ## [0.2.0] - 2018-09-21 127 | ### Added 128 | - detailed report of capital budgeting 129 | - pro forma cash-flow statement for a chosen investment horizon 130 | - Revenue to FCFF top-down approach for financial highlights 131 | - robust DCF-WACC: Equity value does not depend on investment horizon 132 | - basic sensitivity analysis of capital structure 133 | - rd, re, WACC for different level of leverage 134 | - optimal capital structure estimate for max Equity value 135 | - write sensitivity analysis report to .csv file 136 | 137 | ### Changed 138 | - improved delevering/relevering betas 139 | 140 | 141 | ## [0.1.0] - 2018-09-19 142 | ### Added 143 | - Initial commit 144 | - cost of capital: re, rd, WACC 145 | - basic DCF-WACC valuation 146 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oleksii Lialka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | pandas = "*" 8 | numpy = "*" 9 | matplotlib = "*" 10 | sec_edgar_downloader = "*" 11 | bs4 = "*" 12 | html5lib = "*" 13 | 14 | [dev-packages] 15 | flake8 = "*" 16 | pytest = ">=5.4.3" 17 | pylint = "*" 18 | yapf = "*" 19 | mypy = "*" 20 | ipykernel = "*" 21 | 22 | [requires] 23 | python_version = "3.10" 24 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "353e25fe00bcc5c1f0dbe75b862ced93c1dbe3dc40be5ad4bdb3039818e4b645" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "beautifulsoup4": { 20 | "hashes": [ 21 | "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", 22 | "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" 23 | ], 24 | "markers": "python_full_version >= '3.6.0'", 25 | "version": "==4.11.1" 26 | }, 27 | "bs4": { 28 | "hashes": [ 29 | "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a" 30 | ], 31 | "index": "pypi", 32 | "version": "==0.0.1" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", 37 | "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" 38 | ], 39 | "markers": "python_full_version >= '3.6.0'", 40 | "version": "==2022.6.15" 41 | }, 42 | "charset-normalizer": { 43 | "hashes": [ 44 | "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", 45 | "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" 46 | ], 47 | "markers": "python_full_version >= '3.6.0'", 48 | "version": "==2.1.0" 49 | }, 50 | "cycler": { 51 | "hashes": [ 52 | "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3", 53 | "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f" 54 | ], 55 | "markers": "python_full_version >= '3.6.0'", 56 | "version": "==0.11.0" 57 | }, 58 | "faker": { 59 | "hashes": [ 60 | "sha256:067a03f64e555261610e69277536072997b4576dbf84b113faef3c06d85b466b", 61 | "sha256:0e00bfa1eadf1493f15662edb181222fea4847764cf3f9ff3e66ee0f95c9a644" 62 | ], 63 | "markers": "python_full_version >= '3.6.0'", 64 | "version": "==14.1.0" 65 | }, 66 | "fonttools": { 67 | "hashes": [ 68 | "sha256:cb91ef8d5a435d90aeb3ab814b2548c6b515df5bc13b4c5adaa23778f2f79823", 69 | "sha256:e637d2fe06bddabbfc488e02ef32d04d561e3c71e9ba11abc7782ea753ceb218" 70 | ], 71 | "markers": "python_version >= '3.7'", 72 | "version": "==4.36.0" 73 | }, 74 | "html5lib": { 75 | "hashes": [ 76 | "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", 77 | "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" 78 | ], 79 | "index": "pypi", 80 | "version": "==1.1" 81 | }, 82 | "idna": { 83 | "hashes": [ 84 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", 85 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" 86 | ], 87 | "markers": "python_version >= '3.5'", 88 | "version": "==3.3" 89 | }, 90 | "kiwisolver": { 91 | "hashes": [ 92 | "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b", 93 | "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166", 94 | "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c", 95 | "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c", 96 | "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0", 97 | "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4", 98 | "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9", 99 | "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286", 100 | "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767", 101 | "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c", 102 | "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6", 103 | "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b", 104 | "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004", 105 | "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf", 106 | "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494", 107 | "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac", 108 | "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626", 109 | "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766", 110 | "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514", 111 | "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6", 112 | "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f", 113 | "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d", 114 | "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191", 115 | "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d", 116 | "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51", 117 | "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f", 118 | "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8", 119 | "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454", 120 | "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb", 121 | "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da", 122 | "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8", 123 | "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de", 124 | "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a", 125 | "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9", 126 | "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008", 127 | "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3", 128 | "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32", 129 | "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938", 130 | "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1", 131 | "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9", 132 | "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d", 133 | "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824", 134 | "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b", 135 | "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd", 136 | "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2", 137 | "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5", 138 | "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69", 139 | "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3", 140 | "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae", 141 | "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597", 142 | "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e", 143 | "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955", 144 | "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca", 145 | "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a", 146 | "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea", 147 | "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede", 148 | "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4", 149 | "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6", 150 | "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686", 151 | "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408", 152 | "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871", 153 | "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29", 154 | "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750", 155 | "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897", 156 | "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0", 157 | "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2", 158 | "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09", 159 | "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c" 160 | ], 161 | "markers": "python_version >= '3.7'", 162 | "version": "==1.4.4" 163 | }, 164 | "lxml": { 165 | "hashes": [ 166 | "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", 167 | "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", 168 | "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", 169 | "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", 170 | "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", 171 | "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", 172 | "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", 173 | "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", 174 | "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", 175 | "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", 176 | "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", 177 | "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", 178 | "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", 179 | "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", 180 | "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", 181 | "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", 182 | "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", 183 | "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", 184 | "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", 185 | "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", 186 | "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", 187 | "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", 188 | "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", 189 | "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", 190 | "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", 191 | "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", 192 | "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", 193 | "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", 194 | "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", 195 | "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", 196 | "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", 197 | "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", 198 | "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", 199 | "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", 200 | "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", 201 | "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", 202 | "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", 203 | "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", 204 | "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", 205 | "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", 206 | "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", 207 | "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", 208 | "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", 209 | "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", 210 | "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", 211 | "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", 212 | "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", 213 | "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", 214 | "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", 215 | "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", 216 | "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", 217 | "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", 218 | "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", 219 | "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", 220 | "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", 221 | "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", 222 | "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", 223 | "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", 224 | "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", 225 | "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", 226 | "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", 227 | "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", 228 | "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", 229 | "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", 230 | "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", 231 | "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", 232 | "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", 233 | "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", 234 | "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", 235 | "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" 236 | ], 237 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 238 | "version": "==4.9.1" 239 | }, 240 | "matplotlib": { 241 | "hashes": [ 242 | "sha256:0bcdfcb0f976e1bac6721d7d457c17be23cf7501f977b6a38f9d38a3762841f7", 243 | "sha256:1e64ac9be9da6bfff0a732e62116484b93b02a0b4d4b19934fb4f8e7ad26ad6a", 244 | "sha256:22227c976ad4dc8c5a5057540421f0d8708c6560744ad2ad638d48e2984e1dbc", 245 | "sha256:2886cc009f40e2984c083687251821f305d811d38e3df8ded414265e4583f0c5", 246 | "sha256:2e6d184ebe291b9e8f7e78bbab7987d269c38ea3e062eace1fe7d898042ef804", 247 | "sha256:3211ba82b9f1518d346f6309df137b50c3dc4421b4ed4815d1d7eadc617f45a1", 248 | "sha256:339cac48b80ddbc8bfd05daae0a3a73414651a8596904c2a881cfd1edb65f26c", 249 | "sha256:35a8ad4dddebd51f94c5d24bec689ec0ec66173bf614374a1244c6241c1595e0", 250 | "sha256:3b4fa56159dc3c7f9250df88f653f085068bcd32dcd38e479bba58909254af7f", 251 | "sha256:43e9d3fa077bf0cc95ded13d331d2156f9973dce17c6f0c8b49ccd57af94dbd9", 252 | "sha256:57f1b4e69f438a99bb64d7f2c340db1b096b41ebaa515cf61ea72624279220ce", 253 | "sha256:5c096363b206a3caf43773abebdbb5a23ea13faef71d701b21a9c27fdcef72f4", 254 | "sha256:6bb93a0492d68461bd458eba878f52fdc8ac7bdb6c4acdfe43dba684787838c2", 255 | "sha256:6ea6aef5c4338e58d8d376068e28f80a24f54e69f09479d1c90b7172bad9f25b", 256 | "sha256:6fe807e8a22620b4cd95cfbc795ba310dc80151d43b037257250faf0bfcd82bc", 257 | "sha256:73dd93dc35c85dece610cca8358003bf0760d7986f70b223e2306b4ea6d1406b", 258 | "sha256:839d47b8ead7ad9669aaacdbc03f29656dc21f0d41a6fea2d473d856c39c8b1c", 259 | "sha256:874df7505ba820e0400e7091199decf3ff1fde0583652120c50cd60d5820ca9a", 260 | "sha256:879c7e5fce4939c6aa04581dfe08d57eb6102a71f2e202e3314d5fbc072fd5a0", 261 | "sha256:94ff86af56a3869a4ae26a9637a849effd7643858a1a04dd5ee50e9ab75069a7", 262 | "sha256:99482b83ebf4eb6d5fc6813d7aacdefdd480f0d9c0b52dcf9f1cc3b2c4b3361a", 263 | "sha256:9ab29589cef03bc88acfa3a1490359000c18186fc30374d8aa77d33cc4a51a4a", 264 | "sha256:9befa5954cdbc085e37d974ff6053da269474177921dd61facdad8023c4aeb51", 265 | "sha256:a206a1b762b39398efea838f528b3a6d60cdb26fe9d58b48265787e29cd1d693", 266 | "sha256:ab8d26f07fe64f6f6736d635cce7bfd7f625320490ed5bfc347f2cdb4fae0e56", 267 | "sha256:b28de401d928890187c589036857a270a032961411934bdac4cf12dde3d43094", 268 | "sha256:b428076a55fb1c084c76cb93e68006f27d247169f056412607c5c88828d08f88", 269 | "sha256:bf618a825deb6205f015df6dfe6167a5d9b351203b03fab82043ae1d30f16511", 270 | "sha256:c995f7d9568f18b5db131ab124c64e51b6820a92d10246d4f2b3f3a66698a15b", 271 | "sha256:cd45a6f3e93a780185f70f05cf2a383daed13c3489233faad83e81720f7ede24", 272 | "sha256:d2484b350bf3d32cae43f85dcfc89b3ed7bd2bcd781ef351f93eb6fb2cc483f9", 273 | "sha256:d62880e1f60e5a30a2a8484432bcb3a5056969dc97258d7326ad465feb7ae069", 274 | "sha256:dacddf5bfcec60e3f26ec5c0ae3d0274853a258b6c3fc5ef2f06a8eb23e042be", 275 | "sha256:f3840c280ebc87a48488a46f760ea1c0c0c83fcf7abbe2e6baf99d033fd35fd8", 276 | "sha256:f814504e459c68118bf2246a530ed953ebd18213dc20e3da524174d84ed010b2" 277 | ], 278 | "index": "pypi", 279 | "version": "==3.5.3" 280 | }, 281 | "numpy": { 282 | "hashes": [ 283 | "sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0", 284 | "sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f", 285 | "sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0", 286 | "sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde", 287 | "sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913", 288 | "sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8", 289 | "sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38", 290 | "sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6", 291 | "sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842", 292 | "sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414", 293 | "sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e", 294 | "sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074", 295 | "sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f", 296 | "sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d", 297 | "sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418", 298 | "sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01", 299 | "sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215", 300 | "sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66", 301 | "sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5", 302 | "sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389", 303 | "sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77", 304 | "sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c", 305 | "sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722", 306 | "sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c", 307 | "sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d", 308 | "sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450", 309 | "sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5", 310 | "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524" 311 | ], 312 | "index": "pypi", 313 | "version": "==1.23.2" 314 | }, 315 | "packaging": { 316 | "hashes": [ 317 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 318 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 319 | ], 320 | "markers": "python_full_version >= '3.6.0'", 321 | "version": "==21.3" 322 | }, 323 | "pandas": { 324 | "hashes": [ 325 | "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6", 326 | "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d", 327 | "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5", 328 | "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76", 329 | "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81", 330 | "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1", 331 | "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c", 332 | "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf", 333 | "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d", 334 | "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e", 335 | "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0", 336 | "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230", 337 | "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e", 338 | "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84", 339 | "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92", 340 | "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f", 341 | "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4", 342 | "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640", 343 | "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318", 344 | "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef", 345 | "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112" 346 | ], 347 | "index": "pypi", 348 | "version": "==1.4.3" 349 | }, 350 | "pillow": { 351 | "hashes": [ 352 | "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", 353 | "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", 354 | "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", 355 | "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", 356 | "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", 357 | "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", 358 | "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", 359 | "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", 360 | "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", 361 | "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", 362 | "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", 363 | "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", 364 | "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", 365 | "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", 366 | "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", 367 | "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", 368 | "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", 369 | "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", 370 | "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", 371 | "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", 372 | "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", 373 | "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", 374 | "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", 375 | "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", 376 | "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", 377 | "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", 378 | "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", 379 | "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", 380 | "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", 381 | "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", 382 | "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", 383 | "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", 384 | "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", 385 | "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", 386 | "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", 387 | "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", 388 | "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", 389 | "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", 390 | "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", 391 | "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", 392 | "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", 393 | "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", 394 | "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", 395 | "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", 396 | "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", 397 | "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", 398 | "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", 399 | "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", 400 | "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", 401 | "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", 402 | "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", 403 | "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", 404 | "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", 405 | "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", 406 | "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", 407 | "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", 408 | "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", 409 | "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" 410 | ], 411 | "markers": "python_version >= '3.7'", 412 | "version": "==9.2.0" 413 | }, 414 | "pyparsing": { 415 | "hashes": [ 416 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 417 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 418 | ], 419 | "markers": "python_full_version >= '3.6.8'", 420 | "version": "==3.0.9" 421 | }, 422 | "python-dateutil": { 423 | "hashes": [ 424 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 425 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 426 | ], 427 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 428 | "version": "==2.8.2" 429 | }, 430 | "pytz": { 431 | "hashes": [ 432 | "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197", 433 | "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5" 434 | ], 435 | "version": "==2022.2.1" 436 | }, 437 | "requests": { 438 | "hashes": [ 439 | "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", 440 | "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" 441 | ], 442 | "markers": "python_version >= '3.7' and python_version < '4'", 443 | "version": "==2.28.1" 444 | }, 445 | "sec-edgar-downloader": { 446 | "hashes": [ 447 | "sha256:62916974a84108c0f7f719af3e8f35663c056cfd603e011440237a216ce1ff93", 448 | "sha256:cdf9a0609d55b1477ada99f2810d3785cd97624c49a8d2f850ba04685123b33d" 449 | ], 450 | "index": "pypi", 451 | "version": "==4.3.0" 452 | }, 453 | "six": { 454 | "hashes": [ 455 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 456 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 457 | ], 458 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 459 | "version": "==1.16.0" 460 | }, 461 | "soupsieve": { 462 | "hashes": [ 463 | "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", 464 | "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" 465 | ], 466 | "markers": "python_full_version >= '3.6.0'", 467 | "version": "==2.3.2.post1" 468 | }, 469 | "urllib3": { 470 | "hashes": [ 471 | "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", 472 | "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" 473 | ], 474 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", 475 | "version": "==1.26.11" 476 | }, 477 | "webencodings": { 478 | "hashes": [ 479 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 480 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 481 | ], 482 | "version": "==0.5.1" 483 | } 484 | }, 485 | "develop": { 486 | "appnope": { 487 | "hashes": [ 488 | "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", 489 | "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" 490 | ], 491 | "markers": "platform_system == 'Darwin'", 492 | "version": "==0.1.3" 493 | }, 494 | "astroid": { 495 | "hashes": [ 496 | "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b", 497 | "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946" 498 | ], 499 | "markers": "python_full_version >= '3.6.2'", 500 | "version": "==2.11.7" 501 | }, 502 | "asttokens": { 503 | "hashes": [ 504 | "sha256:c61e16246ecfb2cde2958406b4c8ebc043c9e6d73aaa83c941673b35e5d3a76b", 505 | "sha256:e3305297c744ae53ffa032c45dc347286165e4ffce6875dc662b205db0623d86" 506 | ], 507 | "version": "==2.0.8" 508 | }, 509 | "attrs": { 510 | "hashes": [ 511 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", 512 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" 513 | ], 514 | "markers": "python_version >= '3.5'", 515 | "version": "==22.1.0" 516 | }, 517 | "backcall": { 518 | "hashes": [ 519 | "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", 520 | "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" 521 | ], 522 | "version": "==0.2.0" 523 | }, 524 | "debugpy": { 525 | "hashes": [ 526 | "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16", 527 | "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161", 528 | "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae", 529 | "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25", 530 | "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6", 531 | "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908", 532 | "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f", 533 | "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734", 534 | "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b", 535 | "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585", 536 | "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3", 537 | "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d", 538 | "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d", 539 | "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a", 540 | "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4", 541 | "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2", 542 | "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf", 543 | "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7" 544 | ], 545 | "markers": "python_version >= '3.7'", 546 | "version": "==1.6.3" 547 | }, 548 | "decorator": { 549 | "hashes": [ 550 | "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", 551 | "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" 552 | ], 553 | "markers": "python_version >= '3.5'", 554 | "version": "==5.1.1" 555 | }, 556 | "dill": { 557 | "hashes": [ 558 | "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", 559 | "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86" 560 | ], 561 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", 562 | "version": "==0.3.5.1" 563 | }, 564 | "entrypoints": { 565 | "hashes": [ 566 | "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4", 567 | "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f" 568 | ], 569 | "markers": "python_version >= '3.6'", 570 | "version": "==0.4" 571 | }, 572 | "executing": { 573 | "hashes": [ 574 | "sha256:9c745f80cda11eb22b62cbecf21156491a794eb56ab06f9d286a44e62822b24e", 575 | "sha256:d1cd87c2e371e9966261410c5b3769d6df2f9e4a79a83eebd2662dd3388f9833" 576 | ], 577 | "version": "==0.10.0" 578 | }, 579 | "flake8": { 580 | "hashes": [ 581 | "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", 582 | "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248" 583 | ], 584 | "index": "pypi", 585 | "version": "==5.0.4" 586 | }, 587 | "iniconfig": { 588 | "hashes": [ 589 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 590 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 591 | ], 592 | "version": "==1.1.1" 593 | }, 594 | "ipykernel": { 595 | "hashes": [ 596 | "sha256:37acc3254caa8a0dafcddddc8dc863a60ad1b46487b68aee361d9a15bda98112", 597 | "sha256:d8969c5b23b0e453a23166da5a669c954db399789293fcb03fec5cb25367e43c" 598 | ], 599 | "index": "pypi", 600 | "version": "==6.15.1" 601 | }, 602 | "ipython": { 603 | "hashes": [ 604 | "sha256:7ca74052a38fa25fe9bedf52da0be7d3fdd2fb027c3b778ea78dfe8c212937d1", 605 | "sha256:f2db3a10254241d9b447232cec8b424847f338d9d36f9a577a6192c332a46abd" 606 | ], 607 | "markers": "python_version >= '3.8'", 608 | "version": "==8.4.0" 609 | }, 610 | "isort": { 611 | "hashes": [ 612 | "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", 613 | "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" 614 | ], 615 | "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", 616 | "version": "==5.10.1" 617 | }, 618 | "jedi": { 619 | "hashes": [ 620 | "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", 621 | "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" 622 | ], 623 | "markers": "python_version >= '3.6'", 624 | "version": "==0.18.1" 625 | }, 626 | "jupyter-client": { 627 | "hashes": [ 628 | "sha256:17d74b0d0a7b24f1c8c527b24fcf4607c56bee542ffe8e3418e50b21e514b621", 629 | "sha256:aa9a6c32054b290374f95f73bb0cae91455c58dfb84f65c8591912b8f65e6d56" 630 | ], 631 | "markers": "python_version >= '3.7'", 632 | "version": "==7.3.4" 633 | }, 634 | "jupyter-core": { 635 | "hashes": [ 636 | "sha256:2e5f244d44894c4154d06aeae3419dd7f1b0ef4494dc5584929b398c61cfd314", 637 | "sha256:715e22bb6cc7db3718fddfac1f69f1c7e899ca00e42bdfd4bf3705452b9fd84a" 638 | ], 639 | "markers": "python_version >= '3.7'", 640 | "version": "==4.11.1" 641 | }, 642 | "lazy-object-proxy": { 643 | "hashes": [ 644 | "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", 645 | "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", 646 | "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", 647 | "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", 648 | "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", 649 | "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", 650 | "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", 651 | "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", 652 | "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", 653 | "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", 654 | "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", 655 | "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", 656 | "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", 657 | "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", 658 | "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", 659 | "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", 660 | "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", 661 | "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", 662 | "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", 663 | "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", 664 | "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", 665 | "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", 666 | "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", 667 | "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", 668 | "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", 669 | "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", 670 | "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", 671 | "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", 672 | "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", 673 | "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", 674 | "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", 675 | "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", 676 | "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", 677 | "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", 678 | "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", 679 | "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", 680 | "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" 681 | ], 682 | "markers": "python_version >= '3.6'", 683 | "version": "==1.7.1" 684 | }, 685 | "matplotlib-inline": { 686 | "hashes": [ 687 | "sha256:a68624e181d5b272bbfbaadb44412c9d3c9ebbcb703404502b9c937afc377ff5", 688 | "sha256:a728d796a1a44265b310340ef04ba8aba4e89dcb76dfdd1272becab4923dd867" 689 | ], 690 | "markers": "python_version >= '3.5'", 691 | "version": "==0.1.5" 692 | }, 693 | "mccabe": { 694 | "hashes": [ 695 | "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", 696 | "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" 697 | ], 698 | "markers": "python_version >= '3.6'", 699 | "version": "==0.7.0" 700 | }, 701 | "mypy": { 702 | "hashes": [ 703 | "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", 704 | "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", 705 | "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", 706 | "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", 707 | "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0", 708 | "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", 709 | "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", 710 | "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09", 711 | "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417", 712 | "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", 713 | "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2", 714 | "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", 715 | "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", 716 | "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8", 717 | "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", 718 | "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", 719 | "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", 720 | "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", 721 | "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", 722 | "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca", 723 | "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", 724 | "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", 725 | "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c" 726 | ], 727 | "index": "pypi", 728 | "version": "==0.971" 729 | }, 730 | "mypy-extensions": { 731 | "hashes": [ 732 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 733 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 734 | ], 735 | "version": "==0.4.3" 736 | }, 737 | "nest-asyncio": { 738 | "hashes": [ 739 | "sha256:b98e3ec1b246135e4642eceffa5a6c23a3ab12c82ff816a92c612d68205813b2", 740 | "sha256:e442291cd942698be619823a17a86a5759eabe1f8613084790de189fe9e16d65" 741 | ], 742 | "markers": "python_version >= '3.5'", 743 | "version": "==1.5.5" 744 | }, 745 | "packaging": { 746 | "hashes": [ 747 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 748 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 749 | ], 750 | "markers": "python_full_version >= '3.6.0'", 751 | "version": "==21.3" 752 | }, 753 | "parso": { 754 | "hashes": [ 755 | "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", 756 | "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" 757 | ], 758 | "markers": "python_version >= '3.6'", 759 | "version": "==0.8.3" 760 | }, 761 | "pexpect": { 762 | "hashes": [ 763 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 764 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 765 | ], 766 | "markers": "sys_platform != 'win32'", 767 | "version": "==4.8.0" 768 | }, 769 | "pickleshare": { 770 | "hashes": [ 771 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 772 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 773 | ], 774 | "version": "==0.7.5" 775 | }, 776 | "platformdirs": { 777 | "hashes": [ 778 | "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", 779 | "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" 780 | ], 781 | "markers": "python_version >= '3.7'", 782 | "version": "==2.5.2" 783 | }, 784 | "pluggy": { 785 | "hashes": [ 786 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 787 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 788 | ], 789 | "markers": "python_version >= '3.6'", 790 | "version": "==1.0.0" 791 | }, 792 | "prompt-toolkit": { 793 | "hashes": [ 794 | "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", 795 | "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" 796 | ], 797 | "markers": "python_full_version >= '3.6.2'", 798 | "version": "==3.0.30" 799 | }, 800 | "psutil": { 801 | "hashes": [ 802 | "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", 803 | "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", 804 | "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", 805 | "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", 806 | "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", 807 | "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", 808 | "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", 809 | "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", 810 | "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", 811 | "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", 812 | "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", 813 | "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", 814 | "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", 815 | "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", 816 | "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", 817 | "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", 818 | "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", 819 | "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", 820 | "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", 821 | "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", 822 | "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", 823 | "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", 824 | "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", 825 | "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", 826 | "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", 827 | "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", 828 | "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", 829 | "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", 830 | "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", 831 | "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", 832 | "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", 833 | "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" 834 | ], 835 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 836 | "version": "==5.9.1" 837 | }, 838 | "ptyprocess": { 839 | "hashes": [ 840 | "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", 841 | "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" 842 | ], 843 | "version": "==0.7.0" 844 | }, 845 | "pure-eval": { 846 | "hashes": [ 847 | "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", 848 | "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" 849 | ], 850 | "version": "==0.2.2" 851 | }, 852 | "py": { 853 | "hashes": [ 854 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 855 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 856 | ], 857 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 858 | "version": "==1.11.0" 859 | }, 860 | "pycodestyle": { 861 | "hashes": [ 862 | "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", 863 | "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" 864 | ], 865 | "markers": "python_version >= '3.6'", 866 | "version": "==2.9.1" 867 | }, 868 | "pyflakes": { 869 | "hashes": [ 870 | "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", 871 | "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" 872 | ], 873 | "markers": "python_version >= '3.6'", 874 | "version": "==2.5.0" 875 | }, 876 | "pygments": { 877 | "hashes": [ 878 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 879 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 880 | ], 881 | "markers": "python_version >= '3.6'", 882 | "version": "==2.13.0" 883 | }, 884 | "pylint": { 885 | "hashes": [ 886 | "sha256:487ce2192eee48211269a0e976421f334cf94de1806ca9d0a99449adcdf0285e", 887 | "sha256:fabe30000de7d07636d2e82c9a518ad5ad7908590fe135ace169b44839c15f90" 888 | ], 889 | "index": "pypi", 890 | "version": "==2.14.5" 891 | }, 892 | "pyparsing": { 893 | "hashes": [ 894 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 895 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 896 | ], 897 | "markers": "python_full_version >= '3.6.8'", 898 | "version": "==3.0.9" 899 | }, 900 | "pytest": { 901 | "hashes": [ 902 | "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", 903 | "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" 904 | ], 905 | "index": "pypi", 906 | "version": "==7.1.2" 907 | }, 908 | "python-dateutil": { 909 | "hashes": [ 910 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 911 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 912 | ], 913 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 914 | "version": "==2.8.2" 915 | }, 916 | "pyzmq": { 917 | "hashes": [ 918 | "sha256:022cf5ea7bcaa8a06a03c2706e0ae66904b6138b2155577cd34c64bc7cc637ab", 919 | "sha256:044447ae4b2016a6b8697571fd633f799f860b19b76c4a2fd9b1140d52ee6745", 920 | "sha256:07ed8aaf7ffe150af873269690cc654ffeca7491f62aae0f3821baa181f8d5fe", 921 | "sha256:10d1910ec381b851aeb024a042a13db178cb1edf125e76a4e9d2548ad103aadb", 922 | "sha256:12e62ff0d5223ec09b597ab6d73858b9f64a51221399f3cb08aa495e1dff7935", 923 | "sha256:1f368a82b29f80071781b20663c0fc0c8f6b13273f9f5abe1526af939534f90f", 924 | "sha256:20bafc4095eab00f41a510579363a3f5e1f5c69d7ee10f1d88895c4df0259183", 925 | "sha256:2141e6798d5981be04c08996d27962086a1aa3ea536fe9cf7e89817fd4523f86", 926 | "sha256:23e708fbfdf4ee3107422b69ca65da1b9f056b431fc0888096a8c1d6cd908e8f", 927 | "sha256:28dbdb90b2f6b131f8f10e6081012e4e25234213433420e67e0c1162de537113", 928 | "sha256:29b74774a0bfd3c4d98ac853f0bdca55bd9ec89d5b0def5486407cca54472ef8", 929 | "sha256:2b381aa867ece7d0a82f30a0c7f3d4387b7cf2e0697e33efaa5bed6c5784abcd", 930 | "sha256:2f67b63f53c6994d601404fd1a329e6d940ac3dd1d92946a93b2b9c70df67b9f", 931 | "sha256:342ca3077f47ec2ee41b9825142b614e03e026347167cbc72a59b618c4f6106c", 932 | "sha256:35e635343ff367f697d00fa1484262bb68e36bc74c9b80737eac5a1e04c4e1b1", 933 | "sha256:385609812eafd9970c3752c51f2f6c4f224807e3e441bcfd8c8273877d00c8a8", 934 | "sha256:38e106b64bad744fe469dc3dd864f2764d66399178c1bf39d45294cc7980f14f", 935 | "sha256:39dd252b683816935702825e5bf775df16090619ced9bb4ba68c2d0b6f0c9b18", 936 | "sha256:407f909c4e8fde62fbdad9ebd448319792258cc0550c2815567a4d9d8d9e6d18", 937 | "sha256:415ff62ac525d9add1e3550430a09b9928d2d24a20cc4ce809e67caac41219ab", 938 | "sha256:4805af9614b0b41b7e57d17673459facf85604dac502a5a9244f6e8c9a4de658", 939 | "sha256:48400b96788cdaca647021bf19a9cd668384f46e4d9c55cf045bdd17f65299c8", 940 | "sha256:49d30ba7074f469e8167917abf9eb854c6503ae10153034a6d4df33618f1db5f", 941 | "sha256:4bb798bef181648827019001f6be43e1c48b34b477763b37a8d27d8c06d197b8", 942 | "sha256:4d6f110c56f7d5b4d64dde3a382ae61b6d48174e30742859d8e971b18b6c9e5c", 943 | "sha256:55568a020ad2cae9ae36da6058e7ca332a56df968f601cbdb7cf6efb2a77579a", 944 | "sha256:565bd5ab81f6964fc4067ccf2e00877ad0fa917308975694bbb54378389215f8", 945 | "sha256:5c558b50402fca1acc94329c5d8f12aa429738904a5cfb32b9ed3c61235221bb", 946 | "sha256:5e05492be125dce279721d6b54fd1b956546ecc4bcdfcf8e7b4c413bc0874c10", 947 | "sha256:624fd38071a817644acdae075b92a23ea0bdd126a58148288e8284d23ec361ce", 948 | "sha256:650389bbfca73955b262b2230423d89992f38ec48033307ae80e700eaa2fbb63", 949 | "sha256:67975a9e1237b9ccc78f457bef17691bbdd2055a9d26e81ee914ba376846d0ce", 950 | "sha256:6b1e79bba24f6df1712e3188d5c32c480d8eda03e8ecff44dc8ecb0805fa62f3", 951 | "sha256:6fd5d0d50cbcf4bc376861529a907bed026a4cbe8c22a500ff8243231ef02433", 952 | "sha256:71b32a1e827bdcbf73750e60370d3b07685816ff3d8695f450f0f8c3226503f8", 953 | "sha256:794871988c34727c7f79bdfe2546e6854ae1fa2e1feb382784f23a9c6c63ecb3", 954 | "sha256:79a87831b47a9f6161ad23fa5e89d5469dc585abc49f90b9b07fea8905ae1234", 955 | "sha256:7e0113d70b095339e99bb522fe7294f5ae6a7f3b2b8f52f659469a74b5cc7661", 956 | "sha256:84678153432241bcdca2210cf4ff83560b200556867aea913ffbb960f5d5f340", 957 | "sha256:8a68f57b7a3f7b6b52ada79876be1efb97c8c0952423436e84d70cc139f16f0d", 958 | "sha256:8c02a0cd39dc01659b3d6cb70bb3a41aebd9885fd78239acdd8d9c91351c4568", 959 | "sha256:8c842109d31a9281d678f668629241c405928afbebd913c48a5a8e7aee61f63d", 960 | "sha256:8dc66f109a245653b19df0f44a5af7a3f14cb8ad6c780ead506158a057bd36ce", 961 | "sha256:90d88f9d9a2ae6cfb1dc4ea2d1710cdf6456bc1b9a06dd1bb485c5d298f2517e", 962 | "sha256:9269fbfe3a4eb2009199120861c4571ef1655fdf6951c3e7f233567c94e8c602", 963 | "sha256:929d548b74c0f82f7f95b54e4a43f9e4ce2523cfb8a54d3f7141e45652304b2a", 964 | "sha256:99a5a77a10863493a1ee8dece02578c6b32025fb3afff91b40476bc489e81648", 965 | "sha256:9a39ddb0431a68954bd318b923230fa5b649c9c62b0e8340388820c5f1b15bd2", 966 | "sha256:9d0ab2936085c85a1fc6f9fd8f89d5235ae99b051e90ec5baa5e73ad44346e1f", 967 | "sha256:9e5bf6e7239fc9687239de7a283aa8b801ab85371116045b33ae20132a1325d6", 968 | "sha256:a0f09d85c45f58aa8e715b42f8b26beba68b3b63a8f7049113478aca26efbc30", 969 | "sha256:a114992a193577cb62233abf8cb2832970f9975805a64740e325d2f895e7f85a", 970 | "sha256:a3fd44b5046d247e7f0f1660bcafe7b5fb0db55d0934c05dd57dda9e1f823ce7", 971 | "sha256:ad28ddb40db8e450d7d4bf8a1d765d3f87b63b10e7e9a825a3c130c6371a8c03", 972 | "sha256:aecd6ceaccc4b594e0092d6513ef3f1c0fa678dd89f86bb8ff1a47014b8fca35", 973 | "sha256:b815991c7d024bf461f358ad871f2be1135576274caed5749c4828859e40354e", 974 | "sha256:b861db65f6b8906c8d6db51dde2448f266f0c66bf28db2c37aea50f58a849859", 975 | "sha256:c3ebf1668664d20c8f7d468955f18379b7d1f7bc8946b13243d050fa3888c7ff", 976 | "sha256:c56b1a62a1fb87565343c57b6743fd5da6e138b8c6562361d7d9b5ce4acf399a", 977 | "sha256:c780acddd2934c6831ff832ecbf78a45a7b62d4eb216480f863854a8b7d54fa7", 978 | "sha256:c890309296f53f9aa32ffcfc51d805705e1982bffd27c9692a8f1e1b8de279f4", 979 | "sha256:c9cfaf530e6a7ff65f0afe275e99f983f68b54dfb23ea401f0bc297a632766b6", 980 | "sha256:d904f6595acfaaf99a1a61881fea068500c40374d263e5e073aa4005e5f9c28a", 981 | "sha256:e06747014a5ad1b28cebf5bc1ddcdaccfb44e9b441d35e6feb1286c8a72e54be", 982 | "sha256:e1fe30bcd5aea5948c42685fad910cd285eacb2518ea4dc6c170d6b535bee95d", 983 | "sha256:e753eee6d3b93c5354e8ba0a1d62956ee49355f0a36e00570823ef64e66183f5", 984 | "sha256:ec9803aca9491fd6f0d853d2a6147f19f8deaaa23b1b713d05c5d09e56ea7142", 985 | "sha256:efb9e38b2a590282704269585de7eb33bf43dc294cad092e1b172e23d4c217e5", 986 | "sha256:f07016e3cf088dbfc6e7c5a7b3f540db5c23b0190d539e4fd3e2b5e6beffa4b5", 987 | "sha256:f392cbea531b7142d1958c0d4a0c9c8d760dc451e5848d8dd3387804d3e3e62c", 988 | "sha256:f619fd38fc2641abfb53cca719c165182500600b82c695cc548a0f05f764be05", 989 | "sha256:fefdf9b685fda4141b95ebec975946076a5e0723ff70b037032b2085c5317684", 990 | "sha256:ffc6b1623d0f9affb351db4ca61f432dca3628a5ee015f9bf2bfbe9c6836881c" 991 | ], 992 | "markers": "python_version >= '3.6'", 993 | "version": "==23.2.1" 994 | }, 995 | "setuptools": { 996 | "hashes": [ 997 | "sha256:101bf15ca723beef42c8db91a761f3748d4d697e17fae904db60c0b619d8d094", 998 | "sha256:39275e7aafa4a4f0f4308f2302c6ee384dcdacdbaacc1e30dcbb6fd824c625bb" 999 | ], 1000 | "markers": "python_version >= '3.7'", 1001 | "version": "==65.0.2" 1002 | }, 1003 | "six": { 1004 | "hashes": [ 1005 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 1006 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 1007 | ], 1008 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1009 | "version": "==1.16.0" 1010 | }, 1011 | "stack-data": { 1012 | "hashes": [ 1013 | "sha256:a90ae7e260f7d15aefeceb46f0a028d4ccb9eb8856475c53e341945342d41ea7", 1014 | "sha256:b94fed36d725cfabc6d09ed5886913e35eed9009766a1af1d5941b9da3a94aaa" 1015 | ], 1016 | "version": "==0.4.0" 1017 | }, 1018 | "tomli": { 1019 | "hashes": [ 1020 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 1021 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 1022 | ], 1023 | "markers": "python_version < '3.11'", 1024 | "version": "==2.0.1" 1025 | }, 1026 | "tomlkit": { 1027 | "hashes": [ 1028 | "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c", 1029 | "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83" 1030 | ], 1031 | "markers": "python_version >= '3.6' and python_version < '4.0'", 1032 | "version": "==0.11.4" 1033 | }, 1034 | "tornado": { 1035 | "hashes": [ 1036 | "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca", 1037 | "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72", 1038 | "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23", 1039 | "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8", 1040 | "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b", 1041 | "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9", 1042 | "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13", 1043 | "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75", 1044 | "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac", 1045 | "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e", 1046 | "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b" 1047 | ], 1048 | "markers": "python_version >= '3.7'", 1049 | "version": "==6.2" 1050 | }, 1051 | "traitlets": { 1052 | "hashes": [ 1053 | "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2", 1054 | "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a" 1055 | ], 1056 | "markers": "python_version >= '3.7'", 1057 | "version": "==5.3.0" 1058 | }, 1059 | "typing-extensions": { 1060 | "hashes": [ 1061 | "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", 1062 | "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" 1063 | ], 1064 | "markers": "python_version >= '3.7'", 1065 | "version": "==4.3.0" 1066 | }, 1067 | "wcwidth": { 1068 | "hashes": [ 1069 | "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", 1070 | "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" 1071 | ], 1072 | "version": "==0.2.5" 1073 | }, 1074 | "wrapt": { 1075 | "hashes": [ 1076 | "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", 1077 | "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", 1078 | "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", 1079 | "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", 1080 | "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", 1081 | "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", 1082 | "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", 1083 | "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", 1084 | "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", 1085 | "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", 1086 | "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", 1087 | "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", 1088 | "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", 1089 | "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", 1090 | "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", 1091 | "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", 1092 | "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", 1093 | "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", 1094 | "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", 1095 | "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", 1096 | "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", 1097 | "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", 1098 | "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", 1099 | "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", 1100 | "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", 1101 | "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", 1102 | "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", 1103 | "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", 1104 | "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", 1105 | "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", 1106 | "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", 1107 | "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", 1108 | "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", 1109 | "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", 1110 | "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", 1111 | "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", 1112 | "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", 1113 | "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", 1114 | "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", 1115 | "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", 1116 | "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", 1117 | "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", 1118 | "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", 1119 | "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", 1120 | "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", 1121 | "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", 1122 | "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", 1123 | "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", 1124 | "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", 1125 | "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", 1126 | "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", 1127 | "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", 1128 | "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", 1129 | "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", 1130 | "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", 1131 | "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", 1132 | "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", 1133 | "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", 1134 | "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", 1135 | "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", 1136 | "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", 1137 | "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", 1138 | "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", 1139 | "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" 1140 | ], 1141 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1142 | "version": "==1.14.1" 1143 | }, 1144 | "yapf": { 1145 | "hashes": [ 1146 | "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32", 1147 | "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b" 1148 | ], 1149 | "index": "pypi", 1150 | "version": "==0.32.0" 1151 | } 1152 | } 1153 | } 1154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Equity Valuation Kit 2 | 3 | [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/lialkaas/evkit.svg)](CHANGELOG.md) 4 | [![GitHub last commit](https://img.shields.io/github/last-commit/lialkaas/evkit.svg)](CHANGELOG.md) 5 | [![Documentation Status](https://readthedocs.org/projects/evkit/badge/?version=latest)](https://evkit.readthedocs.io/en/latest/?badge=latest) 6 | [![GitHub](https://img.shields.io/github/license/lialkaas/evkit.svg)](LICENSE.md) 7 | 8 | ML/AI powered solution for forecasting stock price from SEC Financial Statement Data Sets. 9 | 10 | ## Description 11 | 12 | Application performs equity valuation of NYSE-listed companies by extracting and analyzing data from public sources. DCF-WACC approach is the core of the valuation algorithm, which is based on pro-forma financial statement projections and the cost of capital analysis. 13 | 14 | ![Summary](media/graphs_dcf_wacc.png) 15 | 16 | ### Features 17 | 18 | - coherent DCF-WACC equity valuation approach 19 | - automated web data extraction from YahooFinance and other public sources 20 | - cost of capital valuation: betas, required rate of return, WACC 21 | - structuring and writing valuation results into csv report 22 | 23 | Unsupervised execution of the valuation algorithm is primary objective of the program. Information is automatically extracted, processed and stored for further analysis. 24 | 25 | ### Algorithm 26 | 27 | - extract web data: financial reports, risk-free rate, equity beta 28 | - estimate cost of capital parameters: betas, cost of debt, equity, WACC 29 | - get discount factors from WACC 30 | - make CF projections in pro-forma financial statement 31 | - calculate FCF, Terminal value 32 | - compute Enterprise value, Equity value 33 | - estimate implied stock price from Equity value/shares outstanding 34 | - save valuation results to csv 35 | - plot results 36 | 37 | ### Structure 38 | 39 | - __evkit/__ - core modules of the program for web data extraction, analysis 40 | - __reports/__ - directory, where all valuation reports and data are written 41 | - __utils/__ - auxiliary bits and pieces to make everything work 42 | 43 | ## Clone, Compile, Run 44 | 45 | The program is written in Python and is self-contained, thus does not require compiling. However, if you wish to tinker with standalone components, you can install it as a module to your Python environment as PyPi package. 46 | 47 | ### Install 48 | 49 | Clone repository, navigate to the folder in Terminal. To get the most up-to-date version, clone master branch. Stable versions are labeled with tags. 50 | 51 | ```bash 52 | git clone https://github.com/lialkaas/evkit.git 53 | cd ~/evkit 54 | ``` 55 | 56 | _Optional_: install as PyPi package 57 | 58 | ```bash 59 | pip install . 60 | ``` 61 | 62 | ### Run 63 | 64 | Run the program 65 | 66 | ```bash 67 | python launcher.py 68 | ``` 69 | 70 | If you have Python 2 installed along with 3, or Python 3 is not initialized as default, run 71 | 72 | ```bash 73 | python3 launcher.py 74 | ``` 75 | 76 | Once you launch the program, it will ask you to select the industry (sector), which contains the pool of listed stocks. Select by pressing ID of listed options. I select 'large_cap' pool by entering '1'. 77 | 78 | ```plain 79 | ID | Industry 80 | 0 | mega_cap 81 | 1 | large_cap 82 | 2 | basic_materials 83 | 3 | healthcare 84 | 4 | utilities 85 | 5 | financial_services 86 | 6 | consumer_defensive 87 | 7 | consumer_cyclical 88 | 8 | technology 89 | 9 | energy 90 | 10 | real_estate 91 | 11 | communication_services 92 | 12 | industrials 93 | Enter Industry ID: 1 94 | ``` 95 | 96 | The script will start collecting and analyzing all data required for equity valuation. Once execution completed or you terminated the program by pressing 'Ctrl+C', all data is written to appropriate csv report. 97 | 98 | ```text 99 | 1/427 Processing ISG -> Missing trading information 100 | 2/427 Processing AZSEY 101 | 3/427 Processing NEE-PR 102 | 4/427 Processing MMM 103 | ... 104 | 24/427 Processing ASMLF 105 | 25/427 Processing SNPMF 106 | 26/427 Processing USB-PH^C 107 | -> Program interrupted by user 108 | -> Results saved to a file ./reports/large_cap-20190619.csv 109 | ``` 110 | 111 | Note, no data is lost due to premature termination of the process, ~~unless unknown critical error pops up~~. No matter how the program finishes, all collected data and valuation results will be saved to a file. The last line indicates the location of report, where you can access the data. 112 | 113 | Here is how the result looks in Tables. 114 | 115 | ![Sample csv table](media/report_csv.png) 116 | 117 | ## References 118 | 119 | 1. SEC Financial Statement Data Sets 120 | 121 | ## License and Copyright 122 | 123 | Copyright (c) 2020 Oleksii Lialka 124 | 125 | Licensed under the [MIT License](LICENSE.md). 126 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | ##### 2 | About 3 | ##### 4 | 5 | Hi! I am Oleksii Lialka, MBA, Data Scientist with specialty in financial technology, 6 | quantitative finance, predictive analytics and risk management. 7 | My primary career focus is setting up tailored, enterprise-grade solutions 8 | and R&D for data-driven decision making by leveraging cross-disciplinary analytics, 9 | domain expertise and cutting edge ML/AI technology. 10 | 11 | I realised, that there is no open-source solutions for Python to the frictionless 12 | financial statement analysis, not to mention tools for equity research. Back in a days, 13 | when I was a quantitative analyst, I used to do a lot of equity valuation, and I remember 14 | just how tedious and time consuming this process is. I (finally) decided to fill in 15 | this gap by starting the project Equity Valuation Kit or Evkit for short. It is supposed to fill 16 | the gaps in quants' and researchers' toolbox by providing cohesive environment and sound 17 | methodologies for equity research. 18 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | 17 | master_doc = 'index' 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'Equity Valuation Kit' 22 | copyright = '2020, Oleksii Lialka' 23 | author = 'Oleksii Lialka' 24 | 25 | 26 | # -- General configuration --------------------------------------------------- 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | ] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # List of patterns, relative to source directory, that match files and 38 | # directories to ignore when looking for source files. 39 | # This pattern also affects html_static_path and html_extra_path. 40 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 41 | 42 | 43 | # -- Options for HTML output ------------------------------------------------- 44 | 45 | # The theme to use for HTML and HTML Help pages. See the documentation for 46 | # a list of builtin themes. 47 | # 48 | html_theme = 'alabaster' 49 | 50 | # Add any paths that contain custom static files (such as style sheets) here, 51 | # relative to this directory. They are copied after the builtin static files, 52 | # so a file named "default.css" will overwrite the builtin "default.css". 53 | html_static_path = ['_static'] 54 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Equity Valuation Kit 2 | ================================================ 3 | 4 | Evkit is a library for financial statement analysis, equity valuation and research. 5 | It allows user to download, and parse financial statement of _all_ public companies 6 | listed on stock exchanges in the U.S. 7 | 8 | Evkit uses curated data sources and cohesive methodologies, 9 | which allows user to perform equity research, on par with professional quantitative researchers. 10 | 11 | 12 | Contents 13 | ======== 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | about 19 | -------------------------------------------------------------------------------- /evkit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olekssy/evkit/f17b60d7d1091e19ffa757720aa523e0776909cb/evkit/__init__.py -------------------------------------------------------------------------------- /evkit/capital.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019 Oleksii Lialka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | """ 23 | Algorithm 24 | 1/ get equity_beta, rf, mrp from sources 25 | 2/ get empirical rd from mean(interest / total debt) 26 | 3/ get debt_beta from the reverse CAPM of rd 27 | 4/ unlever equity beta by βu = (βL + βd*(1-tax)*D/E) / (1 + (1-tax)*D/E) 28 | 5/ get re from CAPM und beta of assets 29 | 6/ calculate WACC 30 | """ 31 | 32 | import numpy as np 33 | 34 | from evkit import utils 35 | 36 | 37 | class CostOfCapital: 38 | # unique ID of YahooFinance urlcurrent_assets_id 39 | summary_url_id = '?p=' 40 | statistics_url_id = '/key-statistics?p=' 41 | # positional index of values in html 42 | mkt_price_id = 1 43 | beta_id = 19 44 | shares_outstanding_id = 37 45 | 46 | def __init__(self, ticker, rf, mrp): 47 | self.ticker = ticker 48 | self.rf = rf # risk-free rate 49 | self.mrp = mrp # market risk premium 50 | self.mkt_price = None # previous close pps 51 | self.equity_beta = None # levered beta 52 | self.asset_beta = None # unlevered beta 53 | self.debt_beta = None 54 | self.rd = None # cost of debt 55 | self.re = None # cost of equity 56 | self.wacc = None 57 | self.num_shares = None # number of shares outstanding 58 | 59 | def get_shares_outstanding(self): 60 | statistics_html = utils.get_html(ticker=self.ticker, 61 | url_id=self.statistics_url_id, 62 | show_page=False) 63 | num_shares = utils.get_shares_num( 64 | html_page=statistics_html, value_index=self.shares_outstanding_id) 65 | return num_shares 66 | 67 | def capm(self, beta, rate=None): 68 | if rate is None: 69 | # find required rate of return from beta 70 | value = self.rf + beta * self.mrp 71 | else: 72 | # find beta from return 73 | value = (rate - self.rf) / self.mrp 74 | return value 75 | 76 | def get_stock_summary(self): 77 | # open summary page 78 | summary_html = utils.get_html(ticker=self.ticker, 79 | url_id=self.summary_url_id, 80 | YahooFinance=True, 81 | show_page=False) 82 | # get equity beta 83 | self.equity_beta = utils.get_value(html_page=summary_html, 84 | value_index=self.beta_id) 85 | # get previous close price 86 | self.mkt_price = utils.get_value(html_page=summary_html, 87 | value_index=self.mkt_price_id) 88 | return self.equity_beta, self.mkt_price 89 | 90 | def get_cost_of_debt(self, debt, interest): 91 | # cost of debt 92 | if 0 in debt: 93 | self.rd = 0 94 | else: 95 | interest = -interest # adjust negative value 96 | self.rd = np.mean(interest / debt) 97 | return self.rd 98 | 99 | def get_debt_beta(self): 100 | self.debt_beta = self.capm(beta=None, rate=self.rd) 101 | return self.debt_beta 102 | 103 | def get_asset_beta(self, debt, equity, tax_rate): 104 | # βu = (βL + βd * (1 - tax) * D / E) / (1 + (1 - tax) * D / E) 105 | debt = debt[3] 106 | equity = equity[3] 107 | de = debt / equity 108 | nominator = self.equity_beta + self.debt_beta * (1 - tax_rate) * de 109 | denominator = 1 + (1 - tax_rate) * de 110 | self.asset_beta = nominator / denominator 111 | return self.asset_beta 112 | 113 | def get_cost_of_equity(self): 114 | self.re = self.capm(beta=self.asset_beta, rate=None) 115 | return self.re 116 | 117 | def get_wacc(self, debt, assets, tax_rate): 118 | debt = debt[3] 119 | assets = assets[3] 120 | dv = debt / assets 121 | self.wacc = self.re * (1 - dv) + (1 - tax_rate) * self.rd * dv 122 | return self.wacc 123 | 124 | 125 | def discount_factors(wacc, periods=3): 126 | periods += 1 # adjust for terminal value 127 | dt = [1 / (1 + wacc)**t for t in range(1, periods + 1)] 128 | return np.array(dt) 129 | 130 | 131 | def main(): 132 | pass 133 | 134 | 135 | if __name__ == '__main__': 136 | main() 137 | -------------------------------------------------------------------------------- /evkit/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "verbose": true 4 | }, 5 | "path": { 6 | "data": "./data", 7 | "media": "./media", 8 | "models": "./models", 9 | "notebooks": "./notebooks", 10 | "reports": "./reports" 11 | }, 12 | "scrapper": { 13 | "formType": "10-K", 14 | "numberDownload": 10 15 | } 16 | } -------------------------------------------------------------------------------- /evkit/financials.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019 Oleksii Lialka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | """ 23 | Algorithm 24 | 1/ extract web data, clean, write actual financials to object attributes 25 | 2/ get ST growth rate, effective tax rate 26 | 3/ get revenue ratios for CF elements 27 | 4/ forecast CF: revenue, EBIT, debt from constant D/E 28 | 5/ get NWC projections 29 | 6/ get terminal value 30 | 7/ DCF-WACC 31 | """ 32 | 33 | import numpy as np 34 | 35 | from evkit import utils 36 | 37 | 38 | class IncomeStatement: 39 | # unique ID of YahooFinance url 40 | fin_statement_url_id = '/financials?p=' # Income statement 41 | # positional index of values in html 42 | revenue_id = 6 43 | ebit_id = 47 44 | interest_id = 63 45 | tax_id = 73 46 | 47 | def __init__(self, ticker): 48 | self.ticker = ticker 49 | self.html = None 50 | self.revenue = np.zeros(4) 51 | self.ebit = np.zeros(4) 52 | self.interest = np.zeros(4) 53 | self.tax = np.zeros(4) 54 | 55 | @staticmethod 56 | def get_actual(html, element_id): 57 | """ 58 | Write clean data from html to object attribute 59 | """ 60 | offset = 4 # nubmer of entries 61 | element_list = np.zeros(offset) 62 | for i_ in range(element_id, element_id + offset): 63 | value = utils.get_value(html, i_) 64 | # break iteration if None value encountered 65 | if value is None: 66 | element_list = np.zeros(4) 67 | break 68 | element_list[i_ - element_id] = value 69 | element_list = np.flip(element_list) 70 | return element_list 71 | 72 | @staticmethod 73 | def get_st_growth(element_list): 74 | """ 75 | Calculate short-term growth rate 76 | """ 77 | if np.any(element_list): 78 | n = len(element_list) - 1 79 | change_list = [ 80 | element_list[i + 1] / element_list[i] - 1 for i in range(n) 81 | ] 82 | st_growth = sum(change_list) / n 83 | else: 84 | st_growth = 0 85 | return st_growth 86 | 87 | @staticmethod 88 | def get_projections(elements_list, periods, growth_rate): 89 | # get projections 90 | for t in range(periods): 91 | last_element = elements_list[-1] 92 | element_t = last_element * (1 + growth_rate) 93 | elements_list = np.append(elements_list, element_t) 94 | return elements_list 95 | 96 | def get_html_data(self): 97 | self.html = utils.get_html(ticker=self.ticker, 98 | url_id=self.fin_statement_url_id, 99 | YahooFinance=True, 100 | show_page=False) 101 | 102 | def get_tax_rate(self): 103 | actual_tax = self.tax[:4] 104 | actual_ebit = self.ebit[:4] 105 | # check data integrity 106 | if np.any(actual_tax) and np.any(actual_ebit): 107 | t_rates = self.tax[:4] / self.ebit[:4] 108 | tax_rate = np.mean(t_rates) 109 | else: 110 | tax_rate = 0.21 111 | return tax_rate 112 | 113 | def get_revenue_ratio(self, elements_list): 114 | # element/revenue from actual results 115 | actual_element = elements_list[:4] 116 | actual_revenue = self.revenue[:4] 117 | if np.any(actual_element) and np.any(actual_revenue): 118 | ratios_np = actual_element / actual_revenue 119 | ratio = np.mean(ratios_np) 120 | else: 121 | ratio = 0 122 | return ratio 123 | 124 | def actual_statement(self): 125 | """ 126 | Write actual data to pro-forma statement 127 | :return: 128 | """ 129 | # revenue 130 | self.revenue = self.get_actual(html=self.html, 131 | element_id=self.revenue_id) 132 | # EBIT 133 | self.ebit = self.get_actual(html=self.html, element_id=self.ebit_id) 134 | # interest expense 135 | # NOTE: negative values 136 | self.interest = self.get_actual(html=self.html, 137 | element_id=self.interest_id) 138 | # tax 139 | self.tax = self.get_actual(html=self.html, element_id=self.tax_id) 140 | 141 | def forecast_statement(self, st_growth, periods=3): 142 | # forecast elements 143 | # ebit 144 | self.ebit = self.get_projections( 145 | elements_list=self.ebit, 146 | periods=periods, 147 | growth_rate=st_growth, 148 | ) 149 | 150 | 151 | class BalanceSheet(IncomeStatement): 152 | # unique ID of YahooFinance url 153 | fin_statement_url_id = '/balance-sheet?p=' 154 | # positional index of values in html 155 | cash_id = 7 156 | current_assets_id = 32 157 | total_assets_id = 72 158 | current_liabilities_id = 93 159 | st_debt_id = 83 160 | lt_debt_id = 98 161 | equity_id = 169 162 | 163 | def __init__(self, ticker): 164 | super().__init__(ticker) 165 | self.cash = np.zeros(4) 166 | self.current_assets = np.zeros(4) 167 | self.total_assets = np.zeros(4) 168 | self.current_liabilities = np.zeros(4) 169 | self.st_debt = np.zeros(4) 170 | self.lt_debt = np.zeros(4) 171 | self.total_debt = np.zeros(4) 172 | self.equity = np.zeros(4) 173 | self.nwc = np.zeros(4) 174 | 175 | def actual_statement(self): 176 | """ 177 | Write actual data to pro-forma statement 178 | :return: 179 | """ 180 | # cash and equivalents 181 | self.cash = self.get_actual(html=self.html, element_id=self.cash_id) 182 | # current assets 183 | self.current_assets = self.get_actual( 184 | html=self.html, element_id=self.current_assets_id) 185 | # total assets 186 | self.total_assets = self.get_actual(html=self.html, 187 | element_id=self.total_assets_id) 188 | # current liabilities 189 | self.current_liabilities = self.get_actual( 190 | html=self.html, element_id=self.current_liabilities_id) 191 | # short-term debt 192 | self.st_debt = self.get_actual(html=self.html, 193 | element_id=self.st_debt_id) 194 | # long-term debt 195 | self.lt_debt = self.get_actual(html=self.html, 196 | element_id=self.lt_debt_id) 197 | # Total Stockholder Equity 198 | self.equity = self.get_actual(html=self.html, 199 | element_id=self.equity_id) 200 | # total debt 201 | self.total_debt = self.st_debt + self.lt_debt 202 | 203 | def get_nwc(self): 204 | # net working capital 205 | wc = self.current_assets - self.current_liabilities 206 | records = len(wc) 207 | nwc = [0] + [wc[i + 1] - wc[i] for i in range(records - 1)] 208 | self.nwc = np.array(nwc) 209 | return self.nwc 210 | 211 | def forecast_statement(self, st_growth, periods=3): 212 | # forecast elements 213 | self.current_assets = self.get_projections( 214 | elements_list=self.current_assets, 215 | periods=periods, 216 | growth_rate=st_growth, 217 | ) 218 | self.current_liabilities = self.get_projections( 219 | elements_list=self.current_liabilities, 220 | periods=periods, 221 | growth_rate=st_growth, 222 | ) 223 | self.get_nwc() 224 | 225 | 226 | class CashFlowStatement(IncomeStatement): 227 | # unique ID of YahooFinance url 228 | fin_statement_url_id = '/cash-flow?p=' 229 | # positional index of values in html 230 | depreciation_id = 12 231 | capex_id = 48 232 | 233 | def __init__(self, ticker): 234 | super().__init__(ticker) 235 | self.depreciation = np.zeros(4) 236 | self.capex = np.zeros(4) 237 | 238 | def actual_statement(self): 239 | """ 240 | Write actual data to pro-forma statement 241 | :return: 242 | """ 243 | # depreciation 244 | self.depreciation = self.get_actual(html=self.html, 245 | element_id=self.depreciation_id) 246 | # capex 247 | # NOTE: negative values 248 | self.capex = self.get_actual(html=self.html, element_id=self.capex_id) 249 | 250 | def forecast_statement(self, st_growth, periods=3): 251 | # forecast elements 252 | # dna_growth = self.get_st_growth(self.depreciation) 253 | self.depreciation = self.get_projections( 254 | elements_list=self.depreciation, 255 | periods=periods, 256 | growth_rate=st_growth, 257 | ) 258 | # capex_growth = self.get_st_growth(self.capex) 259 | self.capex = self.get_projections( 260 | elements_list=self.capex, 261 | periods=periods, 262 | growth_rate=st_growth, 263 | ) 264 | 265 | 266 | def dcf(ebit, dna, nwc, capex, tax_rate, lt_growth, wacc, dt): 267 | # FCF = EBIT(1 - tax) + DnA - NWC - CAPEX 268 | capex = -capex # adjust negative value 269 | fcf = ebit * (1 - tax_rate) + dna - nwc - capex 270 | # terminal value 271 | tv = fcf[-1] * (1 + lt_growth) / (wacc - lt_growth) 272 | fcf_tv = np.append(fcf, tv)[4:] 273 | # discount forecasted fcf 274 | ev = np.dot(fcf_tv, dt) 275 | return fcf_tv, ev 276 | 277 | 278 | def main(): 279 | pass 280 | 281 | 282 | if __name__ == '__main__': 283 | main() 284 | -------------------------------------------------------------------------------- /evkit/launcher.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019 Oleksii Lialka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import warnings 24 | from datetime import datetime 25 | 26 | import pandas as pd 27 | 28 | from evkit import capital, financials, utils 29 | 30 | warnings.filterwarnings('ignore') 31 | 32 | # Assumptions 33 | forecast_horizon = 5 34 | 35 | # get tickers pool url 36 | tickers_key, tickers_urls = utils.get_tickers_url() 37 | # extract list of stocks, write to file 38 | tickers_df = utils.get_tickers(tickers_urls) 39 | # extract risk-free rate, market return, market risk premium 40 | rf, mrp = utils.get_rf_mrp() 41 | # select data to be written to report 42 | results_dict = { 43 | 'beta_equity': [], 44 | 'beta_asset': [], 45 | 'beta_debt': [], 46 | 're': [], 47 | 'rd': [], 48 | 'wacc': [], 49 | 'enterprise_value, $B': [], 50 | 'equity_value, $B': [], 51 | 'stock_price': [], 52 | 'mkt_stock_price': [] 53 | } 54 | # select tickers sample 55 | ticker_collection = tickers_df.ticker[:] 56 | sample_size = len(ticker_collection) 57 | try: 58 | for num, ticker in enumerate(ticker_collection): 59 | print(f'\n{num + 1}/{sample_size} Processing {ticker}', end=' ') 60 | # create instances of pro-forma financial statements and Cost of Capital 61 | fin_is = financials.IncomeStatement(ticker) 62 | fin_bs = financials.BalanceSheet(ticker) 63 | fin_cf = financials.CashFlowStatement(ticker) 64 | cap = capital.CostOfCapital(ticker=ticker, rf=rf, mrp=mrp) 65 | # Web data extraction 66 | # extract beta of equity (levered beta) 67 | beta_eq, mkt_price = cap.get_stock_summary() 68 | # extract shares outstanding 69 | num_shares = cap.get_shares_outstanding() 70 | # extract previous close 71 | # [TBD] 72 | 73 | # web data integrity check 74 | collection = [beta_eq, mkt_price, num_shares] 75 | if None in collection: 76 | print('-> Missing trading information', end='') 77 | for key in results_dict.keys(): 78 | results_dict[key].append(None) 79 | continue 80 | # extract financial reports 81 | fin_is.get_html_data() 82 | fin_bs.get_html_data() 83 | fin_cf.get_html_data() 84 | # populate fin statements with actual data 85 | fin_is.actual_statement() 86 | fin_bs.actual_statement() 87 | fin_cf.actual_statement() 88 | # get effective tax rate 89 | tax_rate = fin_is.get_tax_rate() 90 | 91 | # Cost of Capital 92 | # get cost of debt 93 | rd = cap.get_cost_of_debt(debt=fin_bs.total_debt, 94 | interest=fin_is.interest) 95 | # get debt beta 96 | beta_debt = cap.get_debt_beta() 97 | # get asset beta 98 | beta_asset = cap.get_asset_beta(debt=fin_bs.lt_debt, 99 | equity=fin_bs.equity, 100 | tax_rate=tax_rate) 101 | # get cost of equity 102 | re = cap.get_cost_of_equity() 103 | # get WACC 104 | wacc = cap.get_wacc(debt=fin_bs.lt_debt, 105 | assets=fin_bs.total_assets, 106 | tax_rate=tax_rate) 107 | # get discount factors 108 | dt = capital.discount_factors(wacc=wacc, periods=forecast_horizon) 109 | 110 | # DCF 111 | # implement industry-specific estimates for LT growth rate 112 | lt_growth = wacc * 0.5 113 | # get ST growth rate from revenue 114 | st_growth = fin_is.get_st_growth(fin_is.revenue) 115 | # make financial projections 116 | fin_is.forecast_statement(st_growth=st_growth, 117 | periods=forecast_horizon) 118 | fin_bs.forecast_statement(st_growth=st_growth, 119 | periods=forecast_horizon) 120 | fin_cf.forecast_statement(st_growth=st_growth, 121 | periods=forecast_horizon) 122 | # Enterprise value 123 | fcf, ev = financials.dcf(ebit=fin_is.ebit, 124 | dna=fin_cf.depreciation, 125 | nwc=fin_bs.nwc, 126 | capex=fin_cf.capex, 127 | tax_rate=tax_rate, 128 | lt_growth=lt_growth, 129 | wacc=wacc, 130 | dt=dt) 131 | # Equity value 132 | eq = ev - fin_bs.total_debt[3] + fin_bs.cash[3] 133 | # Share price 134 | stock_price = max(0, eq / num_shares) 135 | 136 | # store results into dictionary 137 | results_dict['beta_equity'].append(beta_eq) 138 | results_dict['beta_asset'].append(beta_asset) 139 | results_dict['beta_debt'].append(beta_debt) 140 | results_dict['re'].append(re) 141 | results_dict['rd'].append(rd) 142 | results_dict['wacc'].append(wacc) 143 | results_dict['enterprise_value, $B'].append(ev / 1_000_000) 144 | results_dict['equity_value, $B'].append(eq / 1_000_000) 145 | results_dict['stock_price'].append(stock_price) 146 | results_dict['mkt_stock_price'].append(mkt_price) 147 | 148 | except KeyboardInterrupt: 149 | print('\n-> Program interrupted by user', end='') 150 | quit() 151 | finally: 152 | # write results to report 153 | results_df = pd.DataFrame(results_dict) 154 | report_df = pd.concat([tickers_df, results_df], axis=1) 155 | # get date as id for report 156 | date = datetime.today().strftime('-%Y%m%d') 157 | report_id = date.join([tickers_key, '']) 158 | utils.results_to_csv(data_df=report_df, report_id=report_id) 159 | # plot results 160 | beta_asset = report_df.beta_asset 161 | wacc = report_df.wacc 162 | dcf = report_df.stock_price 163 | mkt_quote = report_df.mkt_stock_price 164 | utils.plot_beta_wacc(beta=beta_asset, wacc=wacc) 165 | utils.plot_dcf_mkt_price(dcf_price=dcf, mkt_price=mkt_quote) 166 | -------------------------------------------------------------------------------- /evkit/plot.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | def plot_beta_wacc(beta, wacc): 6 | plt.style.use('ggplot') 7 | plt.figure(figsize=(10, 5)) 8 | n = len(wacc) 9 | plt.scatter(x=beta_asset, 10 | y=wacc, 11 | edgecolor='black', 12 | linewidth=1, 13 | alpha=0.75) 14 | plt.title('Asset beta - WACC \nN = ' + str(n)) 15 | plt.xlabel('Asset beta') 16 | plt.ylabel('WACC') 17 | plt.tight_layout() 18 | plt.show() 19 | 20 | 21 | def plot_dcf_mkt_price(dcf_price, mkt_price): 22 | plt.style.use('seaborn') 23 | plt.figure(figsize=(10, 5)) 24 | n = len(dcf_price) 25 | # plt.plot( 26 | # dcf_price, 27 | # linewidth=1, 28 | # ) 29 | # plt.plot( 30 | # mkt_price, 31 | # linewidth=1, 32 | # alpha=0.75 33 | # ) 34 | plt.scatter(x=mkt_price, y=dcf_price / mkt_price, linewidth=1, alpha=0.75) 35 | plt.xscale('log') 36 | # plt.yscale('log') 37 | plt.title('DCF-WACC stock price to market quote \nN = ' + str(n)) 38 | plt.xlabel('DCF-WACC / Market quote') 39 | plt.ylabel('Market quote') 40 | plt.tight_layout() 41 | plt.show() 42 | 43 | 44 | file_name = 'healthcare-20190616.csv' 45 | path_csv = '../reports/' + file_name 46 | 47 | data = pd.read_csv(path_csv) 48 | beta_asset = data['beta_asset'] 49 | wacc = data['wacc'] 50 | mkt_price = data.mkt_stock_price 51 | dcf_price = data.stock_price 52 | ratio = dcf_price / mkt_price 53 | 54 | plot_beta_wacc(beta=beta_asset, wacc=wacc) 55 | plot_dcf_mkt_price(dcf_price, mkt_price) 56 | -------------------------------------------------------------------------------- /evkit/scrapper.py: -------------------------------------------------------------------------------- 1 | """ Script for downloading company filings from the SEC EDGAR database. """ 2 | 3 | import json 4 | import pandas as pd 5 | import sec_edgar_downloader 6 | 7 | # Load configs 8 | with open("evkit/config.json", "r") as configFile: 9 | config = json.load(configFile) 10 | 11 | # -------------------- Assumptions -------------------- 12 | ticker = "AAPL" 13 | verbose = config["global"]["verbose"] 14 | separatorFS = " " 15 | 16 | 17 | # -------------------- Scrapper Settings -------------------- 18 | def scrap_sec_fillings(ticker, verbose=verbose): 19 | """ Retrieve SEC fillings for a ticker """ 20 | if verbose: 21 | print( 22 | f'Started scrapping {config["scrapper"]["formType"]} fillings for {ticker}' 23 | ) 24 | # initialize scrapper 25 | scrapper = sec_edgar_downloader.Downloader( 26 | download_folder=config["path"]["data"]) 27 | # collect fillings 28 | scrapper.get(filing_type=config["scrapper"]["formType"], 29 | ticker_or_cik=ticker, 30 | num_filings_to_download=config["scrapper"]["numberDownload"]) 31 | 32 | 33 | def main(): 34 | # scrap_sec_fillings(ticker) 35 | pathFS = "data/2020q2/num.txt" 36 | df = pd.read_csv(pathFS, sep=separatorFS) 37 | print(df.head()) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /evkit/sec_datareader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for downloading company filings from 3 | SEC Financial Statement Data Sets. 4 | """ 5 | 6 | import os 7 | import shutil 8 | import zipfile 9 | 10 | import pandas as pd 11 | import requests 12 | 13 | 14 | # -------------------- Helper Functions -------------------- 15 | def sec_url(period): 16 | """ Create url link to SEC Financial Statement Data Set """ 17 | url = "".join([ 18 | "https://www.sec.gov/files/dera/data/financial-statement-data-sets/", 19 | period, ".zip" 20 | ]) 21 | 22 | # handle weird path exception of SEC 23 | if period == "2020q1": 24 | url = "".join([ 25 | "https://www.sec.gov/files/node/add/data_distribution/", period, 26 | ".zip" 27 | ]) 28 | return url 29 | 30 | 31 | # -------------------- Global Variables -------------------- 32 | CONFIG = { 33 | "verbose": True, 34 | "path": { 35 | "data": "data/", 36 | "archives": "data/archives/", 37 | "temp": "data/temp/", 38 | "index": "data/sec_index.csv", 39 | "finData": "data/sec_findata.csv", 40 | }, 41 | "dataSource": { 42 | # sec_url("2020q2"), 43 | # sec_url("2020q1"), 44 | sec_url("2019q4"), 45 | sec_url("2019q3"), 46 | sec_url("2019q2"), 47 | sec_url("2019q1"), 48 | }, 49 | "convention": { 50 | "separator": "\t", 51 | "indexColumn": "adsh", 52 | "fileIndex": "/sub.txt", 53 | "fileData": "/num.txt" 54 | } 55 | } 56 | 57 | 58 | # -------------------- Downloader Settings -------------------- 59 | class FinancialDataSEC: 60 | """ 61 | Class for collecting Financial Statement Data Sets from SEC. 62 | """ 63 | 64 | def __init__(self): 65 | self.verbose = CONFIG["verbose"] 66 | self.source = CONFIG["dataSource"] 67 | self.data = CONFIG["path"]["data"] 68 | self.archives = CONFIG["path"]["archives"] 69 | self.temp = CONFIG["path"]["temp"] 70 | 71 | self.indexFile = CONFIG["path"]["index"] 72 | self.finDataFile = CONFIG["path"]["finData"] 73 | 74 | self.index = None 75 | self.finData = None 76 | 77 | # mkdir data/ if not exists 78 | if not os.path.exists(self.data): 79 | os.mkdir(self.data) 80 | 81 | def download(self): 82 | """ 83 | Download Financial Statement Data Sets archives from SEC. 84 | """ 85 | if self.verbose: 86 | print("Start downloading from SEC.") 87 | 88 | for url in self.source: 89 | # mkdir archives/ if not exists 90 | if not os.path.exists(self.archives): 91 | os.mkdir(self.archives) 92 | 93 | # download file from SEC database 94 | fileName = url.split("/")[-1] 95 | filePath = "".join([self.archives, fileName]) 96 | archive = requests.get(url) 97 | 98 | # save file 99 | open(filePath, 'wb').write(archive.content) 100 | 101 | if self.verbose: 102 | print(f"Downloaded {filePath}") 103 | 104 | if self.verbose: 105 | print("Complete all downloads.\n") 106 | 107 | def extract(self): 108 | """ 109 | Extract data from SEC archives. 110 | """ 111 | if self.verbose: 112 | print("Start extracting SEC archives.") 113 | 114 | if not os.path.exists(self.temp): 115 | os.mkdir(self.temp) 116 | 117 | for fileName in os.listdir(self.archives): 118 | # create directory for extracted files 119 | dirName = fileName.split(".")[0] 120 | dirPath = "".join([self.temp, dirName]) 121 | if not os.path.exists(dirPath): 122 | os.mkdir(dirPath) 123 | 124 | # extract files from archive 125 | archPath = "".join([self.archives, fileName]) 126 | with zipfile.ZipFile(archPath, "r") as archive: 127 | archive.extractall(path=dirPath, members=None) 128 | 129 | if self.verbose: 130 | print(f"Extracted {dirName}") 131 | 132 | if self.verbose: 133 | print("Complete extracting all archives.\n") 134 | 135 | def parse_index(self, to_csv=True): 136 | """ 137 | Parse company name and report index to csv. 138 | """ 139 | if self.verbose: 140 | print("Start parsing SEC index data.") 141 | 142 | for dirName in os.listdir(self.temp): 143 | # parse company names and report IDs 144 | filePath = "".join( 145 | [self.temp, dirName, CONFIG["convention"]["fileIndex"]]) 146 | df = pd.read_csv(filePath, 147 | sep=CONFIG["convention"]["separator"], 148 | index_col=CONFIG["convention"]["indexColumn"], 149 | low_memory=False) 150 | self.index = pd.concat([self.index, df]) 151 | 152 | if self.verbose: 153 | print(f"Parsed {dirName}") 154 | 155 | # save data to csv 156 | if to_csv: 157 | self.index.to_csv(self.indexFile) 158 | print(f"Parsed index saved to {self.indexFile}") 159 | 160 | if self.verbose: 161 | print("Complete parsing SEC index data.\n") 162 | 163 | return self.index 164 | 165 | def parse_findata(self, to_csv=True): 166 | """ 167 | Parse financial data to csv. 168 | """ 169 | if self.verbose: 170 | print("Start parsing SEC financial data.") 171 | 172 | for dirName in os.listdir(self.temp): 173 | # parse financial statements 174 | filePath = "".join( 175 | [self.temp, dirName, CONFIG["convention"]["fileData"]]) 176 | df = pd.read_csv(filePath, 177 | sep=CONFIG["convention"]["separator"], 178 | index_col=CONFIG["convention"]["indexColumn"], 179 | low_memory=False) 180 | self.finData = pd.concat([self.finData, df]) 181 | 182 | if self.verbose: 183 | print(f"Parsed {dirName}") 184 | 185 | # save data to csv 186 | if to_csv: 187 | self.finData.to_csv(self.finDataFile) 188 | print(f"Parsed data saved to {self.finDataFile}") 189 | 190 | if self.verbose: 191 | print("Complete parsing SEC financial data.\n") 192 | 193 | return self.finData 194 | 195 | def cleanup(self): 196 | """ 197 | Remove archives and extracted artifacts. 198 | """ 199 | if self.verbose: 200 | print("Start cleanup.") 201 | 202 | # remove archives 203 | if os.path.exists(self.archives): 204 | shutil.rmtree(self.archives) 205 | 206 | # remove extracted artifacts 207 | if os.path.exists(self.temp): 208 | shutil.rmtree(self.temp) 209 | 210 | if self.verbose: 211 | print(f"Removed {self.archives}") 212 | print(f"Removed {self.temp}") 213 | print("Complete cleanup.\n") 214 | 215 | 216 | def main(): 217 | # initialize DataSEC instance 218 | findata = FinancialDataSEC() 219 | 220 | # Download and extract data 221 | findata.download() 222 | findata.extract() 223 | 224 | # Parse collected data 225 | findata.parse_index() 226 | findata.parse_findata() 227 | 228 | # Cleanup redundant files 229 | findata.cleanup() 230 | 231 | 232 | if __name__ == "__main__": 233 | main() 234 | -------------------------------------------------------------------------------- /evkit/urldata.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019 Oleksii Lialka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import requests 24 | from bs4 import BeautifulSoup 25 | 26 | 27 | def get_html(ticker, url_symbol, YahooFinance=True, verbose=False): 28 | if YahooFinance: 29 | url = ticker.join(['https://finance.yahoo.com/quote/', url_symbol, '']) 30 | else: 31 | url = url_symbol 32 | source = requests.get(url).text 33 | html_page = BeautifulSoup(source, 'html5lib') 34 | data = html_page.findAll('td') 35 | 36 | if verbose: 37 | title = html_page.title.text 38 | print(f'\n==Raw data of {title}==') 39 | for num, line in enumerate(data): 40 | print(num, line.text) 41 | return data 42 | 43 | 44 | def get_value(data, value_index, verbose=False): 45 | value_text = data[value_index].text 46 | value_str = value_text.replace(',', '') 47 | value_float = float(value_str) 48 | 49 | if verbose: 50 | print('\n==Verbose mode of data.get_value==') 51 | print(f'value_text = {value_text} {type(value_text)}\n' 52 | f'value_str = {value_str} {type(value_str)}\n' 53 | f'value_float = {value_float} {type(value_float)}\n') 54 | return value_float 55 | 56 | 57 | def main(): 58 | stock = 'GOOG' 59 | verbose = True 60 | 61 | # unique ID of YahooFinance urlcurrent_assets_id 62 | summary_url_id = '?p=' 63 | income_statement_url_id = '/financials?p=' 64 | balance_sheet_url_id = '/balance-sheet?p=' 65 | cf_statement_url_id = '/cash-flow?p=' 66 | 67 | # other sources 68 | index_return_url = 'http://news.morningstar.com/index/indexReturn.html' 69 | 70 | # test html getter 71 | summary_html = get_html(stock, summary_url_id, verbose=verbose) 72 | income_statement_html = get_html(stock, 73 | income_statement_url_id, 74 | verbose=verbose) 75 | balance_sheet_html = get_html(stock, balance_sheet_url_id, verbose=verbose) 76 | cf_statement_html = get_html(stock, cf_statement_url_id, verbose=verbose) 77 | 78 | # test value getter 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /evkit/utils.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019 Oleksii Lialka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import numpy as np 24 | import pandas as pd 25 | import requests 26 | from bs4 import BeautifulSoup 27 | import matplotlib.pyplot as plt 28 | 29 | 30 | def get_html(ticker, url_id, YahooFinance=True, show_page=False): 31 | if YahooFinance: 32 | url = ticker.join(['https://finance.yahoo.com/quote/', url_id, '']) 33 | else: 34 | # treat url_symbol as url link 35 | url = url_id 36 | source = requests.get(url).text 37 | html_page = BeautifulSoup(source, 'html5lib') 38 | data = html_page.findAll('td') 39 | # display structured html page 40 | if show_page: 41 | title = html_page.title.text 42 | print(f'\n==Raw data of {title}==') 43 | for num, line in enumerate(data): 44 | print(num, line.text) 45 | return data 46 | 47 | 48 | def get_value(html_page, value_index): 49 | try: 50 | value_text = html_page[value_index].text 51 | value_str = value_text.replace(',', '') 52 | if value_str == 'N/A': 53 | value_float = None 54 | elif value_str == '-': 55 | value_float = 0 56 | elif not value_str.replace('.', '').replace('-', '').isdigit(): 57 | # some tickers have broken index, 58 | # long text value passes all checks and blows the float value up 59 | value_float = None 60 | else: 61 | value_float = float(value_str) 62 | except IndexError: 63 | value_float = None 64 | return value_float 65 | 66 | 67 | def get_tickers_url(): 68 | tickers_dict = { 69 | 'mega_cap': 'fa45388d-9752-4201-974f-93c0fffdaf2e', 70 | 'large_cap': 'fd702086-e634-4b39-81e8-3326f43373f6', 71 | 'basic_materials': 'b7a89332-49c1-4c36-aa21-0e5dea8c0a4e', 72 | 'healthcare': 'b5bd835a-1f78-4dd3-a22d-14a97f62e2c4', 73 | 'utilities': 'cea6e52c-0be5-4600-a302-c737e9b7f274', 74 | 'financial_services': 'f48ad0e4-430a-41b5-b42d-9b84ded67143', 75 | 'consumer_defensive': '27c9d45d-6ae9-4404-9c29-977dc249b8fe', 76 | 'consumer_cyclical': '2e4f67d2-c4f2-4e6e-9a88-99084a489809h', 77 | 'technology': '8f642d7d-11b7-435a-aa30-b1570a7d9d26', 78 | 'energy': '238a84a7-d96d-4b87-a1b9-090191c412e3', 79 | 'real_estate': '33d68532-22eb-4ca5-aaab-a2c4e45e6337', 80 | 'communication_services': '17bcd658-1187-431d-8dbb-38d4967e1981', 81 | 'industrials': '64550c05-f55d-4620-9f5d-95143b31e9ed' 82 | } 83 | # display available stock pools 84 | print('ID | Industry') 85 | for num, key in enumerate(tickers_dict.keys()): 86 | print(f'{num:>2d} | {key}') 87 | select = input('Enter Industry ID: ') 88 | # render selection (int) into dict key 89 | tickers_key = list(tickers_dict.keys())[int(select)] 90 | print(f'Selected collection of securities: {tickers_key}') 91 | # collect urls with page offset 92 | url_offset = 0 93 | tickers_urls = [] 94 | tickers_url_id = [ 95 | 'https://finance.yahoo.com/screener/unsaved/', '?count=250&offset=' 96 | ] 97 | for i in range(6): 98 | url = tickers_dict[tickers_key].join(tickers_url_id) + str(url_offset) 99 | tickers_urls.append(url) 100 | url_offset += 250 101 | # print(url) 102 | return tickers_key, tickers_urls 103 | 104 | 105 | def get_rf_mrp(): 106 | # vars 107 | summary_url_id = '?p=' 108 | rf_ticker = '^TNX' # Treasury Yield 10 Years 109 | market_url = 'http://news.morningstar.com/index/indexReturn.html' 110 | rf_id = 1 111 | market_id = 43 # 1Y US market return value id 112 | 113 | # get risk-free rate 114 | rf_html = get_html(ticker=rf_ticker, 115 | url_id=summary_url_id, 116 | YahooFinance=True, 117 | show_page=False) 118 | rf = get_value(rf_html, rf_id) / 100 119 | # get market return 120 | market_html = get_html(ticker=None, 121 | url_id=market_url, 122 | YahooFinance=False, 123 | show_page=False) 124 | mkt_return = get_value(market_html, market_id) / 100 125 | # market risk pemium 126 | mrp = mkt_return - rf 127 | return rf, mrp 128 | 129 | 130 | def get_tickers(url_list): 131 | collection = {'ticker': [], 'name': []} 132 | # open urls from the offset collection 133 | for url in url_list: 134 | html_page = get_html(ticker=None, 135 | url_id=url, 136 | YahooFinance=False, 137 | show_page=False) 138 | data_len = len(html_page) 139 | value_indices = np.arange(start=0, stop=data_len, step=10) 140 | # extract data, write to dictionary collection 141 | for i in value_indices: 142 | ticker = html_page[i].text 143 | company_name = html_page[i + 1].text 144 | # avoid duplicates 145 | if company_name in collection['name']: 146 | continue 147 | else: 148 | collection['ticker'].append(ticker) 149 | collection['name'].append(company_name) 150 | # render dictionary into DataFrame 151 | stocks_df = pd.DataFrame(collection) 152 | return stocks_df 153 | 154 | 155 | def plot_beta_wacc(beta, wacc): 156 | plt.style.use('ggplot') 157 | plt.figure(figsize=(10, 5)) 158 | n = len(wacc) 159 | plt.scatter(x=beta, y=wacc, edgecolor='black', linewidth=1, alpha=0.75) 160 | plt.title('Asset beta - WACC \nN = ' + str(n)) 161 | plt.xlabel('Asset beta') 162 | plt.ylabel('WACC') 163 | plt.tight_layout() 164 | plt.show() 165 | 166 | 167 | def plot_dcf_mkt_price(dcf_price, mkt_price): 168 | plt.style.use('seaborn') 169 | plt.figure(figsize=(10, 5)) 170 | n = len(dcf_price) 171 | # plt.plot( 172 | # dcf_price, 173 | # linewidth=1, 174 | # ) 175 | # plt.plot( 176 | # mkt_price, 177 | # linewidth=1, 178 | # alpha=0.75 179 | # ) 180 | plt.scatter(x=mkt_price, y=dcf_price / mkt_price, linewidth=1, alpha=0.75) 181 | plt.xscale('log') 182 | # plt.yscale('log') 183 | plt.title('DCF-WACC stock price to market quote \nN = ' + str(n)) 184 | plt.xlabel('DCF-WACC / Market quote') 185 | plt.ylabel('Market quote') 186 | plt.tight_layout() 187 | plt.show() 188 | 189 | 190 | def get_shares_num(html_page, value_index): 191 | try: 192 | value_text = html_page[value_index].text 193 | value_str = value_text.replace(',', '') 194 | except IndexError: 195 | value_text = 'IndexError' 196 | value_str = 'N/A' 197 | if value_str == 'N/A': 198 | value_float = None 199 | elif value_str[-1] == 'B': 200 | value_float = float(value_str[:-1]) * 1_000_000 201 | elif value_str[-1] == 'M': 202 | value_float = float(value_str[:-1]) * 1_000 203 | elif value_str[-1] == 'k': 204 | value_float = float(value_str[:-1]) 205 | else: 206 | raise Exception(f'Error: unknown scale of shares outstanding. ' 207 | f'raw value = {value_text}') 208 | return value_float 209 | 210 | 211 | def results_to_csv(data_df, report_id): 212 | # save results to csv 213 | path = report_id.join(['./reports/', '.csv']) 214 | data_df.to_csv(path_or_buf=path, index=False) 215 | print(f'\n-> Results saved to a file {path}') 216 | return None 217 | 218 | 219 | def main(): 220 | ticker = 'AMZN' 221 | display_results = True 222 | 223 | # unique ID of YahooFinance urlcurrent_assets_id 224 | summary_url_id = '?p=' 225 | income_statement_url_id = '/financials?p=' 226 | balance_sheet_url_id = '/balance-sheet?p=' 227 | cf_statement_url_id = '/cash-flow?p=' 228 | statistics_url_id = '/key-statistics?p=' 229 | 230 | # test html getter 231 | get_html(ticker=ticker, url_id=summary_url_id, show_page=display_results) 232 | get_html(ticker=ticker, 233 | url_id=income_statement_url_id, 234 | show_page=display_results) 235 | get_html(ticker=ticker, 236 | url_id=balance_sheet_url_id, 237 | show_page=display_results) 238 | get_html(ticker=ticker, 239 | url_id=cf_statement_url_id, 240 | show_page=display_results) 241 | get_html(ticker=ticker, 242 | url_id=statistics_url_id, 243 | show_page=display_results) 244 | # get_tickers_url() 245 | 246 | 247 | if __name__ == '__main__': 248 | main() 249 | -------------------------------------------------------------------------------- /media/graphs_dcf_wacc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olekssy/evkit/f17b60d7d1091e19ffa757720aa523e0776909cb/media/graphs_dcf_wacc.png -------------------------------------------------------------------------------- /media/report_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olekssy/evkit/f17b60d7d1091e19ffa757720aa523e0776909cb/media/report_csv.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='evkit', 4 | version='0.7.0', 5 | packages=[''], 6 | url='https://github.com/olekssy/evkit', 7 | license='MIT', 8 | author='Olekssy Lialka', 9 | author_email='lialka.dev@protonamil.ch', 10 | description= 11 | 'Application for equity valuations, discovery of mispriced stocks.', 12 | install_requires=['numpy', 'pandas', 'requests', 'beautifulsoup4']) 13 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olekssy/evkit/f17b60d7d1091e19ffa757720aa523e0776909cb/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_sec_datareader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pandas as pd 4 | 5 | from evkit import sec_datareader 6 | 7 | 8 | class TestFinancialDataSEC(unittest.TestCase): 9 | """ 10 | Tests for SEC Financial Statement Data Sets downloader. 11 | """ 12 | def setUp(self): 13 | self.findata = sec_datareader.FinancialDataSEC() 14 | return super().setUp() 15 | 16 | def tearDown(self): 17 | return super().tearDown() 18 | 19 | def test_download(self): 20 | self.assertIsNone(self.findata.download()) 21 | 22 | def test_extract(self): 23 | self.assertIsNone(self.findata.extract()) 24 | 25 | def test_parse_index(self): 26 | self.assertIsInstance(self.findata.parse_index(), (pd.DataFrame, )) 27 | 28 | def test_parse_findata(self): 29 | self.assertIsInstance(self.findata.parse_findata(), (pd.DataFrame, )) 30 | 31 | def test_cleanup(self): 32 | self.assertIsNone(self.findata.cleanup()) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | --------------------------------------------------------------------------------