diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 8cc8e95..dbdad8a 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -6,9 +6,15 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10"] - poetry-version: ["1.3.1"] - os: [ubuntu-latest] + include: + - os: ubuntu-latest + python-version: "3.10" + poetry-version: "1.3.1" + use-mypyc: true + - os: ubuntu-latest + python-version: "3.10" + poetry-version: "1.3.1" + use-mypyc: false runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 @@ -20,6 +26,9 @@ jobs: cache: "poetry" - name: Install dependencies run: poetry install --with dev + - if: ${{ matrix.use-mypyc }} + name: Build mypyc modules + run: poetry run python build.py --inplace - name: Run tests with coverage run: poetry run coverage run -m unittest - name: Show coverage report diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b4ee0b7..a5435cb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,14 +9,17 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10"] - poetry-version: ["1.3.1"] - os: [ubuntu-latest] + include: + - os: ubuntu-latest + python-version: "3.10" + poetry-version: "1.3.1" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install poetry - run: pipx install poetry==${{ matrix.poetry-version }} + run: | + pipx install poetry==${{ matrix.poetry-version }} + poetry self add poetry-plugin-ignore-build-script - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -25,5 +28,7 @@ jobs: env: PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} run: | + poetry build + poetry build ----ignore-build-script poetry config pypi-token.pypi $PYPI_TOKEN - poetry publish --build + poetry publish diff --git a/build.py b/build.py new file mode 100644 index 0000000..f096537 --- /dev/null +++ b/build.py @@ -0,0 +1,63 @@ +import sys + +mypyc_modules = [ + "yabte/backtest/__init__.py", + "yabte/backtest/_helpers.py", + "yabte/backtest/asset.py", + "yabte/backtest/book.py", + "yabte/backtest/order.py", + "yabte/backtest/strategy.py", + "yabte/backtest/transaction.py", +] + + +def build(setup_kwargs): + """Imported by poetry generated setup.py during build.""" + try: + from mypyc.build import mypycify + + setup_kwargs.update( + { + "ext_modules": mypycify(mypyc_modules), + } + ) + except: + pass + + +def build_inplace(): + """Build modules inplace for running tests.""" + import os + import subprocess + + # mypyc generates a setup.py and calls build_ext --inplace + env = os.environ.copy() + cmd = subprocess.run([sys.executable, "-m", "mypyc"] + mypyc_modules, env=env) + return cmd.returncode + + +def cleanup(): + from pathlib import Path + + this_dir = Path(__file__).parent + + # rm module shared objs + for mod in mypyc_modules: + fp = this_dir / mod + for gfp in fp.parent.glob(fp.with_suffix("").name + "*.so"): + gfp.unlink(missing_ok=True) + + for gfp in this_dir.glob("*__mypyc.*.so"): + gfp.unlink(missing_ok=True) + + return 0 + + +if __name__ == "__main__": + if len(sys.argv) == 2: + if "clean" in sys.argv[1]: + sys.exit(cleanup()) + elif "inplace" in sys.argv[1]: + sys.exit(build_inplace()) + + sys.exit("Unknown option, use '--clean' or '--inplace'") diff --git a/poetry.lock b/poetry.lock index 9ae5356..eee701d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -497,7 +497,6 @@ files = [ {file = "debugpy-1.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c"}, {file = "debugpy-1.6.6-cp38-cp38-win32.whl", hash = "sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32"}, {file = "debugpy-1.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225"}, - {file = "debugpy-1.6.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:11a0f3a106f69901e4a9a5683ce943a7a5605696024134b522aa1bfda25b5fec"}, {file = "debugpy-1.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6"}, {file = "debugpy-1.6.6-cp39-cp39-win32.whl", hash = "sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe"}, {file = "debugpy-1.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1"}, @@ -1075,46 +1074,42 @@ files = [ [[package]] name = "mypy" -version = "0.991" +version = "1.3.0" description = "Optional static typing for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, + {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, + {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, + {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, + {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, + {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, + {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, + {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, + {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, + {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, + {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, + {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, + {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, + {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, + {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, + {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, + {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, + {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, + {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" @@ -1128,7 +1123,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1329,6 +1324,21 @@ pytz = ">=2020.1" [package.extras] test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +[[package]] +name = "pandas-stubs" +version = "2.0.0.230412" +description = "Type annotations for pandas" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas_stubs-2.0.0.230412-py3-none-any.whl", hash = "sha256:311ab8b42ee574d9fea5061d1f63aeca297e472de6073ba84bf2a017c6cb1b6b"}, + {file = "pandas_stubs-2.0.0.230412.tar.gz", hash = "sha256:016f567cb9947edd0067ea2665ab00b77fa47e73a65ce1a097de4f499b3485c0"}, +] + +[package.dependencies] +types-pytz = ">=2022.1.1" + [[package]] name = "pandocfilters" version = "1.5.0" @@ -1411,6 +1421,13 @@ files = [ {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, + {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, + {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, + {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, + {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, + {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, + {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, @@ -2135,7 +2152,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2180,16 +2197,28 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +[[package]] +name = "types-pytz" +version = "2023.3.0.0" +description = "Typing stubs for pytz" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"}, + {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"}, +] + [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] @@ -2247,4 +2276,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "ac63e8a58abdd29345e32438b234c5ce7920a0e354ba7939e79daea2696c1b02" +content-hash = "2fba1356f4ba8e6000188d2998c64c5a0bbfbe5fd12f7aa7f269b9cdd35be7e2" diff --git a/pyproject.toml b/pyproject.toml index 0a88f39..94f1fd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,21 @@ license = "MIT" readme = "README.md" repository = "https://github.com/bsdz/yabte" +[tool.poetry.build] +script = "build.py" +generate-setup-file = true + [tool.poetry.dependencies] python = "^3.10,<3.12" pandas = "^1.5.2" scipy = "^1.10.0" +pandas-stubs = "^2.0.0.230412" +mypy = "^1.3.0" [tool.poetry.group.dev] optional = true [tool.poetry.group.dev.dependencies] -mypy = "^0.991" isort = "^5.11.4" black = {extras = ["jupyter"], version = "^23.1.0"} docformatter = "^1.5.1" @@ -42,5 +47,5 @@ nbconvert = "^7.2.9" profile = "black" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0", "setuptools==67.8.0", "mypy==1.3.0", "pandas-stubs==2.0.0.230412"] build-backend = "poetry.core.masonry.api" diff --git a/yabte/backtest/__init__.py b/yabte/backtest/__init__.py index c4d5cd9..9e6d92b 100644 --- a/yabte/backtest/__init__.py +++ b/yabte/backtest/__init__.py @@ -1,6 +1,8 @@ -from .asset import Asset, AssetName -from .book import Book, BookMandate, BookName -from .order import ( +# TODO: use absolute imports until mypyc fixes relative imports in __init__.py +# (https://github.com/mypyc/mypyc/issues/996) +from yabte.backtest.asset import Asset, AssetName +from yabte.backtest.book import Book, BookMandate, BookName +from yabte.backtest.order import ( BasketOrder, Order, OrderSizeType, @@ -9,8 +11,8 @@ PositionalOrder, PositionalOrderCheckType, ) -from .strategy import Strategy, StrategyRunner -from .transaction import CashTransaction, Trade +from yabte.backtest.strategy import Strategy, StrategyRunner +from yabte.backtest.transaction import CashTransaction, Trade __all__ = [ "Asset", diff --git a/yabte/backtest/asset.py b/yabte/backtest/asset.py index 74ef1f6..1df6250 100644 --- a/yabte/backtest/asset.py +++ b/yabte/backtest/asset.py @@ -1,16 +1,18 @@ from dataclasses import dataclass from decimal import Decimal -from typing import Sequence +from typing import Sequence, TypeAlias import pandas as pd +from mypy_extensions import mypyc_attr __all__ = ["Asset"] -class AssetName(str): - """Asset name string.""" +AssetName: TypeAlias = str +"""Asset name string.""" +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class AssetBase: """Anything that has a price.""" @@ -60,6 +62,7 @@ def check_and_fix_data(self, data: pd.DataFrame) -> pd.DataFrame: raise NotImplementedError("The apply methods needs to be implemented.") +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class Asset(AssetBase): """Assets whose price history is represented by High, Low, Open, Close and diff --git a/yabte/backtest/book.py b/yabte/backtest/book.py index cdf386c..c0784de 100644 --- a/yabte/backtest/book.py +++ b/yabte/backtest/book.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from decimal import Decimal from itertools import groupby -from typing import Any, Dict, List, Sequence +from typing import Any, Dict, List, Sequence, TypeAlias import pandas as pd @@ -23,8 +23,8 @@ def check(self, current_pos, quantity) -> bool: raise NotImplementedError() -class BookName(str): - """Book name string.""" +BookName: TypeAlias = str +"""Book name string.""" @dataclass(kw_only=True) diff --git a/yabte/backtest/order.py b/yabte/backtest/order.py index 7154bea..baf7921 100644 --- a/yabte/backtest/order.py +++ b/yabte/backtest/order.py @@ -4,9 +4,10 @@ from dataclasses import dataclass, field from decimal import Decimal from enum import Enum -from typing import Callable, Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple import pandas as pd +from mypy_extensions import mypyc_attr from ._helpers import ensure_decimal, ensure_enum from .asset import Asset, AssetName @@ -50,6 +51,7 @@ class OrderSizeType(Enum): """Size is a percentage of book value.""" +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class OrderBase: """Base class for all orders.""" @@ -105,6 +107,7 @@ def apply( raise NotImplementedError("The apply methods needs to be implemented.") +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class Order(OrderBase): """Simple market order.""" @@ -187,6 +190,7 @@ class PositionalOrderCheckType(Enum): ZERO_POS = 2 +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class PositionalOrder(Order): """Ensures current position is `size` and will close out existing positions @@ -248,6 +252,7 @@ def apply( self._book_trades(trades) +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass class BasketOrder(OrderBase): """Combine multiple assets into a single order.""" @@ -331,6 +336,7 @@ def apply( self._book_trades(trades) +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class PositionalBasketOrder(BasketOrder): """Similar to a :py:class:`BasketOrder` but will close out existing diff --git a/yabte/backtest/strategy.py b/yabte/backtest/strategy.py index f03f24d..5f40033 100644 --- a/yabte/backtest/strategy.py +++ b/yabte/backtest/strategy.py @@ -3,51 +3,74 @@ from copy import deepcopy from dataclasses import dataclass, field from itertools import chain, product -from typing import Any, Dict, List, Optional, Type +from typing import Any, Dict, Iterable, List, Optional, Type -import numpy as np import pandas as pd +from mypy_extensions import mypyc_attr + +# TODO: use explicit imports until mypyc fixes attribute lookups in dataclass +# (https://github.com/mypyc/mypyc/issues/1000) +from pandas import DataFrame, Series, Timestamp # type: ignore from .asset import Asset, AssetName from .book import Book, BookMandate, BookName -from .order import Order, OrderStatus +from .order import Order, OrderBase, OrderStatus logger = logging.getLogger(__name__) __all__ = ["Strategy", "StrategyRunner"] -class Orders(deque): +class Orders: + def __init__(self): + self.deque = deque() + + def __len__(self): + return len(self.deque) + + def __iter__(self): + return iter(self.deque) + + def popleft(self): + return self.deque.popleft() + + def append(self, order: OrderBase): + return self.deque.append(order) + + def extend(self, orders: Iterable[OrderBase]): + return self.deque.extend(orders) + def sort_by_priority(self): """Sorts orders by order priority.""" - ou_sorted = sorted(self, key=lambda o: o.priority, reverse=True) - self.clear() - self.extend(ou_sorted) + ou_sorted = sorted(self.deque, key=lambda o: o.priority, reverse=True) + self.deque.clear() + self.deque.extend(ou_sorted) - def remove_duplicate_keys(self) -> List[Order]: + def remove_duplicate_keys(self) -> List[OrderBase]: """Remove older orders with same key. Returns a list of orders than were removed with status set to REPLACED. """ removed = [] - cntr = Counter(o.key for o in self if o.key is not None) + cntr = Counter(o.key for o in self.deque if o.key is not None) if any(v > 1 for v in cntr.values()): kept = [] - while self: - o = self.popleft() + while self.deque: + o = self.deque.popleft() if o.key in cntr and cntr[o.key] > 1: o.status = OrderStatus.REPLACED removed.append(o) cntr[o.key] -= 1 else: kept.append(o) - self.clear() - self.extend(kept) + self.deque.clear() + self.deque.extend(kept) return removed +@mypyc_attr(allow_interpreted_subclasses=True) @dataclass(kw_only=True) class Strategy: """Trading strategy base class. @@ -68,16 +91,16 @@ class Strategy: assets: Dict[AssetName, Asset] """Dictionary of assets.""" - _ts = None - _data_lock = True - _mask_open = False + _ts: pd.Timestamp | None = None + _data_lock: bool = True + _mask_open: bool = False @property - def ts(self): + def ts(self) -> pd.Timestamp | None: """Stores the current timestamp.""" return self._ts - def _set_ts(self, ts): + def _set_ts(self, ts: pd.Timestamp): """Internal method to update timestep to current `ts`""" self._ts = ts @@ -103,7 +126,7 @@ def data(self) -> pd.DataFrame: if not self.ts: return self._data else: - df_t = self._data.loc[: self.ts, :] + df_t = self._data.loc[: self.ts, :] # type: ignore[misc] if not self._mask_open: data = df_t else: diff --git a/yabte/backtest/transaction.py b/yabte/backtest/transaction.py index 00da07a..478b176 100644 --- a/yabte/backtest/transaction.py +++ b/yabte/backtest/transaction.py @@ -4,6 +4,10 @@ import pandas as pd +# TODO: use explicit imports until mypyc fixes attribute lookups in dataclass +# (https://github.com/mypyc/mypyc/issues/1000) +from pandas import Timestamp # type: ignore + from .asset import AssetName logger = logging.getLogger(__name__)