├── .gitignore ├── LICENSE ├── README.rst ├── poetry.lock ├── pyproject.toml ├── setup.py ├── tests ├── mockrequests │ ├── .gitattributes │ ├── .gitignore │ ├── LICENSE.txt │ ├── README.rst │ ├── mockrequests │ │ ├── __init__.py │ │ └── mockrequests.py │ ├── response │ │ ├── GET │ │ │ ├── map.json │ │ │ ├── response1.p │ │ │ ├── response2.p │ │ │ ├── response3.p │ │ │ ├── response4.p │ │ │ ├── response5.p │ │ │ └── response6.p │ │ └── POST │ │ │ └── .gitignore │ └── setup.py ├── test_option.py └── test_stock.py └── wallstreet ├── __init__.py ├── blackandscholes.py ├── constants.py └── wallstreet.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | .idea 62 | 63 | .python-version 64 | *.ipynb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mike Dallas 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 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Wallstreet: Real time Stock and Option tools 2 | -------------------------------------------- 3 | 4 | Wallstreet is a Python 3 library for monitoring and analyzing real time Stock and 5 | Option data. Quotes are provided from the Google Finance API. Wallstreet requires 6 | minimal input from the user, it uses available online data to calculate option 7 | greeks and even scrapes the US Treasury website to get the current risk free rate. 8 | 9 | 10 | Usage 11 | ----- 12 | 13 | Stocks: 14 | 15 | .. code-block:: Python 16 | 17 | from wallstreet import Stock, Call, Put 18 | 19 | >>> s = Stock('AAPL') 20 | >>> s.price 21 | 96.44 22 | >>> s.price 23 | 96.48 24 | >>> s.change 25 | -0.35 26 | >>> s.last_trade 27 | '21 Jan 2016 13:32:12' 28 | 29 | Options: 30 | 31 | .. code-block:: Python 32 | 33 | >>> g = Call('GOOG', d=12, m=2, y=2016, strike=700) 34 | >>> g.price 35 | 38.2 36 | >>> g.implied_volatility() 37 | 0.49222968442691889 38 | >>> g.delta() 39 | 0.56522039722040063 40 | >>> g.vega() 41 | 0.685034827159825 42 | >>> g.underlying.price 43 | 706.59 44 | 45 | Alternative construction: 46 | 47 | .. code-block:: Python 48 | 49 | >>> g = Call('GOOG', d=12, m=2, y=2016) 50 | >>> g 51 | Call(ticker=GOOG, expiration='12-02-2016') 52 | >>> g.strikes 53 | (580, 610, 620, 630, 640, 650, 660, 670, 680, 690, 697.5, 700, 702.5, 707.5, 710, 712.5, 715, 720, ...) 54 | >>> g.set_strike(712.5) 55 | >>> g 56 | Call(ticker=GOOG, expiration='12-02-2016', strike=712.5) 57 | 58 | or 59 | 60 | .. code-block:: Python 61 | 62 | >>> g = Put("GOOG") 63 | 'No options listed for given date, using 22-01-2016 instead' 64 | >>> g.expirations 65 | ['22-01-2016', '29-01-2016', '05-02-2016', '12-02-2016', '19-02-2016', '26-02-2016', '04-03-2016', ...] 66 | >>> g 67 | Put(ticker=GOOG, expiration='22-01-2016') 68 | 69 | Yahoo Finance Support (keep in mind that YF quotes might be delayed): 70 | 71 | .. code-block:: Python 72 | 73 | >>> apple = Stock('AAPL', source='yahoo') 74 | >>> call = Call('AAPL', strike=apple.price, source='yahoo') 75 | No options listed for given date, using '26-05-2017' instead 76 | No option for given strike, using 155 instead 77 | 78 | Download historical data (requires pandas) 79 | 80 | .. code-block:: Python 81 | 82 | s = Stock('BTC-USD') 83 | >>> df = s.historical(days_back=30, frequency='d') 84 | >>> df 85 | Date Open High Low Close Adj Close Volume 86 | 0 2019-07-10 12567.019531 13183.730469 11569.940430 12099.120117 12099.120117 1554955347 87 | 1 2019-07-11 12099.120117 12099.910156 11002.389648 11343.120117 11343.120117 1185222449 88 | 2 2019-07-12 11343.120117 11931.910156 11096.610352 11797.370117 11797.370117 647690095 89 | 3 2019-07-13 11797.370117 11835.870117 10827.530273 11363.969727 11363.969727 668325183 90 | 4 2019-07-14 11363.969727 11447.919922 10118.849609 10204.410156 10204.410156 814667763 91 | 5 2019-07-15 10204.410156 11070.179688 9877.019531 10850.259766 10850.259766 965178341 92 | 6 2019-07-16 10850.259766 11025.759766 9366.820313 9423.440430 9423.440430 1140137759 93 | 7 2019-07-17 9423.440430 9982.240234 9086.509766 9696.150391 9696.150391 965256823 94 | 8 2019-07-18 9696.150391 10776.540039 9292.610352 10638.349609 10638.349609 1033842556 95 | 9 2019-07-19 10638.349609 10757.410156 10135.160156 10532.940430 10532.940430 658190962 96 | 10 2019-07-20 10532.940430 11094.320313 10379.190430 10759.419922 10759.419922 608954333 97 | 11 2019-07-21 10759.419922 10833.990234 10329.889648 10586.709961 10586.709961 405339891 98 | 12 2019-07-22 10586.709961 10676.599609 10072.070313 10325.870117 10325.870117 524442852 99 | 13 2019-07-23 10325.870117 10328.440430 9820.610352 9854.150391 9854.150391 529438124 100 | 14 2019-07-24 9854.150391 9920.540039 9535.780273 9772.139648 9772.139648 531611909 101 | 15 2019-07-25 9772.139648 10184.429688 9744.700195 9882.429688 9882.429688 403576364 102 | 16 2019-07-26 9882.429688 9890.049805 9668.519531 9847.450195 9847.450195 312717110 103 | 17 2019-07-27 9847.450195 10202.950195 9310.469727 9478.320313 9478.320313 512612117 104 | 18 2019-07-28 9478.320313 9591.519531 9135.639648 9531.769531 9531.769531 267243770 105 | 19 2019-07-29 9531.769531 9717.690430 9386.900391 9506.929688 9506.929688 299936368 106 | 20 2019-07-30 9506.929688 9749.530273 9391.780273 9595.519531 9595.519531 276402322 107 | 21 2019-07-31 9595.519531 10123.940430 9581.599609 10089.250000 10089.250000 416343142 108 | 22 2019-08-01 10089.250000 10488.809570 9890.490234 10409.790039 10409.790039 442037342 109 | 23 2019-08-02 10409.790039 10666.639648 10340.820313 10528.990234 10528.990234 463688251 110 | 24 2019-08-03 10528.990234 10915.000000 10509.349609 10820.410156 10820.410156 367536516 111 | 25 2019-08-04 10820.410156 11074.950195 10572.240234 10978.910156 10978.910156 431699306 112 | 26 2019-08-05 10978.910156 11945.379883 10978.889648 11807.959961 11807.959961 870917186 113 | 27 2019-08-06 11807.959961 12316.849609 11224.099609 11467.099609 11467.099609 949534020 114 | 28 2019-08-07 11467.099609 12138.549805 11393.980469 11974.280273 11974.280273 834719365 115 | 29 2019-08-08 11974.280273 12042.870117 11498.040039 11982.799805 11982.799805 588463519 116 | 30 2019-08-09 11983.620117 12027.570313 11674.059570 11810.679688 11810.679688 366160288 117 | 118 | Installation 119 | ------------ 120 | Simply 121 | 122 | .. code-block:: bash 123 | 124 | $ pip install wallstreet 125 | 126 | 127 | Stock Attributes 128 | ---------------- 129 | 130 | - ticker 131 | - price 132 | - id 133 | - exchange 134 | - last_trade 135 | - change (change in currency) 136 | - cp (percentage change) 137 | 138 | 139 | Option Attributes and Methods 140 | ----------------------------- 141 | 142 | - strike 143 | - expiration 144 | - underlying (underlying stock object) 145 | - ticker 146 | - bid 147 | - ask 148 | - price (option price) 149 | - id 150 | - exchange 151 | - change (in currency) 152 | - cp (percentage change) 153 | - volume 154 | - open_interest 155 | - code 156 | - expirations (list of possible expiration dates for option chain) 157 | - strikes (list of possible strike prices) 158 | 159 | - set_strike() 160 | - implied_volatility() 161 | - delta() 162 | - gamma() 163 | - vega() 164 | - theta() 165 | - rho() 166 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "appdirs" 5 | version = "1.4.4" 6 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 11 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 12 | ] 13 | 14 | [[package]] 15 | name = "beautifulsoup4" 16 | version = "4.12.3" 17 | description = "Screen-scraping library" 18 | optional = false 19 | python-versions = ">=3.6.0" 20 | files = [ 21 | {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, 22 | {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, 23 | ] 24 | 25 | [package.dependencies] 26 | soupsieve = ">1.2" 27 | 28 | [package.extras] 29 | cchardet = ["cchardet"] 30 | chardet = ["chardet"] 31 | charset-normalizer = ["charset-normalizer"] 32 | html5lib = ["html5lib"] 33 | lxml = ["lxml"] 34 | 35 | [[package]] 36 | name = "certifi" 37 | version = "2024.2.2" 38 | description = "Python package for providing Mozilla's CA Bundle." 39 | optional = false 40 | python-versions = ">=3.6" 41 | files = [ 42 | {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 43 | {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 44 | ] 45 | 46 | [[package]] 47 | name = "charset-normalizer" 48 | version = "3.3.2" 49 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 50 | optional = false 51 | python-versions = ">=3.7.0" 52 | files = [ 53 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 54 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 55 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 56 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 57 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 58 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 59 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 60 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 61 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 62 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 63 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 64 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 65 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 66 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 67 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 68 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 69 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 70 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 71 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 72 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 73 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 74 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 75 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 76 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 77 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 78 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 79 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 80 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 81 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 82 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 83 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 84 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 85 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 86 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 87 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 88 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 89 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 90 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 91 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 92 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 93 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 94 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 95 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 96 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 97 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 98 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 99 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 100 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 101 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 102 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 103 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 104 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 105 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 106 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 107 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 108 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 109 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 110 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 111 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 112 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 113 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 114 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 115 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 116 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 117 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 118 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 119 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 120 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 121 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 122 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 123 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 124 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 125 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 126 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 127 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 128 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 129 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 130 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 131 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 132 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 133 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 134 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 135 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 136 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 137 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 138 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 139 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 140 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 141 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 142 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 143 | ] 144 | 145 | [[package]] 146 | name = "frozendict" 147 | version = "2.4.0" 148 | description = "A simple immutable dictionary" 149 | optional = false 150 | python-versions = ">=3.6" 151 | files = [ 152 | {file = "frozendict-2.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:475c65202a6f5421df8cacb8a2f29c5087134a0542b0540ae95fbf4db7af2ff9"}, 153 | {file = "frozendict-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2607e82efdd2c277224a58bda3994d4cd48e49eff7fa31e404cf3066e8dbfeae"}, 154 | {file = "frozendict-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd4583194baabe100c135883017da76259a315d34e303eddf198541b7e02e44"}, 155 | {file = "frozendict-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efca7281184b54f7abab6980cf25837b709f72ced62791f62dabcd7b184d958a"}, 156 | {file = "frozendict-2.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fc4cba1ced988ce9020dfcaae6fe3f5521eebc00c5772b511aaf691b0be91e6"}, 157 | {file = "frozendict-2.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fab616e7c0fea2ac928f107c740bd9ba516fc083adfcd1c391d6bfc9164403d"}, 158 | {file = "frozendict-2.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:09ba8ee37d260adde311b8eb4cd12bf27f64071242f736757ae6a11d331eb860"}, 159 | {file = "frozendict-2.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:0615ed71570eec3cc96df063930ea6e563211efeeac86e3f3cc8bdfc9c9bfab7"}, 160 | {file = "frozendict-2.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc754117a7d60ba8e55b3c39abd67f37fbc05dd63cdcb03d1717a382fe0a3421"}, 161 | {file = "frozendict-2.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2804ea4bd2179bb33b99483cc8d69246630cc00632b9affe2914e8666f1cc7e5"}, 162 | {file = "frozendict-2.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4700c3f0aebdc8f4375c35590135794b1dbf2aca132f4756b584fa9910af2d"}, 163 | {file = "frozendict-2.4.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:da4406d95c340e0b1cc43a3858fac729f52689325bcf61a9182eb94aff7451dc"}, 164 | {file = "frozendict-2.4.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1875e7b70a5724bf964354da8fd542240d2cead0d80053ac96bf4494ce3517fa"}, 165 | {file = "frozendict-2.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a60f353496637ca21396289a7d969af1eb4ec4d11a7c37a0e7f25fc1761a0c97"}, 166 | {file = "frozendict-2.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b666f9c6c8a9e794d2713a944b10a65480ff459579d75b5f686c75031c2c2dfc"}, 167 | {file = "frozendict-2.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d81fb396ea81fcba3b3dde4a4b51adcb74ff31632014fbfd030f8acd5a7292"}, 168 | {file = "frozendict-2.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4925c8e82d2bd23d45996cd0827668a52b9c51103897c98ce409a763d0c00c61"}, 169 | {file = "frozendict-2.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aa86325da6a6071284b4ed3d9d2cd9db068560aebad503b658d6a889a0575683"}, 170 | {file = "frozendict-2.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5bb5b62d4e2bce12e91800496d94de41bec8f16e4d8a7b16e8f263676ae2031a"}, 171 | {file = "frozendict-2.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3909df909516cfd7bcefd9a3003948970a12a50c5648d8bbddafcef171f2117f"}, 172 | {file = "frozendict-2.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:204f2c5c10fc018d1ba8ccc67758aa83fe769c782547bd26dc250317a7ccba71"}, 173 | {file = "frozendict-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8d1d269874c94b1ed2b6667e5e43dcf4541838019b1caa4c48f848ac73634df"}, 174 | {file = "frozendict-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:809f1cffb602cf06e5186c69c0e3b74bec7a3684593145331f9aa2a65b5ba3b7"}, 175 | {file = "frozendict-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b017cba5f73869b04c2977139ad08e57a7480de1e384c34193939698119baa1d"}, 176 | {file = "frozendict-2.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0b75e5e231621dedaef88334997e79fbd137dd89895543d3862fe0220fc3572c"}, 177 | {file = "frozendict-2.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df3819a5d48ab3aae1548e62093d0111ad7c3b62ff9392421b7bbf149c08b629"}, 178 | {file = "frozendict-2.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:42a9b33ccf9d417b22146e59803c53d5c39d7d9151d2df8df59c235f6a1a5ed7"}, 179 | {file = "frozendict-2.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3f51bfa64e0c4a6608e3f2878bab1211a6b3b197de6fa57151bbe73f1184457"}, 180 | {file = "frozendict-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a1d232f092dc686e6ef23d436bde30f82c018f31cef1b89b31caef03814b1617"}, 181 | {file = "frozendict-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e530658134e88607ff8c2c8934a07b2bb5e9fffab5045f127746f6542c6c77e"}, 182 | {file = "frozendict-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23a52bbea30c9e35b89291273944393770fb031e522a172e3aff19b62cc50047"}, 183 | {file = "frozendict-2.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f91acaff475d0ef0d3436b805c9b91fc627a6a8a281771a24f7ab7f458a0b34f"}, 184 | {file = "frozendict-2.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:08d9c7c1aa92b94538b3a79c43999f999012e174588435f197794d5e5a80e0f5"}, 185 | {file = "frozendict-2.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:05c5a77957ecba4286c7ab33861a8f4f2badc7ea86fc82b834fb360d3aa4c108"}, 186 | {file = "frozendict-2.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:c8af8a6a39e0050d3f3193cda56c42b43534a9b3995c44241bb9527e3c3fd451"}, 187 | {file = "frozendict-2.4.0.tar.gz", hash = "sha256:c26758198e403337933a92b01f417a8240c954f553e1d4b5e0f8e39d9c8e3f0a"}, 188 | ] 189 | 190 | [[package]] 191 | name = "html5lib" 192 | version = "1.1" 193 | description = "HTML parser based on the WHATWG HTML specification" 194 | optional = false 195 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 196 | files = [ 197 | {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, 198 | {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, 199 | ] 200 | 201 | [package.dependencies] 202 | six = ">=1.9" 203 | webencodings = "*" 204 | 205 | [package.extras] 206 | all = ["chardet (>=2.2)", "genshi", "lxml"] 207 | chardet = ["chardet (>=2.2)"] 208 | genshi = ["genshi"] 209 | lxml = ["lxml"] 210 | 211 | [[package]] 212 | name = "idna" 213 | version = "3.6" 214 | description = "Internationalized Domain Names in Applications (IDNA)" 215 | optional = false 216 | python-versions = ">=3.5" 217 | files = [ 218 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 219 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 220 | ] 221 | 222 | [[package]] 223 | name = "lxml" 224 | version = "5.1.0" 225 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 226 | optional = false 227 | python-versions = ">=3.6" 228 | files = [ 229 | {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, 230 | {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, 231 | {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, 232 | {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, 233 | {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, 234 | {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, 235 | {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, 236 | {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, 237 | {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, 238 | {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, 239 | {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, 240 | {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, 241 | {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, 242 | {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, 243 | {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, 244 | {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, 245 | {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, 246 | {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, 247 | {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, 248 | {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, 249 | {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, 250 | {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, 251 | {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, 252 | {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, 253 | {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, 254 | {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, 255 | {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, 256 | {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, 257 | {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, 258 | {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, 259 | {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, 260 | {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, 261 | {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, 262 | {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, 263 | {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, 264 | {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, 265 | {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, 266 | {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, 267 | {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, 268 | {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, 269 | {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, 270 | {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, 271 | {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, 272 | {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, 273 | {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, 274 | {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, 275 | {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, 276 | {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, 277 | {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, 278 | {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, 279 | {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, 280 | {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, 281 | {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, 282 | {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, 283 | {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, 284 | {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, 285 | {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, 286 | {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, 287 | {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, 288 | {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, 289 | {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, 290 | {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, 291 | {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, 292 | {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, 293 | {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, 294 | {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, 295 | {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, 296 | {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, 297 | {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, 298 | {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, 299 | {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, 300 | {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, 301 | {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, 302 | {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, 303 | {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, 304 | {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, 305 | {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, 306 | {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, 307 | ] 308 | 309 | [package.extras] 310 | cssselect = ["cssselect (>=0.7)"] 311 | html5 = ["html5lib"] 312 | htmlsoup = ["BeautifulSoup4"] 313 | source = ["Cython (>=3.0.7)"] 314 | 315 | [[package]] 316 | name = "multitasking" 317 | version = "0.0.11" 318 | description = "Non-blocking Python methods using decorators" 319 | optional = false 320 | python-versions = "*" 321 | files = [ 322 | {file = "multitasking-0.0.11-py3-none-any.whl", hash = "sha256:1e5b37a5f8fc1e6cfaafd1a82b6b1cc6d2ed20037d3b89c25a84f499bd7b3dd4"}, 323 | {file = "multitasking-0.0.11.tar.gz", hash = "sha256:4d6bc3cc65f9b2dca72fb5a787850a88dae8f620c2b36ae9b55248e51bcd6026"}, 324 | ] 325 | 326 | [[package]] 327 | name = "numpy" 328 | version = "1.26.4" 329 | description = "Fundamental package for array computing in Python" 330 | optional = false 331 | python-versions = ">=3.9" 332 | files = [ 333 | {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, 334 | {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, 335 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, 336 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, 337 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, 338 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, 339 | {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, 340 | {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, 341 | {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, 342 | {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, 343 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, 344 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, 345 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, 346 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, 347 | {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, 348 | {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, 349 | {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, 350 | {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, 351 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, 352 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, 353 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, 354 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, 355 | {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, 356 | {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, 357 | {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, 358 | {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, 359 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, 360 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, 361 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, 362 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, 363 | {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, 364 | {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, 365 | {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, 366 | {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, 367 | {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, 368 | {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, 369 | ] 370 | 371 | [[package]] 372 | name = "pandas" 373 | version = "2.2.1" 374 | description = "Powerful data structures for data analysis, time series, and statistics" 375 | optional = false 376 | python-versions = ">=3.9" 377 | files = [ 378 | {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, 379 | {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, 380 | {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, 381 | {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"}, 382 | {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"}, 383 | {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"}, 384 | {file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"}, 385 | {file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"}, 386 | {file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"}, 387 | {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"}, 388 | {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"}, 389 | {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"}, 390 | {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"}, 391 | {file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"}, 392 | {file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"}, 393 | {file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"}, 394 | {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"}, 395 | {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"}, 396 | {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"}, 397 | {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"}, 398 | {file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"}, 399 | {file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"}, 400 | {file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"}, 401 | {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"}, 402 | {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"}, 403 | {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"}, 404 | {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"}, 405 | {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, 406 | {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, 407 | ] 408 | 409 | [package.dependencies] 410 | numpy = [ 411 | {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, 412 | {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, 413 | {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, 414 | ] 415 | python-dateutil = ">=2.8.2" 416 | pytz = ">=2020.1" 417 | tzdata = ">=2022.7" 418 | 419 | [package.extras] 420 | all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] 421 | aws = ["s3fs (>=2022.11.0)"] 422 | clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] 423 | compression = ["zstandard (>=0.19.0)"] 424 | computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] 425 | consortium-standard = ["dataframe-api-compat (>=0.1.7)"] 426 | excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] 427 | feather = ["pyarrow (>=10.0.1)"] 428 | fss = ["fsspec (>=2022.11.0)"] 429 | gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] 430 | hdf5 = ["tables (>=3.8.0)"] 431 | html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] 432 | mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] 433 | output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] 434 | parquet = ["pyarrow (>=10.0.1)"] 435 | performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] 436 | plot = ["matplotlib (>=3.6.3)"] 437 | postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] 438 | pyarrow = ["pyarrow (>=10.0.1)"] 439 | spss = ["pyreadstat (>=1.2.0)"] 440 | sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] 441 | test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] 442 | xml = ["lxml (>=4.9.2)"] 443 | 444 | [[package]] 445 | name = "peewee" 446 | version = "3.17.1" 447 | description = "a little orm" 448 | optional = false 449 | python-versions = "*" 450 | files = [ 451 | {file = "peewee-3.17.1.tar.gz", hash = "sha256:e009ac4227c4fdc0058a56e822ad5987684f0a1fbb20fed577200785102581c3"}, 452 | ] 453 | 454 | [[package]] 455 | name = "python-dateutil" 456 | version = "2.9.0.post0" 457 | description = "Extensions to the standard Python datetime module" 458 | optional = false 459 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 460 | files = [ 461 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 462 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 463 | ] 464 | 465 | [package.dependencies] 466 | six = ">=1.5" 467 | 468 | [[package]] 469 | name = "pytz" 470 | version = "2024.1" 471 | description = "World timezone definitions, modern and historical" 472 | optional = false 473 | python-versions = "*" 474 | files = [ 475 | {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, 476 | {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, 477 | ] 478 | 479 | [[package]] 480 | name = "requests" 481 | version = "2.31.0" 482 | description = "Python HTTP for Humans." 483 | optional = false 484 | python-versions = ">=3.7" 485 | files = [ 486 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 487 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 488 | ] 489 | 490 | [package.dependencies] 491 | certifi = ">=2017.4.17" 492 | charset-normalizer = ">=2,<4" 493 | idna = ">=2.5,<4" 494 | urllib3 = ">=1.21.1,<3" 495 | 496 | [package.extras] 497 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 498 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 499 | 500 | [[package]] 501 | name = "scipy" 502 | version = "1.12.0" 503 | description = "Fundamental algorithms for scientific computing in Python" 504 | optional = false 505 | python-versions = ">=3.9" 506 | files = [ 507 | {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, 508 | {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, 509 | {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, 510 | {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, 511 | {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, 512 | {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, 513 | {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, 514 | {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, 515 | {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, 516 | {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, 517 | {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, 518 | {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, 519 | {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, 520 | {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, 521 | {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, 522 | {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, 523 | {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, 524 | {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, 525 | {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, 526 | {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, 527 | {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, 528 | {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, 529 | {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, 530 | {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, 531 | {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, 532 | ] 533 | 534 | [package.dependencies] 535 | numpy = ">=1.22.4,<1.29.0" 536 | 537 | [package.extras] 538 | dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] 539 | doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] 540 | test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] 541 | 542 | [[package]] 543 | name = "six" 544 | version = "1.16.0" 545 | description = "Python 2 and 3 compatibility utilities" 546 | optional = false 547 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 548 | files = [ 549 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 550 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 551 | ] 552 | 553 | [[package]] 554 | name = "soupsieve" 555 | version = "2.5" 556 | description = "A modern CSS selector implementation for Beautiful Soup." 557 | optional = false 558 | python-versions = ">=3.8" 559 | files = [ 560 | {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, 561 | {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, 562 | ] 563 | 564 | [[package]] 565 | name = "tzdata" 566 | version = "2024.1" 567 | description = "Provider of IANA time zone data" 568 | optional = false 569 | python-versions = ">=2" 570 | files = [ 571 | {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, 572 | {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, 573 | ] 574 | 575 | [[package]] 576 | name = "urllib3" 577 | version = "2.2.1" 578 | description = "HTTP library with thread-safe connection pooling, file post, and more." 579 | optional = false 580 | python-versions = ">=3.8" 581 | files = [ 582 | {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, 583 | {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, 584 | ] 585 | 586 | [package.extras] 587 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 588 | h2 = ["h2 (>=4,<5)"] 589 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 590 | zstd = ["zstandard (>=0.18.0)"] 591 | 592 | [[package]] 593 | name = "webencodings" 594 | version = "0.5.1" 595 | description = "Character encoding aliases for legacy web content" 596 | optional = false 597 | python-versions = "*" 598 | files = [ 599 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 600 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 601 | ] 602 | 603 | [[package]] 604 | name = "yfinance" 605 | version = "0.2.37" 606 | description = "Download market data from Yahoo! Finance API" 607 | optional = false 608 | python-versions = "*" 609 | files = [ 610 | {file = "yfinance-0.2.37-py2.py3-none-any.whl", hash = "sha256:3ac75fa1cd3496ee76b6df5d63d29679487ea9447123c5b935d1593240737a8f"}, 611 | {file = "yfinance-0.2.37.tar.gz", hash = "sha256:e5f78c9bd27bae7abfd0af9b7996620eaa9aba759d67f957296634d7d54f0cef"}, 612 | ] 613 | 614 | [package.dependencies] 615 | appdirs = ">=1.4.4" 616 | beautifulsoup4 = ">=4.11.1" 617 | frozendict = ">=2.3.4" 618 | html5lib = ">=1.1" 619 | lxml = ">=4.9.1" 620 | multitasking = ">=0.0.7" 621 | numpy = ">=1.16.5" 622 | pandas = ">=1.3.0" 623 | peewee = ">=3.16.2" 624 | pytz = ">=2022.5" 625 | requests = ">=2.31" 626 | 627 | [package.extras] 628 | nospam = ["requests-cache (>=1.0)", "requests-ratelimiter (>=0.3.1)"] 629 | repair = ["scipy (>=1.6.3)"] 630 | 631 | [metadata] 632 | lock-version = "2.0" 633 | python-versions = "^3.9" 634 | content-hash = "307a7ff1aa504b2fa1a71a386179197129d2887888882ddcb5749065a83c6018" 635 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "wallstreet" 3 | version = "0.4.0" 4 | description = "Stock and Option tools" 5 | authors = ["Mike Dallas "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | requests = "^2.31" 11 | scipy = "^1.12" 12 | yfinance = "^0.2.37" 13 | 14 | [tool.poetry.dev-dependencies] 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='wallstreet', 5 | version='0.4.0', 6 | description='Real-time Stock and Option tools', 7 | url='https://github.com/mcdallas/wallstreet', 8 | author='Mike Dallas', 9 | author_email='mcdallas@protonmail.com', 10 | license='MIT', 11 | packages=['wallstreet'], 12 | classifiers=[ 13 | # How mature is this project? Common values are 14 | # 3 - Alpha 15 | # 4 - Beta 16 | # 5 - Production/Stable 17 | 'Development Status :: 3 - Alpha', 18 | 19 | # Indicate who your project is intended for 20 | 'Intended Audience :: Developers', 21 | 'Intended Audience :: Financial and Insurance Industry', 22 | 'Topic :: Office/Business :: Financial :: Investment', 23 | 'Topic :: Software Development :: Libraries :: Python Modules', 24 | 'Operating System :: OS Independent', 25 | 26 | # Pick your license as you wish (should match "license" above) 27 | 'License :: OSI Approved :: MIT License', 28 | 29 | # Specify the Python versions you support here. In particular, ensure 30 | # that you indicate whether you support Python 2, Python 3 or both. 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.5', 33 | ], 34 | keywords='stocks options finance market shares greeks implied volatility real-time', 35 | install_requires=['requests', 'scipy', 'yfinance'], 36 | ) 37 | -------------------------------------------------------------------------------- /tests/mockrequests/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /tests/mockrequests/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .idea/ 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask instance folder 59 | instance/ 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # ========================= 93 | # Operating System Files 94 | # ========================= 95 | 96 | # OSX 97 | # ========================= 98 | 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Thumbnails 104 | ._* 105 | 106 | # Files that might appear in the root of a volume 107 | .DocumentRevisions-V100 108 | .fseventsd 109 | .Spotlight-V100 110 | .TemporaryItems 111 | .Trashes 112 | .VolumeIcon.icns 113 | 114 | # Directories potentially created on remote AFP share 115 | .AppleDB 116 | .AppleDesktop 117 | Network Trash Folder 118 | Temporary Items 119 | .apdisk 120 | 121 | # Windows 122 | # ========================= 123 | 124 | # Windows image file caches 125 | Thumbs.db 126 | ehthumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | # Recycle Bin used on file shares 132 | $RECYCLE.BIN/ 133 | 134 | # Windows Installer files 135 | *.cab 136 | *.msi 137 | *.msm 138 | *.msp 139 | 140 | # Windows shortcuts 141 | *.lnk 142 | -------------------------------------------------------------------------------- /tests/mockrequests/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mike Dallas 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 | -------------------------------------------------------------------------------- /tests/mockrequests/README.rst: -------------------------------------------------------------------------------- 1 | Mockrequests: Easy unit testing for HTTP requests 2 | ------------------------------------------------- 3 | 4 | Mockrequests is a Python 3 library for unit testing code that makes HTTP requests 5 | using the requests module. Simply monkey patch the imported requests module with 6 | mockrequests when unittesting and everything else is done for you. Mockrequests 7 | makes use of the unittest.mock module added in Python 3.3 so you will be able to 8 | take advantage of its functionality. 9 | 10 | 11 | Usage 12 | ----- 13 | 14 | .. code-block:: Python 15 | 16 | import unittest 17 | from my_package import my_module 18 | import mockrequests 19 | 20 | class MyModuleTest(unittest.TestCase): 21 | def setUp(self): 22 | self.oldrequests = my_module.requests # Make a backup of your requests import 23 | my_module.requests = mockrequests # Replace the requests import with mockrequests 24 | 25 | def test_my_module(self): 26 | # blah blah 27 | 28 | def tearDown(self): 29 | my_module.requests = self.oldrequests # Move the old requests back 30 | 31 | 32 | Mockrequests will redirect all the HTTP request of your code to cached request objects that you 33 | set up before. 34 | 35 | Setup 36 | ----- 37 | 38 | .. code-block:: Python 39 | 40 | import requests 41 | import mockrequests 42 | 43 | >>> r = requests.get('http://www.somewebsite.com/') 44 | >>> mockrequests.save(r) 45 | 46 | # That's it. Now every time you try to access this url you will get the cached response. 47 | 48 | >>> mockrequests.get('http://www.somewebsite.com/') 49 | 50 | 51 | If the url that your code tries to reach is not static (i.e it contains a date) then you can pass a regex expression and mockrequests will return the first saved url that matches the regex. 52 | 53 | .. code-block:: Python 54 | 55 | >>> r = requests.get('http://www.google.com/') 56 | >>> mockrequests.save(r, regex='^.*google.*?') 57 | >>> mockrequests.get('http://www.google.co.uk/') 58 | 59 | 60 | You can also pass the strict=True parameter and you will get a response only if the HTTP headers match. 61 | -------------------------------------------------------------------------------- /tests/mockrequests/mockrequests/__init__.py: -------------------------------------------------------------------------------- 1 | from .mockrequests import get, post, Request, save, Session 2 | 3 | __all__ = ['get', 'post', 'Request', 'save', 'Session'] 4 | -------------------------------------------------------------------------------- /tests/mockrequests/mockrequests/mockrequests.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pickle 3 | import os 4 | import json 5 | from unittest.mock import Mock 6 | 7 | import requests 8 | 9 | ABSPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 10 | 11 | 12 | def abspath(path): 13 | return os.path.join(ABSPATH, path) 14 | 15 | 16 | def prepare_request(*args, **kwargs): 17 | r = requests.Request(*args, **kwargs) 18 | s = requests.Session() 19 | return s.prepare_request(r) 20 | 21 | 22 | def are_equal(req1, req2): 23 | urls = req1.url == req2.url 24 | headers = req1.headers == req2.headers 25 | return all((urls, headers)) 26 | 27 | 28 | def side_effect(method, *args, **kwargs): 29 | """ Main function driving the Mock object """ 30 | request = prepare_request(method, *args, **kwargs) 31 | 32 | d = load_map(method) 33 | for key, val in d.items(): 34 | stored_url, stored_regex, stored_strict = val 35 | 36 | if request.url == stored_url or (stored_regex and re.compile(stored_regex).match(request.url)): 37 | saved_request = load_file(key, method) 38 | if not stored_strict: 39 | return saved_request 40 | else: 41 | if are_equal(request, saved_request): 42 | return saved_request 43 | 44 | 45 | def side_effect_get(*args, **kwargs): 46 | return side_effect('GET', *args, **kwargs) 47 | 48 | 49 | def side_effect_post(*args, **kwargs): 50 | return side_effect('POST', *args, **kwargs) 51 | 52 | 53 | def load_file(filename, method): 54 | """ Unpickles the response object """ 55 | with open(abspath('response/%s/%s' % (method, filename)), 'rb') as fileobj: 56 | return pickle.load(fileobj) 57 | 58 | get = Mock(side_effect=side_effect_get) 59 | post = Mock(side_effect=side_effect_post) 60 | Request = Mock(side_effect=side_effect) 61 | 62 | 63 | class Session: 64 | get = get 65 | post = post 66 | 67 | def dump(request): 68 | """ Pickles the given response object to a folder corresponding to the request method """ 69 | method = request.request.method 70 | 71 | i = 1 72 | while os.path.exists(abspath('response/%s/response%s.p' % (method, i))): 73 | i += 1 74 | filename = 'response%s.p' % i 75 | with open(abspath('response/%s/%s' % (method, filename)), 'wb') as fileobj: 76 | pickle.dump(request, fileobj) 77 | return filename 78 | 79 | 80 | def load_map(method): 81 | """ Loads the map file from disk """ 82 | try: 83 | with open(abspath('response/%s/map.json' % method), 'r') as fileobj: 84 | d = json.load(fileobj) 85 | except FileNotFoundError: 86 | d = {} 87 | return d 88 | 89 | 90 | def save_map(d, method): 91 | """ Saves the map file to disk """ 92 | if not d: 93 | d = {} 94 | with open(abspath('response/%s/map.json' % method), 'w') as fileobj: 95 | json.dump(d, fileobj) 96 | 97 | 98 | def save(response, regex=None, strict=False): 99 | """ Saves the response object and creates a reference to it in the map file """ 100 | request = response.history[0].request if response.history else response.request # The original request object 101 | url = request.url 102 | method = response.request.method 103 | 104 | filename = dump(response) 105 | 106 | d = load_map(method) 107 | d[filename] = [url, regex, strict] 108 | save_map(d, method) 109 | -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/map.json: -------------------------------------------------------------------------------- 1 | {"response1.p": ["http://finance.google.com/finance/info?client=ig&q=GOOG", null, false], "response2.p": ["https://query2.finance.yahoo.com/v7/finance/options/GOOG", null, false], "response3.p": ["http://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yield", null, false], "response4.p": ["https://www.google.com/finance/option_chain?q=GOOG&expd=16&expm=6&expy=2017&output=json", null, false], "response5.p": ["https://query2.finance.yahoo.com/v7/finance/options/GOOG?date=1497571200", null, false], "response6.p": ["https://www.google.com/finance/option_chain?q=GOOG&expd=15&expm=6&expy=2017&output=json", null, false]} -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response1.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response1.p -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response2.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response2.p -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response3.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response3.p -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response4.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response4.p -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response5.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response5.p -------------------------------------------------------------------------------- /tests/mockrequests/response/GET/response6.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcdallas/wallstreet/1aef8343da71de8167157943b3bdbcb465c9fc6a/tests/mockrequests/response/GET/response6.p -------------------------------------------------------------------------------- /tests/mockrequests/response/POST/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /tests/mockrequests/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup 3 | 4 | setup( 5 | name='mockrequests', 6 | version='0.1.2', 7 | description='Easy unit testing for HTTP requests', 8 | url='https://github.com/mcdallas/mockrequests', 9 | author='Mike Dallas', 10 | author_email='mc-dallas@hotmail.com', 11 | license='MIT', 12 | packages=['mockrequests'], 13 | classifiers=[ 14 | # How mature is this project? Common values are 15 | # 3 - Alpha 16 | # 4 - Beta 17 | # 5 - Production/Stable 18 | 'Development Status :: 3 - Alpha', 19 | 20 | # Indicate who your project is intended for 21 | 'Intended Audience :: Developers', 22 | 'Topic :: Software Development :: Testing', 23 | 'Topic :: Internet :: WWW/HTTP', 24 | 'Topic :: Software Development :: Libraries :: Python Modules', 25 | 'Operating System :: OS Independent', 26 | 27 | # Pick your license as you wish (should match "license" above) 28 | 'License :: OSI Approved :: MIT License', 29 | 30 | # Specify the Python versions you support here. In particular, ensure 31 | # that you indicate whether you support Python 2, Python 3 or both. 32 | 'Programming Language :: Python :: 3', 33 | 'Programming Language :: Python :: 3.5', 34 | ], 35 | keywords='requests mock unit test unit-test http', 36 | install_requires=['requests'], 37 | ) -------------------------------------------------------------------------------- /tests/test_option.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wallstreet import wallstreet, blackandscholes 3 | from tests.mockrequests import mockrequests 4 | 5 | 6 | class CallTest(unittest.TestCase): 7 | def setUp(self): 8 | self.oldrequests = wallstreet.requests 9 | wallstreet.requests = mockrequests 10 | blackandscholes.requests = mockrequests 11 | 12 | def test_creation(self): 13 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017) 14 | 15 | def test_creation_yahoo(self): 16 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, source='yahoo') 17 | 18 | def test_closest_date(self): 19 | s = wallstreet.Call('GOOG', d=15, m=6, y=2017) 20 | self.assertEqual(s.expiration, '16-06-2017') 21 | 22 | def test_closest_strike(self): 23 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, strike=798) 24 | self.assertEqual(s.strike, 800) 25 | 26 | def test_strike(self): 27 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017) 28 | s.set_strike(800) 29 | 30 | def test_strike_yahoo(self): 31 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, source='yahoo') 32 | s.set_strike(800) 33 | 34 | def test_price(self): 35 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, strike=800) 36 | self.assertEqual(s.price, 40.39) 37 | 38 | def test_price_yahoo(self): 39 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, strike=800, source='yahoo') 40 | self.assertEqual(s.price, 40.39) 41 | 42 | def test_rate(self): 43 | self.assertEqual(wallstreet.Option.rate(0.2328767123287671), 0.007689726027397261) 44 | 45 | def test_bs_called_with(self): 46 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017) 47 | s.T = 0.2328767123287671 48 | s.set_strike(800) 49 | self.assertEqual(s.BandS.K, 800) 50 | self.assertEqual(s.BandS.T, 0.2328767123287671) 51 | self.assertEqual(s.BandS.S, 834.34) 52 | self.assertEqual(s.BandS.opt_price, 40.39) 53 | self.assertEqual(s.BandS.r, 0.007689726027397261) 54 | 55 | def test_underlying_price(self): 56 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, strike=800) 57 | self.assertEqual(s.underlying.price, 834.34) 58 | 59 | def test_underlying_price_yahoo(self): 60 | s = wallstreet.Call('GOOG', d=16, m=6, y=2017, strike=800, source='yahoo') 61 | self.assertEqual(s.underlying.price, 833.65) 62 | 63 | def tearDown(self): 64 | wallstreet.requests = self.oldrequests 65 | blackandscholes.requests = self.oldrequests 66 | -------------------------------------------------------------------------------- /tests/test_stock.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wallstreet import wallstreet 3 | from tests.mockrequests import mockrequests 4 | 5 | 6 | class StockTest(unittest.TestCase): 7 | def setUp(self): 8 | self.oldrequests = wallstreet.requests 9 | wallstreet.requests = mockrequests 10 | 11 | def test_price(self): 12 | s = wallstreet.Stock('GOOG') 13 | self.assertEqual(s.price, 834.34) 14 | 15 | def test_last_trade(self): 16 | s = wallstreet.Stock('GOOG') 17 | self.assertEqual(s.last_trade, '22 Mar 2017 11:38:12') 18 | 19 | def test_yahoo_price(self): 20 | s = wallstreet.Stock('GOOG', source='yahoo') 21 | self.assertEqual(s.price, 833.65) 22 | 23 | def test_yahoo_last_trade(self): 24 | s = wallstreet.Stock('GOOG', source='yahoo') 25 | self.assertEqual(s.last_trade, '22 Mar 2017 15:53:24') 26 | 27 | def tearDown(self): 28 | wallstreet.requests = self.oldrequests 29 | -------------------------------------------------------------------------------- /wallstreet/__init__.py: -------------------------------------------------------------------------------- 1 | from wallstreet.wallstreet import Stock, Call, Put 2 | 3 | __all__ = ['Stock', 'Call', 'Put'] 4 | 5 | __version__ = "0.4.0" -------------------------------------------------------------------------------- /wallstreet/blackandscholes.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import xml.etree.ElementTree as ET 3 | 4 | from scipy.interpolate import interp1d 5 | from numpy import sqrt, log, exp 6 | from scipy.stats import norm 7 | from scipy.optimize import fsolve 8 | 9 | from wallstreet.constants import * 10 | 11 | import xml.etree.ElementTree as ET 12 | 13 | def riskfree(): 14 | try: 15 | r = requests.get(TREASURY_URL) 16 | 17 | root = ET.fromstring(r.text) 18 | days = root.findall('.//G_BC_CAT') 19 | last = days[-1] 20 | 21 | def parse(node): 22 | return float(node.text) 23 | 24 | m1 = parse(last.find('BC_1MONTH')) 25 | m2 = parse(last.find('BC_2MONTH')) 26 | m3 = parse(last.find('BC_3MONTH')) 27 | m6 = parse(last.find('BC_6MONTH')) 28 | y1 = parse(last.find('BC_1YEAR')) 29 | y2 = parse(last.find('BC_2YEAR')) 30 | y3 = parse(last.find('BC_3YEAR')) 31 | y5 = parse(last.find('BC_5YEAR')) 32 | y7 = parse(last.find('BC_7YEAR')) 33 | y10 = parse(last.find('BC_10YEAR')) 34 | y20 = parse(last.find('BC_20YEAR')) 35 | y30 = parse(last.find('BC_30YEAR')) 36 | 37 | years = (0, 1/12, 2/12, 3/12, 6/12, 12/12, 24/12, 36/12, 60/12, 84/12, 120/12, 240/12, 360/12) 38 | rates = (OVERNIGHT_RATE, m1/100, m2/100, m3/100, m6/100, y1/100, y2/100, y3/100, y5/100, y7/100, y10/100, y20/100, y30/100) 39 | return interp1d(years, rates) 40 | except Exception: 41 | return lambda x: FALLBACK_RISK_FREE_RATE 42 | 43 | class BlackandScholes: 44 | 45 | def __init__(self, S, K, T, price, r, option, q=0): 46 | self.S, self.K, self.T, self.option, self.q = S, K, T, option, q 47 | self.r = r 48 | self.opt_price = price 49 | self.impvol = self.implied_volatility() 50 | 51 | @staticmethod 52 | def _BlackScholesCall(S, K, T, sigma, r, q): 53 | d1 = (log(S/K) + (r - q + (sigma**2)/2)*T)/(sigma*sqrt(T)) 54 | d2 = d1 - sigma*sqrt(T) 55 | return S*exp(-q*T)*norm.cdf(d1) - K*exp(-r*T)*norm.cdf(d2) 56 | 57 | @staticmethod 58 | def _BlackScholesPut(S, K, T, sigma, r, q): 59 | d1 = (log(S/K) + (r - q + (sigma**2)/2)*T)/(sigma*sqrt(T)) 60 | d2 = d1 - sigma*sqrt(T) 61 | return K*exp(-r*T)*norm.cdf(-d2) - S*exp(-q*T)*norm.cdf(-d1) 62 | 63 | def _fprime(self, sigma): 64 | logSoverK = log(self.S/self.K) 65 | n12 = ((self.r + sigma**2/2)*self.T) 66 | numerd1 = logSoverK + n12 67 | d1 = numerd1/(sigma*sqrt(self.T)) 68 | return self.S*sqrt(self.T)*norm.pdf(d1)*exp(-self.r*self.T) 69 | 70 | def BS(self, S, K, T, sigma, r, q): 71 | if self.option == 'Call': 72 | return self._BlackScholesCall(S, K, T, sigma, r, q) 73 | elif self.option == 'Put': 74 | return self._BlackScholesPut(S, K, T, sigma, r, q) 75 | 76 | def implied_volatility(self): 77 | impvol = lambda x: self.BS(self.S, self.K, self.T, x, self.r, self.q) - self.opt_price 78 | iv = fsolve(impvol, SOLVER_STARTING_VALUE, fprime=self._fprime, xtol=IMPLIED_VOLATILITY_TOLERANCE) 79 | return iv[0] 80 | 81 | def delta(self): 82 | h = DELTA_DIFFERENTIAL 83 | p1 = self.BS(self.S + h, self.K, self.T, self.impvol, self.r, self.q) 84 | p2 = self.BS(self.S - h, self.K, self.T, self.impvol, self.r, self.q) 85 | return (p1-p2)/(2*h) 86 | 87 | def gamma(self): 88 | h = GAMMA_DIFFERENTIAL 89 | p1 = self.BS(self.S + h, self.K, self.T, self.impvol, self.r, self.q) 90 | p2 = self.BS(self.S, self.K, self.T, self.impvol, self.r, self.q) 91 | p3 = self.BS(self.S - h, self.K, self.T, self.impvol, self.r, self.q) 92 | return (p1 - 2*p2 + p3)/(h**2) 93 | 94 | def vega(self): 95 | h = VEGA_DIFFERENTIAL 96 | p1 = self.BS(self.S, self.K, self.T, self.impvol + h, self.r, self.q) 97 | p2 = self.BS(self.S, self.K, self.T, self.impvol - h, self.r, self.q) 98 | return (p1-p2)/(2*h*100) 99 | 100 | def theta(self): 101 | h = THETA_DIFFERENTIAL 102 | p1 = self.BS(self.S, self.K, self.T + h, self.impvol, self.r, self.q) 103 | p2 = self.BS(self.S, self.K, self.T - h, self.impvol, self.r, self.q) 104 | return (p1-p2)/(2*h*365) 105 | 106 | def rho(self): 107 | h = RHO_DIFFERENTIAL 108 | p1 = self.BS(self.S, self.K, self.T, self.impvol, self.r + h, self.q) 109 | p2 = self.BS(self.S, self.K, self.T, self.impvol, self.r - h, self.q) 110 | return (p1-p2)/(2*h*100) 111 | -------------------------------------------------------------------------------- /wallstreet/constants.py: -------------------------------------------------------------------------------- 1 | 2 | DATE_FORMAT = '%d-%m-%Y' 3 | DATETIME_FORMAT = '%d %b %Y %H:%M:%S' 4 | 5 | TREASURY_URL = "https://home.treasury.gov/sites/default/files/interest-rates/yield.xml" 6 | DELTA_DIFFERENTIAL = 1.e-3 7 | VEGA_DIFFERENTIAL = 1.e-4 8 | GAMMA_DIFFERENTIAL = 1.e-3 9 | RHO_DIFFERENTIAL = 1.e-4 10 | THETA_DIFFERENTIAL = 1.e-5 11 | 12 | IMPLIED_VOLATILITY_TOLERANCE = 1.e-6 13 | SOLVER_STARTING_VALUE = 0.27 14 | 15 | OVERNIGHT_RATE = 0 16 | FALLBACK_RISK_FREE_RATE = 0.02 17 | -------------------------------------------------------------------------------- /wallstreet/wallstreet.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from yfinance.data import YfData 3 | 4 | from datetime import datetime, date, timedelta 5 | from time import mktime 6 | from io import StringIO 7 | 8 | from wallstreet.constants import DATE_FORMAT, DATETIME_FORMAT 9 | from wallstreet.blackandscholes import riskfree, BlackandScholes 10 | 11 | from functools import wraps 12 | from collections import defaultdict 13 | 14 | def parse(val): 15 | if val == '-': 16 | return 0 17 | elif val is None: 18 | return None 19 | 20 | if isinstance(val, str): 21 | val = val.replace(',', '') 22 | val = float(val) 23 | if val.is_integer(): 24 | return int(val) 25 | return val 26 | 27 | # send headers=headers on every session.get request to add a user agent to the header per https://stackoverflow.com/questions/10606133/sending-user-agent-using-requests-library-in-python 28 | def get_headers(agent='Mozilla/5.0'): 29 | headers = requests.utils.default_headers() 30 | headers.update( 31 | { 32 | 'User-Agent': agent, 33 | } 34 | ) 35 | 36 | return headers 37 | 38 | class ClassPropertyDescriptor: 39 | def __init__(self, f): 40 | self.f = f 41 | 42 | def __get__(self, obj, objtype): 43 | return self.f.__get__(obj, objtype)() 44 | 45 | 46 | def classproperty(func): 47 | if not isinstance(func, (classmethod, staticmethod)): 48 | func = classmethod(func) 49 | 50 | return ClassPropertyDescriptor(func) 51 | 52 | 53 | def strike_required(func): 54 | """ Decorator for methods that require the set_strike method to be used first """ 55 | 56 | @wraps(func) 57 | def deco(self, *args, **kwargs): 58 | if self.strike: 59 | self.update() 60 | return func(self, *args, **kwargs) 61 | else: 62 | raise AttributeError('Use set_strike() method first') 63 | return deco 64 | 65 | 66 | class YahooFinanceHistory: 67 | timeout = 5 68 | quote_link = 'https://query1.finance.yahoo.com/v7/finance/download/{quote}' 69 | 70 | def __init__(self, symbol, days_back=7, frequency='d'): 71 | self.symbol = symbol 72 | self.session = requests.Session() 73 | self.dt = timedelta(days=days_back) 74 | self.frequency = {'m': 'mo', 'w': 'wk', 'd': 'd'}[frequency] 75 | 76 | def get_quote(self): 77 | try: 78 | import pandas as pd 79 | except ImportError: 80 | raise ImportError('This functionality requires pandas to be installed') 81 | 82 | now = datetime.utcnow() 83 | dateto = int(now.timestamp()) 84 | datefrom = int((now - self.dt).timestamp()) 85 | url = self.quote_link.format(quote=self.symbol) 86 | params = {'period1': datefrom, 'period2': dateto, 'interval': f'1{self.frequency}', 'events': 'history', 'includeAdjustedClose': True} 87 | headers = get_headers() 88 | response = self.session.get(url, params=params, headers=headers, timeout=self.timeout) 89 | response.raise_for_status() 90 | return pd.read_csv(StringIO(response.text), parse_dates=['Date']) 91 | 92 | 93 | class Stock: 94 | _Y_API = 'https://query2.finance.yahoo.com/v7/finance/options/' 95 | 96 | def __init__(self, quote, exchange=None, source='yahoo'): 97 | quote = quote.upper() 98 | self._attempted_ticker = quote 99 | self._attempted_exchange = exchange 100 | self.session = requests.Session() 101 | self._yfdata = YfData(session=self.session) 102 | 103 | self.source = source.lower() 104 | self._yahoo(quote, exchange) 105 | 106 | def _yahoo(self, quote, exchange=None): 107 | """ Collects data from Yahoo Finance API """ 108 | 109 | query = quote + "." + exchange.upper() if exchange else quote 110 | 111 | url = __class__._Y_API + query 112 | r = self._yfdata.get(url) 113 | 114 | if r.status_code == 404: 115 | raise LookupError('Ticker symbol not found.') 116 | else: 117 | r.raise_for_status() 118 | 119 | jayson = r.json()['optionChain']['result'][0]['quote'] 120 | 121 | self.ticker = jayson['symbol'] 122 | self._price = jayson['regularMarketPrice'] 123 | self.currency = jayson['currency'] 124 | self.exchange = jayson['exchange'] 125 | self.change = jayson['regularMarketChange'] 126 | self.cp = jayson['regularMarketChangePercent'] 127 | self._last_trade = datetime.utcfromtimestamp(jayson['regularMarketTime']) 128 | self.name = jayson.get('longName', '') 129 | self.dy = jayson.get('trailingAnnualDividendYield', 0) 130 | 131 | def update(self): 132 | self.__init__(self._attempted_ticker, exchange=self._attempted_exchange, source=self.source) 133 | 134 | def __repr__(self): 135 | return 'Stock(ticker=%s, price=%s)' % (self.ticker, self.price) 136 | 137 | @property 138 | def price(self): 139 | self.update() 140 | return self._price 141 | 142 | @property 143 | def last_trade(self): 144 | if not self._last_trade: 145 | return None 146 | self.update() 147 | return self._last_trade.strftime(DATETIME_FORMAT) 148 | 149 | def historical(self, days_back=30, frequency='d'): 150 | return YahooFinanceHistory(symbol=self.ticker, days_back=days_back, frequency=frequency).get_quote() 151 | 152 | 153 | class Option: 154 | _Y_API = 'https://query2.finance.yahoo.com/v7/finance/options/' 155 | 156 | def __new__(cls, *args, **kwargs): 157 | instance = super().__new__(cls) 158 | instance._has_run = False # This is to prevent an infinite loop 159 | instance._skip_dates = defaultdict(set) # In case a date is listed as an expiration date but has only one type of options 160 | return instance 161 | 162 | def __init__(self, quote, opt_type, d=date.today().day, m=date.today().month, 163 | y=date.today().year, strict=False, source='yahoo'): 164 | 165 | self.source = source.lower() 166 | self.underlying = Stock(quote, source=self.source) 167 | 168 | self._yahoo(quote, d, m, y) 169 | 170 | self._exp = [exp for exp in self._exp if exp not in self._skip_dates[opt_type]] 171 | self.expirations = [exp.strftime(DATE_FORMAT) for exp in self._exp] 172 | self.expiration = date(y, m, d) 173 | 174 | try: 175 | if opt_type == 'Call': 176 | self.data = self.data['calls'] 177 | elif opt_type == 'Put': 178 | self.data = self.data['puts'] 179 | assert self.data 180 | 181 | except (KeyError, AssertionError): 182 | if self._expiration in self._exp: # Date is in expirations list but no data for it 183 | self._skip_dates[opt_type].add(self._expiration) 184 | self._exp.remove(self._expiration) 185 | self._has_run = False 186 | 187 | if all((d, m, y)) and not self._has_run and not strict: 188 | closest_date = min(self._exp, key=lambda x: abs(x - self._expiration)) 189 | print('No options listed for given date, using %s instead' % closest_date.strftime(DATE_FORMAT)) 190 | self._has_run = True 191 | self.__init__(quote, closest_date.day, closest_date.month, closest_date.year, source=source) 192 | else: 193 | raise ValueError('Possible expiration dates for this option are:', self.expirations) from None 194 | 195 | def _yahoo(self, quote, d, m, y): 196 | """ Collects data from Yahoo Finance API """ 197 | 198 | epoch = int(round(mktime(date(y, m, d).timetuple())/86400, 0)*86400) 199 | 200 | self._yfdata = self.underlying._yfdata 201 | 202 | r = self._yfdata.get(__class__._Y_API + quote + '?date=' + str(epoch)) 203 | 204 | if r.status_code == 404: 205 | raise LookupError('Ticker symbol not found.') 206 | else: 207 | r.raise_for_status() 208 | 209 | json = r.json() 210 | 211 | try: 212 | self.data = json['optionChain']['result'][0]['options'][0] 213 | except IndexError: 214 | raise LookupError('No options listed for this stock.') 215 | 216 | self._exp = [datetime.utcfromtimestamp(i).date() for i in json['optionChain']['result'][0]['expirationDates']] 217 | 218 | @classproperty 219 | def rate(cls): 220 | if not hasattr(cls, '_rate'): 221 | cls._rate = riskfree() 222 | 223 | return cls._rate 224 | 225 | @property 226 | def expiration(self): 227 | return self._expiration.strftime(DATE_FORMAT) 228 | 229 | @expiration.setter 230 | def expiration(self, val): 231 | self._expiration = val 232 | 233 | 234 | class Call(Option): 235 | Option_type = 'Call' 236 | 237 | def __init__(self, quote, d=date.today().day, m=date.today().month, 238 | y=date.today().year, strike=None, strict=False, source='yahoo'): 239 | 240 | quote = quote.upper() 241 | kw = {'d': d, 'm': m, 'y': y, 'strict': strict, 'source': source} 242 | super().__init__(quote, self.__class__.Option_type, **kw) 243 | 244 | self.T = (self._expiration - date.today()).days/365 245 | self.q = self.underlying.dy 246 | self.ticker = quote 247 | self.strike = None 248 | self.strikes = tuple(parse(dic['strike']) for dic in self.data 249 | if dic.get('p') != '-') 250 | if strike: 251 | if strike in self.strikes: 252 | self.set_strike(strike) 253 | else: 254 | if strict: 255 | raise LookupError('No options listed for given strike price.') 256 | else: 257 | closest_strike = min(self.strikes, key=lambda x: abs(x - strike)) 258 | print('No option for given strike, using %s instead' % closest_strike) 259 | self.set_strike(closest_strike) 260 | 261 | def set_strike(self, val): 262 | """ Specifies a strike price """ 263 | 264 | d = {} 265 | for dic in self.data: 266 | if parse(dic['strike']) == val and val in self.strikes: 267 | d = dic 268 | break 269 | if d: 270 | self._price = parse(d.get('p')) or d.get('lastPrice') 271 | self.id = d.get('cid') 272 | self.exchange = d.get('e') 273 | self._bid = parse(d.get('b')) or d.get('bid', 0) 274 | self._ask = parse(d.get('a')) or d.get('ask', 0) 275 | self.strike = parse(d['strike']) 276 | self._change = parse(d.get('c')) or d.get('change', 0) # change in currency 277 | self._cp = parse(d.get('cp', 0)) or d.get('percentChange', 0) # percentage change 278 | self._volume = parse(d.get('vol')) or d.get('volume', 0) 279 | self._open_interest = parse(d.get('oi')) or d.get('openInterest', 0) 280 | self.code = d.get('s') or d.get('contractSymbol') 281 | self.itm = ((self.__class__.Option_type == 'Call' and self.underlying.price > self.strike) or 282 | (self.__class__.Option_type == 'Put' and self.underlying.price < self.strike)) # in the money 283 | self.BandS = BlackandScholes( 284 | self.underlying.price, 285 | self.strike, 286 | self.T, 287 | self._price, 288 | self.rate(self.T), 289 | self.__class__.Option_type, 290 | self.q 291 | ) 292 | 293 | else: 294 | raise LookupError('No options listed for given strike price.') 295 | 296 | def __repr__(self): 297 | if self.strike: 298 | return self.__class__.Option_type + "(ticker=%s, expiration=%s, strike=%s)" % (self.ticker, self.expiration, self.strike) 299 | else: 300 | return self.__class__.Option_type + "(ticker=%s, expiration=%s)" % (self.ticker, self.expiration) 301 | 302 | def update(self): 303 | self.__init__(self.ticker, self._expiration.day, 304 | self._expiration.month, self._expiration.year, 305 | self.strike, source=self.source) 306 | 307 | @property 308 | @strike_required 309 | def bid(self): 310 | return self._bid 311 | 312 | @property 313 | @strike_required 314 | def ask(self): 315 | return self._ask 316 | 317 | @property 318 | @strike_required 319 | def price(self): 320 | return self._price 321 | 322 | @property 323 | @strike_required 324 | def change(self): 325 | return self._change 326 | 327 | @property 328 | @strike_required 329 | def cp(self): 330 | return self._cp 331 | 332 | @property 333 | @strike_required 334 | def open_interest(self): 335 | return self._open_interest 336 | 337 | @property 338 | @strike_required 339 | def volume(self): 340 | return self._volume 341 | 342 | @strike_required 343 | def implied_volatility(self): 344 | return self.BandS.impvol 345 | 346 | @strike_required 347 | def delta(self): 348 | return self.BandS.delta() 349 | 350 | @strike_required 351 | def gamma(self): 352 | return self.BandS.gamma() 353 | 354 | @strike_required 355 | def vega(self): 356 | return self.BandS.vega() 357 | 358 | @strike_required 359 | def rho(self): 360 | return self.BandS.rho() 361 | 362 | @strike_required 363 | def theta(self): 364 | return self.BandS.theta() 365 | 366 | 367 | class Put(Call): 368 | Option_type = 'Put' 369 | --------------------------------------------------------------------------------