stefan-jansen / zipline-reloaded

Zipline, a Pythonic Algorithmic Trading Library
https://zipline.ml4trading.io
Apache License 2.0
1.12k stars 208 forks source link

Limit orders bug #19

Closed ydm closed 3 years ago

ydm commented 3 years ago

Dear Zipline Maintainers,

Before I tell you about my issue, let me describe my environment:

Environment

* Operating System: Linux rocket 5.10.16-arch1-1 #1 SMP PREEMPT Sat, 13 Feb 2021 20:50:18 +0000 x86_64 GNU/Linux * Python Version: Python 3.9.4 * Python Bitness: 64 * How did you install Zipline: git clone && pip install . * Python packages: ``` absl-py @ file:///tmp/build/80754af9/absl-py_1615410667905/work aiohttp @ file:///tmp/build/80754af9/aiohttp_1614360979875/work alabaster==0.7.12 alembic==1.6.2 alphalens-reloaded==0.4.1.post1 anyio @ file:///tmp/build/80754af9/anyio_1617783277988/work/dist argon2-cffi @ file:///tmp/build/80754af9/argon2-cffi_1613037499734/work astunparse==1.6.3 async-generator @ file:///home/ktietz/src/ci/async_generator_1611927993394/work async-timeout==3.0.1 attrs @ file:///tmp/build/80754af9/attrs_1604765588209/work awswrangler==2.4.0 Babel==2.9.0 backcall @ file:///home/ktietz/src/ci/backcall_1611930011877/work bcolz-zipline==1.2.3.post2 beautifulsoup4==4.9.3 bleach @ file:///tmp/build/80754af9/bleach_1612211392645/work blinker==1.4 boto3==1.17.8 botocore==1.20.8 Bottleneck==1.3.2 brotlipy==0.7.0 cachetools @ file:///tmp/build/80754af9/cachetools_1619597386817/work certifi==2020.12.5 cffi @ file:///tmp/build/80754af9/cffi_1613246951236/work chardet==4.0.0 click==8.0.0 configparser==5.0.1 coverage @ file:///tmp/build/80754af9/coverage_1614614861224/work cryptography @ file:///tmp/build/80754af9/cryptography_1616767007030/work cycler==0.10.0 Cython @ file:///tmp/build/80754af9/cython_1618435158654/work dateparser==1.0.0 decorator==4.4.2 defusedxml @ file:///tmp/build/80754af9/defusedxml_1615228127516/work docker-pycreds==0.4.0 docutils==0.16 empyrical-reloaded==0.5.7 entrypoints==0.3 et-xmlfile==1.0.1 flatbuffers @ file:///tmp/build/80754af9/python-flatbuffers_1614345733764/work gast @ file:///tmp/build/80754af9/gast_1597433534803/work gitdb==4.0.5 GitPython==3.1.13 google-auth @ file:///tmp/build/80754af9/google-auth_1619477128713/work google-auth-oauthlib @ file:///tmp/build/80754af9/google-auth-oauthlib_1617120569401/work google-pasta==0.2.0 greenlet==1.1.0 grpcio @ file:///tmp/build/80754af9/grpcio_1614884176703/work h5py==3.2.1 idna==2.10 imagesize==1.2.0 importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1617877307939/work intervaltree==3.1.0 ipykernel @ file:///tmp/build/80754af9/ipykernel_1607452791405/work/dist/ipykernel-5.3.4-py3-none-any.whl ipython @ file:///tmp/build/80754af9/ipython_1617120888991/work ipython-genutils @ file:///tmp/build/80754af9/ipython_genutils_1606773439826/work iso3166==1.0.1 iso4217==1.6.20180829 jdcal==1.4.1 jedi @ file:///tmp/build/80754af9/jedi_1606932531272/work Jinja2==2.11.3 jmespath==0.10.0 joblib==1.0.1 json5==0.9.5 jsonschema @ file:///tmp/build/80754af9/jsonschema_1602607155483/work jupyter-client @ file:///tmp/build/80754af9/jupyter_client_1616770841739/work jupyter-core @ file:///tmp/build/80754af9/jupyter_core_1612213314396/work jupyter-packaging @ file:///tmp/build/80754af9/jupyter-packaging_1613502826984/work jupyter-server @ file:///tmp/build/80754af9/jupyter_server_1616084066671/work jupyterlab @ file:///tmp/build/80754af9/jupyterlab_1619133235951/work jupyterlab-pygments @ file:///tmp/build/80754af9/jupyterlab_pygments_1601490720602/work jupyterlab-server @ file:///tmp/build/80754af9/jupyterlab_server_1617134334258/work Keras-Preprocessing @ file:///tmp/build/80754af9/keras-preprocessing_1612283640596/work kiwisolver==1.3.1 lazy-object-proxy==1.4.3 llvmlite==0.36.0 Logbook==1.5.3 lru-dict==1.1.7 lxml==4.6.3 Mako==1.1.4 Markdown @ file:///tmp/build/80754af9/markdown_1614363852612/work MarkupSafe==2.0.0 matplotlib==3.3.4 mccabe==0.6.1 mistune @ file:///tmp/build/80754af9/mistune_1607364877025/work mkl-fft==1.3.0 mkl-random @ file:///tmp/build/80754af9/mkl_random_1618853971975/work mkl-service==2.3.0 multidict @ file:///tmp/build/80754af9/multidict_1607367781142/work multipledispatch==0.6.0 multitasking==0.0.9 nbclassic @ file:///tmp/build/80754af9/nbclassic_1616085367084/work nbclient @ file:///tmp/build/80754af9/nbclient_1614364831625/work nbconvert @ file:///tmp/build/80754af9/nbconvert_1607370433589/work nbformat @ file:///tmp/build/80754af9/nbformat_1617383369282/work nest-asyncio @ file:///tmp/build/80754af9/nest-asyncio_1613680548246/work networkx==2.5.1 notebook @ file:///tmp/build/80754af9/notebook_1616443118287/work numba @ file:///tmp/build/80754af9/numba_1616774250644/work numexpr==2.7.3 numpy==1.20.3 oauthlib==3.1.0 openpyxl==3.0.6 opt-einsum==3.1.0 osqp==0.6.2.post0 packaging @ file:///tmp/build/80754af9/packaging_1611952188834/work pandas==1.2.4 pandas-datareader==0.9.0 pandocfilters @ file:///tmp/build/80754af9/pandocfilters_1605120906940/work parso==0.7.0 pathtools==0.1.2 patsy==0.5.1 pexpect @ file:///tmp/build/80754af9/pexpect_1605563209008/work pg8000==1.17.0 pickleshare @ file:///tmp/build/80754af9/pickleshare_1606932040724/work Pillow==8.1.0 plotly==4.14.3 prometheus-client @ file:///tmp/build/80754af9/prometheus_client_1618088486455/work promise==2.3 prompt-toolkit @ file:///tmp/build/80754af9/prompt-toolkit_1616415428029/work protobuf==3.14.0 psutil==5.8.0 ptyprocess @ file:///tmp/build/80754af9/ptyprocess_1609355006118/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl pyarrow==3.0.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser @ file:///tmp/build/80754af9/pycparser_1594388511720/work pyfinance==1.3.0 Pygments==2.8.0 PyJWT @ file:///tmp/build/80754af9/pyjwt_1619682484438/work PyMySQL==1.0.2 pyOpenSSL @ file:///tmp/build/80754af9/pyopenssl_1608057966937/work pyparsing @ file:///home/linux1/recipes/ci/pyparsing_1610983426697/work pyrsistent @ file:///tmp/build/80754af9/pyrsistent_1607365177477/work PySocks @ file:///tmp/build/80754af9/pysocks_1605305812635/work python-binance==1.0.7 python-dateutil @ file:///home/ktietz/src/ci/python-dateutil_1611928101742/work python-editor==1.0.4 python-interface==1.6.1 pytz @ file:///tmp/build/80754af9/pytz_1612215392582/work PyYAML==5.4.1 pyzmq==20.0.0 qdldl==0.1.5.post0 redshift-connector==2.0.874 regex==2021.4.4 requests==2.25.1 requests-oauthlib==1.3.0 rsa @ file:///tmp/build/80754af9/rsa_1614366226499/work s3transfer==0.3.4 scikit-learn==0.24.1 scipy==1.6.3 scramp==1.2.0 scs==2.1.2 seaborn==0.11.1 Send2Trash @ file:///tmp/build/80754af9/send2trash_1607525499227/work sentry-sdk==0.20.2 shortuuid==1.0.1 six==1.16.0 sklearn==0.0 smmap==3.0.5 sniffio @ file:///tmp/build/80754af9/sniffio_1614030464178/work snowballstemmer==2.1.0 sortedcontainers==2.3.0 soupsieve==2.2 Sphinx==3.5.1 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.4 SQLAlchemy==1.4.15 statsmodels==0.12.2 subprocess32==3.5.4 TA-Lib==0.4.19 tables==3.6.1 tensorboard @ file:///home/builder/ktietz/aggregate/tensorflow_recipes/ci_te/tensorboard_1614593728657/work/tmp_pip_dir tensorboard-plugin-wit==1.6.0 tensorflow @ file:///home/builder/ktietz/aggregate/tensorflow_recipes/ci_cpu/tensorflow-base_1614360980945/work/tensorflow_pkg/tensorflow-2.4.1-cp39-cp39-linux_x86_64.whl tensorflow-estimator @ file:///home/builder/ktietz/aggregate/tensorflow_recipes/ci_te/tensorflow-estimator_1614592767119/work/whl_temp/tensorflow_estimator-2.4.0-py2.py3-none-any.whl termcolor==1.1.0 terminado==0.9.4 testpath @ file:///home/ktietz/src/ci/testpath_1611930608132/work threadpoolctl==2.1.0 toolz==0.11.1 tornado @ file:///tmp/build/80754af9/tornado_1606942317143/work trading-calendars==2.1.1 traitlets @ file:///home/ktietz/src/ci/traitlets_1611929699868/work typing-extensions @ file:///tmp/build/80754af9/typing_extensions_1611751222202/work tzlocal==2.1 ujson==4.0.2 urllib3==1.26.4 wandb==0.10.19 wcwidth @ file:///tmp/build/80754af9/wcwidth_1593447189090/work webencodings==0.5.1 websockets==9.0.1 Werkzeug @ file:///home/ktietz/src/ci/werkzeug_1611932622770/work wrapt==1.12.1 xlrd==2.0.1 xmltodict==0.12.0 yarl @ file:///tmp/build/80754af9/yarl_1606939947528/work yfinance==0.1.59 zipline-reloaded==2.0.0.post1 zipp @ file:///tmp/build/80754af9/zipp_1615904174917/work ```

Now that you know a little about me, let me tell you about the issue I am having:

Description of Issue

I expect a limit order to respect the specified ---capital-base and the order_target(target=X) target setting.

On a custom test with BTC data and 24/7 market it's totally broken. In a simpler test with AAPL, it buys 13 shares at first.

Here is how you can reproduce this issue on your machine:

https://exchange.ml4trading.io/uploads/default/original/1X/4cb658a17618f5c4f373a0422443ce9f8d1f7a05.png

Reproduction Steps

  1. Sync the quandl bundle
  2. Copy the code from the image given above
  3. Run ...

What steps have you taken to resolve this already?

I pray to the universe for help.

...

Anything else?

...

Sincerely, ydm

MBounouar commented 3 years ago

This bug also exists in 1.4.1 and 1.4.0.

After some investigation the issue is that for daily simulations orders don't get cancelled. Currently the EODCancel is only implemented for minutely simulations.

In summary, what is happening is that the orders keep getting recorded until the limit_order is satisfied then it fills all the orders. The following day the order_target sells back to fill the target.

Unfortunately, due to the zipline implementation logic, I don't think there is a clean and easy fix to this daily transactions issue

stefan-jansen commented 3 years ago

See PR 20 that, thanks to @MBounouar, should help. Please feel free to reopen if he issues persists regardless.

ydm commented 3 years ago

Well, it fixes it for daily frequency, but not for minute, since EODCancel and CancelPolicy in general can only be called on SESSION_END (gens/tradesimulation.py:~240). Another problem is self.clock does never fire MINUTE_END, otherwise a fix would be a piece of cake.

It can be fixed in the userspace by placing an order only if there isn't already an open order for the same asset:

def limit_order(asset, amount, limit):
    exists = False
    for order in api.get_open_orders().get(asset, []):
        if np.allclose(order['amount'], amount) and np.allclose(order['limit'], limit):
            exists = True
    if not exists:
        api.order_target(asset, amount, limit)
MBounouar commented 3 years ago

I would'nt mind trying to implement a better solution, if you put together a few examples of what you expect. Otherwise, I do agree that the default order placing logic is heavy handed and its up to the user to be careful and implement his logic.

ydm commented 3 years ago

@MBounouar , thanks for paying attention to that! I'll do my best to describe the problem. Perhaps in another issue, I'll leave a link here in a sec.

ydm commented 3 years ago

Tested against: 9e57abd37f0749a0f152fc3092b162f36370ab4b https://github.com/ydm/zipline-limit-order-bug/blob/main/limit_order_bug.ipynb