Compare commits

..
Author SHA1 Message Date
Avishai WeingartenandGitHub 19cdbcaa85 Revert "Allow jupyter to run as root inside Docker image" 2018-01-30 17:25:26 +02:00
Avishai WeingartenandGitHub c3662443e4 Merge pull request #180 from gthouret/jupyter-allow-root-docker
Allow jupyter to run as root inside Docker image
2018-01-30 17:22:47 +02:00
Frederic Fortier 911fb6e934 Merge branch 'gthouret-echo-usage-for-jupyter' 2018-01-24 00:33:52 -05:00
Guy Thouret afa2af3014 Add sys.stdout as second paramter to click.echo calls to prevent 'not writable' when running catalyst from Jupyter
Related to #179

Signed-off-by: Guy Thouret <guy@thouret.uk>
2018-01-22 12:54:48 +00:00
Guy Thouret 55521b87b9 Allow jupyter to run as root inside Docker image
Signed-off-by: Guy Thouret <guy@thouret.uk>
2018-01-22 11:11:37 +00:00
Frederic Fortier 45978ab193 Merge branch 'develop' 2018-01-19 18:18:15 -05:00
Frederic Fortier 575ac4a36d BLD: updated release notes for release 0.4.7 2018-01-19 18:17:07 -05:00
Frederic Fortier db07ac0abb Merge remote-tracking branch 'origin/develop' into develop 2018-01-19 18:13:03 -05:00
Frederic Fortier a754497d65 BLD: completed implementation of issue #60, authentication aliases 2018-01-19 18:12:54 -05:00
Victor Grau Serrat d669419d18 DOC: updating references to catalyst 2018-01-19 14:55:40 -07:00
Frederic Fortier 7d3c53dbef Merge branch 'develop' 2018-01-18 23:12:34 -05:00
Frederic Fortier 04a1513e73 DOC: updated CCXT dependency 2018-01-18 23:09:27 -05:00
Frederic Fortier 03d2c0e306 DOC: updated release notes for 0.4.6 2018-01-18 23:09:04 -05:00
Frederic Fortier 853707dfb2 BLD: updated CCXT and temporarily fixed symbol mapping issue 2018-01-18 21:57:11 -05:00
Frederic Fortier 540dd97dbf BLD: fixed benchmark loader 2018-01-18 19:36:54 -05:00
Frederic Fortier 9f372828b4 BLD: fixed benchmark loader 2018-01-18 19:30:16 -05:00
Frederic Fortier 6a929b6e25 BLD: updating example algos for testing 2018-01-18 19:26:33 -05:00
Frederic Fortier 45f278ab15 Merge remote-tracking branch 'origin/develop' into develop 2018-01-18 19:26:14 -05:00
Frederic Fortier a58fa21234 BLD: fixed benchmark loader 2018-01-18 19:26:07 -05:00
Victor Grau Serrat c8d6e07179 DOC: added update instructions (#156) 2018-01-18 17:02:21 -07:00
Frederic Fortier fdfc3f2ec3 Merge remote-tracking branch 'origin/develop' into develop 2018-01-18 18:58:21 -05:00
Frederic Fortier 3a321eb195 BLD: housekeeping and adjustments 2018-01-18 18:58:14 -05:00
Victor Grau Serrat d21eb3b946 DOC: improved documentation of paper trading mode (#168) 2018-01-18 16:29:09 -07:00
Frederic Fortier 439b5404ae BLD: cleanup and minor adjustments to get_candles() in live mode 2018-01-18 18:06:10 -05:00
Frederic Fortier 821f60897f BUG: troubleshooting issue #169 2018-01-18 18:05:14 -05:00
Frederic Fortier 5ff935723f Merge remote-tracking branch 'origin/develop' into develop 2018-01-18 17:09:43 -05:00
Frederic Fortier 51126fd7ae BLD: improved the bundle test suite and related adjustments 2018-01-18 17:09:37 -05:00
Victor Grau Serrat fec6a159e6 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2018-01-18 13:10:12 -07:00
Victor Grau Serrat eee9a07f54 DOC: updated timeframe of upcoming features 2018-01-18 13:10:06 -07:00
Frederic Fortier 563fc433d5 BUG: fixed issue with number of superfluous candles at the beginning of bundle history 2018-01-18 15:08:12 -05:00
Frederic Fortier 53a54fde7c Merge remote-tracking branch 'origin/develop' into develop 2018-01-18 14:25:07 -05:00
Frederic Fortier b0f2202b54 BUG: fixed bundle test suite 2018-01-18 14:08:11 -05:00
Victor Grau Serrat cf6c3bb76b BUG: switched benchmark from Poloniex to Bitfinex (#161) 2018-01-18 11:05:21 -07:00
Frederic Fortier 772640e098 BUG: fixed an issue with balancing transactions 2018-01-18 00:05:56 -05:00
Victor Grau Serrat 52d4ced37c BLD: live mode accepts end parameter, when algo finishes 2018-01-16 23:40:18 -07:00
Victor Grau Serrat 3ed44f72ad BLD: preservation of context.state dict between runs 2018-01-16 23:00:53 -07:00
Frederic Fortier 7569f7eb7c BUG: fixed issue #120 with currency substitution 2018-01-15 23:19:18 -05:00
Frederic Fortier 6d28e289c4 Merge remote-tracking branch 'origin/develop' into develop 2018-01-15 23:03:11 -05:00
Frederic Fortier 22154f2337 DOC: code documentation 2018-01-15 23:03:01 -05:00
Victor Grau Serrat 5314d3e1f8 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2018-01-13 06:17:02 -07:00
Victor Grau Serrat bb16975400 DOC: typo in link 2018-01-13 06:16:40 -07:00
Frederic Fortier 0ce624e6f6 BUG: removing computation of partial order for now to avoid a calculation issue 2018-01-12 22:00:10 -05:00
Frederic Fortier 50dc322230 BLD: minor adjustments and updated release notes 2018-01-12 19:53:26 -05:00
Frederic Fortier fb36435231 BLD: added subfolder to stats exports 2018-01-12 19:52:53 -05:00
Frederic Fortier e688783931 BLD: added auth alias to support more than one api token per exchange 2018-01-12 19:35:32 -05:00
Frederic Fortier 2f3dbeedcd Merge branch 'bfeeser-bfeeser_handle_ticker_errors' into develop 2018-01-12 18:08:45 -05:00
Frederic Fortier cebb1cd6d4 Merge branch 'bfeeser_handle_ticker_errors' of https://github.com/bfeeser/catalyst into bfeeser-bfeeser_handle_ticker_errors 2018-01-12 18:08:30 -05:00
Frederic Fortier 020ec50258 BUG: for issue #159, improved frequency validation in live mode 2018-01-12 17:39:49 -05:00
Ben Feeser df42cc9047 BUG: handle errors more gracefully when fetching tickers 2018-01-12 16:58:39 -05:00
Frederic Fortier 8a9b4e2df7 DOC: updated release notes for next release 2018-01-12 16:56:51 -05:00
Frederic Fortier b9150aab79 BUG: fixed issue with low order amount after adjustment 2018-01-12 16:46:57 -05:00
Ben Feeser d4efed0d3a DEV: add .python-version to .gitignore for pyenv 2018-01-12 16:45:20 -05:00
Frederic Fortier db1ad9aac8 BLD: For issue #151, significantly improved the way in which we are processing order for exchanges supporting "fetch-my-trades" 2018-01-12 16:36:23 -05:00
Frederic Fortier 270c261203 Merge remote-tracking branch 'origin/develop' into develop 2018-01-12 16:33:07 -05:00
Frederic Fortier 3f974b1adf DOC: For issue #151, significantly improved the way in which we are processing order for exchanges supporting "fetch-my-trades" 2018-01-12 16:33:00 -05:00
VictorandGitHub 9eac88344d Merge pull request #148 from treethought/tutorial-fixes
DOC: fix import and support python3 for beginner tutorial
2018-01-12 14:03:03 -07:00
VictorandGitHub 5d1f00bd19 Merge branch 'develop' into tutorial-fixes 2018-01-12 14:02:49 -07:00
VictorandGitHub 8d3f3ba81d Merge branch 'develop' into tutorial-fixes 2018-01-12 14:01:47 -07:00
VictorandGitHub 248299a725 Merge pull request #141 from danim7/master
DOC: wrong year: 2017 --> 2018 ??
2018-01-12 13:56:41 -07:00
VictorandGitHub e916283522 Merge pull request #155 from treethought/virtualenv-install
DOC: present virtualenv info before pip install command
2018-01-12 13:49:18 -07:00
Victor Grau Serrat b7a32656d5 DOC: improved doc of matplotlib error after installation 2018-01-12 12:15:04 -07:00
VictorandGitHub 78eb5d9d64 Update requirements_docs.txt 2018-01-12 09:00:44 -07:00
Victor Grau Serrat 14c7170159 DOC: sphinx/docutils issue when building docs 2018-01-12 08:57:00 -07:00
Victor Grau Serrat 415fceb11c DOC: typo - #158 2018-01-12 08:32:28 -07:00
Frederic Fortier 05c8957c90 BUG: fixed issue with history of multiple assets 2018-01-12 00:48:53 -05:00
Frederic Fortier e4bacb169e Merge branch 'treethought-python3-compatibility' into develop 2018-01-12 00:42:31 -05:00
Frederic Fortier 608dd843d6 Merge branch 'python3-compatibility' of https://github.com/treethought/catalyst into treethought-python3-compatibility 2018-01-12 00:42:19 -05:00
Cam Sweeney e0827fe4ad DOC: present virtualenv info before pip install command
Creating a virtualenv is now explained before the command for install
via pip. Following the instructions in the current state may cause
users to install using the system python, negating the point of the
virtualenv
2018-01-11 12:42:13 -08:00
Cam Sweeney 050fda1bdb MAINT: Convert dictionary .values() to list for python3 2018-01-11 10:36:44 -08:00
Frederic Fortier 713d487808 BUG: troubleshooting and minor fixes 2018-01-10 23:25:27 -05:00
Frederic Fortier ff7d7c5256 Merge branch 'develop' 2018-01-10 13:33:35 -05:00
Frederic Fortier b60b50e99a BUG: fixed python3 issue in run_algo 2018-01-10 13:31:54 -05:00
Cam Sweeney 93ebbf8b1f DOC: fix import and support python3 for beginner tutorial
The beginner tutorial Dual Moving Average example attempted to import
extract_transactions from the wrong location.

The tutorial and corresponding example also would fail using python3
due to indexing the view object returned via context.exchange.values()
2018-01-09 18:25:59 -08:00
Frederic Fortier db37c9c6a7 BLD: updated sample algo 2018-01-09 16:57:57 -05:00
Frederic Fortier a49cb55821 BUG: fixed issue #147 related to python 3 compatibility 2018-01-09 11:32:44 -05:00
Frederic Fortier ac15413af8 DOC: updated release notes for version 0.4.4 2018-01-09 01:09:23 -05:00
Frederic Fortier 141ee65c91 BLD: working on unit tests. 2018-01-09 01:08:57 -05:00
Frederic Fortier 55d1fee82d DOC: added an alpha warning message in response to issue #146 2018-01-08 17:01:58 -05:00
Frederic Fortier 33f94b3ef9 BLD: for issue #144, skipped cash verification when there are open orders. 2018-01-07 02:32:12 -05:00
Frederic Fortier 30448a65c5 BLD: Housekeeping 2018-01-06 19:58:11 -05:00
Frederic Fortier 9c33ee123c BLD: Housekeeping 2018-01-06 19:51:41 -05:00
Frederic Fortier d88710501f DOC: updated release notes in preparation for version 0.4.4. 2018-01-06 19:50:58 -05:00
Frederic Fortier ba0208910f BUG: Fixed issue #142 by removing unecessary capital_base check since we are now validating positions and cash against the exchange 2018-01-06 19:37:53 -05:00
Frederic Fortier 5d9708901d BUG: fixed issue #111 related to positions update after restoring algo state 2018-01-06 19:08:47 -05:00
danim7andGitHub 96b36c6614 DOC: wrong year: 2017 --> 2018 2018-01-06 23:55:07 +01:00
Frederic Fortier 13de3e69ef BLD: refined CCXT error handlers 2018-01-05 22:22:04 -05:00
Frederic Fortier 215de33c35 BUG: Fixed issue with updating positions after restoring the state of an algo 2018-01-05 02:46:11 -05:00
Frederic Fortier 790ac22f8d Merge branch 'develop' 2018-01-05 00:16:26 -05:00
Frederic Fortier 4d8d1e33d0 DOC: updated release notes 2018-01-05 00:15:19 -05:00
Frederic Fortier 595bd82234 BLD: improving unit tests 2018-01-05 00:13:18 -05:00
Frederic Fortier 9515d10cef DOC: updated release notes 2018-01-05 00:11:04 -05:00
Frederic Fortier 56481bbbe0 BUG: rolled back run_algo functions splitting to quickly resolve issue #137. We'll merge it more carefully in the next release. 2018-01-05 00:09:51 -05:00
Frederic Fortier 79e4854973 BUG: fixed potential issue with refreshing the stats in live mode 2018-01-04 22:46:21 -05:00
Frederic Fortier b4e7629e8b BLD: upgraded CCXT 2018-01-04 21:07:15 -05:00
Frederic Fortier a818d10d40 BLD: improving unit tests 2018-01-04 21:06:40 -05:00
Frederic Fortier bd4b0d2756 DOC: Fixed old release header 2018-01-04 02:46:10 -05:00
Frederic Fortier 80e29b2aa4 Merge branch 'develop' 2018-01-04 02:15:35 -05:00
Frederic Fortier 54ebfd6aad DOC: updating release notes with new version number 2018-01-04 02:15:05 -05:00
Frederic Fortier 606e2d5278 Merge branch 'develop' 2018-01-04 02:05:24 -05:00
Frederic Fortier 194b96f5c1 BLD: fixed the log granularity 2018-01-04 02:04:36 -05:00
Frederic Fortier 91dcd8d83b BLD: fixed the log granularity 2018-01-04 02:04:13 -05:00
Frederic Fortier 0396dee74d Merge branch 'develop' 2018-01-04 01:55:39 -05:00
Frederic Fortier 3045c5108a BLD: backing out CCXT update, not enough time to test properly 2018-01-04 01:44:25 -05:00
Frederic Fortier 9bb12eb781 BLD: adjusting sample algorithmns for validation 2018-01-03 22:59:07 -05:00
Frederic Fortier d64fe12751 DOC: 0.4.1 release notes 2018-01-03 22:58:40 -05:00
Frederic Fortier f5503ae717 BLD: upgraded CCXT 2018-01-03 22:58:19 -05:00
Frederic Fortier 4c2a727cd7 BLD: adjusted the algo state data in live mode 2018-01-03 21:49:56 -05:00
Frederic Fortier 0605fca115 BLD: tested issue #111 2018-01-03 20:20:19 -05:00
Frederic Fortier a60311b002 BUG: minor fixes from unit testing 2018-01-03 20:18:53 -05:00
Frederic Fortier bd2ee45664 BUG: created an empyrical patch for issue #126 2018-01-03 17:56:52 -05:00
Frederic Fortier 89f7060a80 BUG: created an empyrical patch for issue #126 2018-01-03 17:01:23 -05:00
Frederic Fortier 15d0337b34 BUG: fixed issue #133 with updating performance stats before handle_date after recent orders 2018-01-02 20:55:05 -05:00
Frederic Fortier 8d4caa2a1a Merge branch 'inevity-fixingestcsv' into develop 2018-01-01 21:49:51 -05:00
Frederic Fortier 5aef794f97 Merge branch 'fixingestcsv' of https://github.com/inevity/catalyst into inevity-fixingestcsv 2018-01-01 21:49:42 -05:00
Frederic Fortier d01d8d5bbc Merge branch 'caioaao-run-algo-and-fix-124' into develop 2018-01-01 21:48:57 -05:00
Frederic Fortier aaced362a0 Merge branch 'run-algo-and-fix-124' of https://github.com/caioaao/catalyst into caioaao-run-algo-and-fix-124 2018-01-01 21:48:46 -05:00
Frederic Fortier 178801d3a7 Merge branch 'caioaao-signal-handling-only-on-main-thread' into develop 2018-01-01 21:47:43 -05:00
Frederic Fortier 6931dae8fd Merge branch 'signal-handling-only-on-main-thread' of https://github.com/caioaao/catalyst into caioaao-signal-handling-only-on-main-thread 2018-01-01 21:47:32 -05:00
Frederic Fortier 036aab07e1 Merge branch '7AC-master' into develop 2018-01-01 21:45:34 -05:00
Frederic Fortier e61017a86f Merge branch 'caioaao-fix-live-trading' into develop 2018-01-01 21:13:29 -05:00
Frederic Fortier 39065f9dfa Merge branch 'fix-live-trading' of https://github.com/caioaao/catalyst into caioaao-fix-live-trading 2018-01-01 21:13:19 -05:00
Frederic Fortier 6069673cdd Merge branch 'caioaao-fix-exchange-order' into develop 2018-01-01 21:11:11 -05:00
Frederic Fortier e3ad6558a5 Merge branch 'fix-exchange-order' of https://github.com/caioaao/catalyst into caioaao-fix-exchange-order 2018-01-01 21:09:40 -05:00
Caio Oliveira 74c10c45a0 Fix cleanup on retries
From redo docs:

> cleanup (callable): optional; called if one of retry_exceptions is caught. __No arguments are passed to the cleanup function__; if your cleanup requires arguments, consider using functools.partial or a lambda function.
2018-01-01 23:49:52 -02:00
Caio Oliveira 35fb862fa9 Fix exchange order 2018-01-01 21:36:35 -02:00
Caio Oliveira 8f9b1af729 Fix live trading bug
Error:
`AttributeError: 'ExchangeBlotter' object has no attribute 'retry_sleeptime'`
2018-01-01 20:21:11 -02:00
Giuseppe Valente 1e3a0dddf6 exchange: make sure last_entry is set to recompute end 2018-01-01 19:13:51 +01:00
Caio Oliveira e8c4a3636a Forgot one arg 2017-12-31 15:09:40 -02:00
Caio Oliveira 81eaa6426a remove skip init
no idea what's the downside, but should fix #124
2017-12-31 14:26:03 -02:00
Caio Oliveira 0a946e4200 bugfix on run algo 2017-12-31 14:25:46 -02:00
Caio Oliveira 0a9137fbf9 Stop trying to handle signals when inside thread
Plus exposed the code for exiting the algorithm.
2017-12-31 04:29:42 -02:00
Caio Oliveira 8a1d914ed9 💅 2017-12-30 14:09:13 -02:00
Caio Oliveira cf89e01a51 further splitting big functions 2017-12-30 14:05:21 -02:00
Caio Oliveira 3b10a59572 refactoring _run: first iteration
Split the juggernaut function into smaller functions and commented about some
possible issues
2017-12-30 13:54:15 -02:00
Frederic Fortier 4a904a0106 BLD: house keeping 2017-12-29 18:43:22 -05:00
Frederic Fortier 24d7f52d46 BLD: for issue #121, fixed typo 2017-12-29 18:36:59 -05:00
Frederic Fortier 8ab3382f47 BLD: for issue #121, refactored the data portal 2017-12-29 18:35:36 -05:00
Frederic Fortier 2bd54202ce BLD: for issue #121, added retry for get and cancel orders 2017-12-29 18:15:20 -05:00
Frederic Fortier 2e8dd3840b BLD: retry refactoring for issue #121, more testing required 2017-12-29 18:09:33 -05:00
Frederic Fortier b1f49d3c8d BLD: trying redo for retrying calls as suggesting in issue #121 2017-12-29 16:14:26 -05:00
Frederic Fortier 8e232ac5ef BLD: Live chart refactoring 2017-12-29 15:54:44 -05:00
Frederic Fortier ac2f0bbbf8 BLD: Removing old exchange implementations 2017-12-28 17:51:59 -05:00
Frederic Fortier cd1f5ca97b BLD: housekeeping, reorganizing files into smaller packages 2017-12-28 17:49:15 -05:00
Frederic Fortier 44614a75c2 BLD: working around a pipeline issue 2017-12-27 00:30:23 -05:00
Frederic Fortier 8c028e75a0 BLD: saving the cumulative_performance only which seems sufficient to keep the algo state. more testing required. 2017-12-26 20:43:54 -05:00
Frederic Fortier 3a7128ad4f BLD: improved serialization of portfolio data 2017-12-26 19:46:26 -05:00
Frederic Fortier 869ef8ec87 BLD: refinements to positions synchronization in live mode 2017-12-25 06:54:06 -05:00
Frederic Fortier 683f24b24f BLD: added pointer for issue #114 2017-12-24 01:37:52 -05:00
Frederic Fortier 332a3d41e3 BLD: improved portfolio synchronization in live trading to handle situations where positions in the exchange are less than tracked by the algo 2017-12-24 00:26:24 -05:00
Frederic Fortier e9714cfb32 BLD: improved saving algo state 2017-12-23 21:42:04 -05:00
Baul 0408899998 BUG: fix the ingest_csv 2017-12-23 16:21:58 +08:00
Frederic Fortier 67bd5c8f6a BLD: improvements following unit tests 2017-12-22 15:45:23 -05:00
Frederic Fortier 662af595cb BUG: fixed an issue with bad s3 dependency 2017-12-21 21:22:03 -05:00
Frederic Fortier 555b1d817e BUG: fixed deprecation issue 2017-12-21 01:27:06 -05:00
Frederic Fortier d289a1cba5 BUG: fixed issue retrying failed orders 2017-12-21 01:24:39 -05:00
Frederic Fortier 3b16bf7538 BLD: completed unit tests for validating bundles against OHLCV data on each exchange 2017-12-20 16:04:01 -05:00
Frederic Fortier 55262eeb46 BLD: working on bundle unit tests 2017-12-20 15:03:42 -05:00
Frederic Fortier 5d8d14640c Merge branch 'vonpupp-feature/doc_pipenv_install' into develop 2017-12-19 18:01:21 -05:00
Albert De La Fuente Vigliotti 99db2ca46d Specify python2 2017-12-19 18:24:43 -02:00
Albert De La Fuente Vigliotti 9b382f58b8 Minor rewrites 2017-12-19 18:04:43 -02:00
Albert De La Fuente Vigliotti b1a735d83f Initial pipenv instructions 2017-12-19 18:03:12 -02:00
Albert De La Fuente Vigliotti 15e963cee1 Initial pipenv instructions 2017-12-19 17:56:59 -02:00
Frederic Fortier 1921c3dc80 Merge branch 'ykgoon-develop' into develop 2017-12-19 13:39:14 -05:00
Frederic Fortier 757b8a0eef Merge branch 'develop' of https://github.com/ykgoon/catalyst into ykgoon-develop 2017-12-19 13:38:57 -05:00
Frederic Fortier 2e4c9fe027 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-19 13:37:54 -05:00
Frederic Fortier 35e71559a4 Merge branch 'inevity-commissioncalc' into develop 2017-12-19 13:37:31 -05:00
Frederic Fortier 6bfd76ffc7 Merge branch 'commissioncalc' of https://github.com/inevity/catalyst into inevity-commissioncalc 2017-12-19 13:35:37 -05:00
Baul e536b88f1c BUG: fixed the commission calc 2017-12-19 23:26:32 +08:00
Victor Grau Serrat ff1df11384 BUG: minor fix in Poloniex curate script 2017-12-19 14:05:45 +01:00
Frederic Fortier dcefca6038 BLD: completed exchange unit tests and fixed misc issues 2017-12-18 21:04:40 -05:00
Frederic Fortier 6aefc71449 BUG: fixed issue #103, bad order status on Poloniex 2017-12-18 20:46:47 -05:00
Frederic Fortier 394afdba6d BLD: improved frequency / timeframe mapping 2017-12-18 14:59:22 -05:00
Frederic Fortier c698956f9f BUG: worked around CCXT issue with fetch_tickers, see: https://github.com/ccxt/ccxt/issues/870 2017-12-16 21:36:20 -05:00
Frederic Fortier 1ff9c9f39b BLD: utilities for loading historical data 2017-12-15 22:11:57 -05:00
Frederic Fortier b93f9c10d3 BLD: unit tests for historical data 2017-12-15 18:59:13 -05:00
Frederic Fortier 5962496d83 BLD: working on unit tests and data ingestion 2017-12-14 20:42:31 -05:00
Frederic Fortier 952ccf37fa BLD: removing deprecated tests 2017-12-14 15:43:01 -05:00
Frederic Fortier 4740aebd7f BLD: testing markets for each exchange 2017-12-14 15:42:39 -05:00
Frederic Fortier 2a9fe7dbe2 BLD: added CCXT market data cache 2017-12-13 18:44:17 -05:00
Frederic Fortier 44753b681d DOC: formal unit tests scenarios 2017-12-13 15:12:12 -05:00
Frederic Fortier 865ca54e86 DOC: formal unit tests scenarios 2017-12-13 15:10:59 -05:00
Y.K. Goon 71654ff9ff DOC: Fix docker-build instruction
And mostly other style fixes.
2017-12-13 18:15:25 +08:00
Frederic Fortier 95ce66a66a Merge remote-tracking branch 'origin/develop' into develop 2017-12-12 19:44:50 -05:00
Frederic Fortier 4ed000609d BLD: trying to better handle invalid order status 2017-12-12 19:44:43 -05:00
Victor Grau Serrat 024342ce12 BUG: fix of bug from commit ce085e01ec 2017-12-12 16:56:29 -07:00
Victor Grau Serrat 24460967b3 Merge branch 'master' into develop 2017-12-12 16:28:56 -07:00
Victor Grau Serrat 94583c2684 DOC: updating docs - no stop orders on ccxt 2017-12-12 16:28:13 -07:00
Frederic Fortier 7f7cb80e37 DOC: fixed release notes 2017-12-12 17:52:35 -05:00
Frederic Fortier 263a2ba547 merged from develop 2017-12-12 15:48:35 -05:00
Frederic Fortier 3014651ac2 BLD: updated CLI with new parameters 2017-12-12 15:44:50 -05:00
Frederic Fortier a7c9846245 BLD: updated CLI with new parameters 2017-12-12 15:36:57 -05:00
Frederic Fortier d41d9095a1 BLD: adjusted the example algorithms 2017-12-12 15:13:57 -05:00
Frederic Fortier ddf0c480a0 BLD: testing each sample algo and fixing an issue with data.history 2017-12-12 14:43:06 -05:00
Frederic Fortier 7091546b2b BUG: fixed a standardization issue with historical data in live mode 2017-12-12 14:19:10 -05:00
Frederic Fortier a7bcf063c3 BLD: improved stats display in live mode 2017-12-12 13:52:45 -05:00
Frederic Fortier f2e4637f29 BUG: trying to mitigate a date adjustment issue which occurs sometimes sometimes in live trading especially with Bitrrex at certain frequencies. 2017-12-12 13:34:18 -05:00
Frederic Fortier 021e1fd8c8 DOC: updating feature list 2017-12-12 13:28:57 -05:00
Frederic Fortier e46604707f Merge remote-tracking branch 'origin/develop' into develop 2017-12-12 13:23:21 -05:00
Frederic Fortier eee8dcbbd6 DOC: documented paper trading and updated the release notes 2017-12-12 13:23:15 -05:00
Victor Grau Serrat 025929035e DOC: fixed missing link 2017-12-12 09:13:38 -07:00
Victor Grau Serrat 1d15e12b8d DOC: added features page, restructured Jupyter & naming convention 2017-12-12 09:04:30 -07:00
Frederic Fortier 552f4260b4 BLD: for issue #87, added configurable slippage and commission 2017-12-11 22:34:46 -05:00
Frederic Fortier 0c3b5fc3c5 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-11 20:01:11 -05:00
Frederic Fortier 42f59e99df BLD: improving stats upload 2017-12-11 20:00:45 -05:00
Victor Grau Serrat 5d84f26b72 DOC: Updated Jupyter documentation 2017-12-11 15:52:55 -07:00
Victor Grau Serrat 60747082b6 DOC: added jupyter notebook in the examples 2017-12-11 15:46:55 -07:00
VictorandGitHub 1e00be4cf3 Update dual_vwap.py 2017-12-11 15:18:07 -07:00
Victor Grau Serrat 153469664c DOC: link README to DOC landing page, added badges 2017-12-11 12:31:30 -07:00
Victor Grau Serrat d40e9f1623 Merge branch 'master' into develop 2017-12-11 12:28:29 -07:00
VictorandGitHub 4f8bf413bd DOC: Update README.rst 2017-12-11 12:12:46 -07:00
Victor Grau Serrat 0558cc61cf DOC: Updated README.rst 2017-12-11 12:09:53 -07:00
Frederic Fortier a3761beae2 BUG: fixed retry issue with synchronize_portfolio 2017-12-11 02:22:38 -05:00
Frederic Fortier 49aaaa8f26 BLD: Housekeeping 2017-12-10 01:24:39 -05:00
fredfortier a74da31964 BLD: improved error handling of the tickers operations 2017-12-09 21:47:47 -05:00
fredfortier e41eca0d8a BUG: fixed some issues with capital_base 2017-12-08 20:29:55 -05:00
fredfortier 48f4d01c70 Merge remote-tracking branch 'origin/develop' into develop 2017-12-08 18:26:49 -05:00
fredfortier 6981669a68 BUG: adding capital_base to the interface 2017-12-08 18:26:42 -05:00
Victor Grau Serrat ebd1ca44f6 BUG: fix missing context in ingest-exchange, bug introduced in commit ce085e01ec 2017-12-08 14:07:52 -07:00
Victor Grau Serrat 12f0f4319b Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-08 14:01:15 -07:00
Victor Grau Serrat 4c15f5efda BUG: _run() missing paper-trading params 2017-12-08 14:01:06 -07:00
fredfortier ce61f49f27 Merge remote-tracking branch 'origin/develop' into develop 2017-12-08 15:23:00 -05:00
fredfortier 313db1def9 BLD: improvements to stats output 2017-12-08 15:22:53 -05:00
Victor Grau Serrat 57f6a69e94 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-08 13:18:45 -07:00
Victor Grau Serrat ce085e01ec MAINT: PEP8 compliance 2017-12-08 13:18:24 -07:00
fredfortier 89f9a1179e BLD: improvements to stats output 2017-12-08 15:15:18 -05:00
Victor Grau Serrat eb5d55478d MAINT: added CCXT requirement to Conda yml environment 2017-12-07 23:04:54 -07:00
Victor Grau Serrat 0855391f77 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-07 22:32:28 -07:00
Victor Grau Serrat 619fbfc6ea DOC: PEP8 simple_universe.py & added to example_algos.html 2017-12-07 22:32:17 -07:00
fredfortier b644c947e3 BUG: fixed issue with stats output 2017-12-07 23:01:12 -05:00
fredfortier 1c03d837cc BUG: fixed issue with stats output 2017-12-07 22:26:01 -05:00
fredfortier 2e7aabd973 BLD: tested stats with multiple assets 2017-12-07 22:14:49 -05:00
fredfortier 6147df5c6c Merge remote-tracking branch 'origin/develop' into develop 2017-12-07 20:26:45 -05:00
fredfortier 54dcc58ee8 BLD: improved stats display to better support multiple assets per algo 2017-12-07 20:26:37 -05:00
VictorandGitHub ff94c735e8 Merge pull request #45 from abnera/patch-2
Example: Simple Universe
2017-12-07 17:11:05 -07:00
VictorandGitHub aa3f75390d Merge pull request #88 from cyzanfar/develop
MAINT: python3 compatible
2017-12-07 16:52:19 -07:00
VictorandGitHub f4a1ad6e61 Merge pull request #70 from zie1ony/develop
Python 3 support
2017-12-07 16:49:33 -07:00
Frederic Fortier 87428b299f fixed requirements 2017-12-07 12:38:50 -08:00
fredfortier 5b78f161a4 BLD: more live trading testing and added s3 stats output 2017-12-07 00:20:27 -05:00
fredfortier 9688d71e23 BLD: tested blotter changes with live trading 2017-12-06 23:32:26 -05:00
cyzanfar 02875ef7ab Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-12-06 18:02:17 -05:00
fredfortier e42276affa BLD: making some adjustment to the blotter to improve paper trading 2017-12-06 18:02:02 -05:00
cyzanfar f47aa13dd3 Python version 3 compatible 2017-12-06 17:58:42 -05:00
fredfortier dcdf4f77db BLD: making some adjustment to the blotter to improve paper trading 2017-12-05 22:08:30 -05:00
fredfortier 96a27d083c BLD: more live trading tests and fixed related issues 2017-12-03 00:04:15 -05:00
fredfortier f995f451a7 BLD: some refactoring to simplify the integration logic and tested several algos 2017-12-02 21:27:03 -05:00
fredfortier a3838fc00f BLD: tested ccxt with manual data ingestion 2017-12-02 20:20:57 -05:00
fredfortier 4db8131397 BLD: paper trading adjustments 2017-12-01 00:06:11 -05:00
fredfortier 207bce6216 Merge remote-tracking branch 'origin/develop' into develop 2017-11-30 23:16:16 -05:00
fredfortier 8fb8b80a12 BLD: first rough test of CCXT in live trading 2017-11-30 23:15:10 -05:00
fredfortier b762689225 BLD: all exchange operations now implemented an unit tested with CCXT 2017-11-30 22:45:52 -05:00
fredfortier 7bbb6e0b42 BLD: tested creating orders and viewing open orders with CCXT 2017-11-30 20:18:16 -05:00
Victor Grau Serrat b587804e3e DOC: fix video links to algos 2017-11-30 17:11:15 -07:00
fredfortier 5660247da2 BLD: tested all public APIs with CCXT 2017-11-30 17:08:13 -05:00
fredfortier fc2c44a6b7 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	catalyst/examples/mean_reversion_simple.py
2017-11-29 22:33:46 -05:00
fredfortier 4eb8a6eb0f BLD: populating assets with help from CCXT 2017-11-29 22:31:44 -05:00
Victor Grau Serrat c8eaa11f80 DOC: added portfolio_optimization to documented examples 2017-11-29 09:37:46 -07:00
Victor Grau Serrat 55a9d76b9b Merge branch 'master' into develop 2017-11-29 09:24:59 -07:00
Victor Grau Serrat 7abd992d17 DOC: added portfolio_optimization example 2017-11-29 09:19:41 -07:00
Victor Grau Serrat daeccaed36 DOC: video - live trading 2017-11-28 16:44:13 -07:00
Victor Grau Serrat 25358a4077 Merge branch 'master' into develop 2017-11-28 12:44:19 -07:00
Victor Grau Serrat 7cf4f84e89 DOC: documented 4 example algorithms 2017-11-28 12:35:50 -07:00
Victor Grau Serrat b63199e4e1 DOC: adjusting example params to match video tutorial 2017-11-28 12:24:53 -07:00
Victor Grau Serrat 4af08be7e8 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-11-28 12:23:19 -07:00
fredfortier 606148e19c DOC: Integrating with ccxt 2017-11-28 13:42:58 -05:00
Victor Grau Serrat d38e560265 Merge branch 'master' into develop 2017-11-28 11:23:31 -07:00
Victor Grau Serrat ffd1bc07cc DOC: making example algos consistent with doc website 2017-11-28 11:22:41 -07:00
fredfortier dfcfe5a370 merging from develop 2017-11-28 01:44:46 -05:00
fredfortier 803823eac0 merging from develop 2017-11-28 01:41:58 -05:00
fredfortier 5a18e09730 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	docs/source/releases.rst
2017-11-28 01:34:38 -05:00
fredfortier dd41f8c006 BUG: fixed issue with daily frequency 2017-11-28 01:34:17 -05:00
Victor Grau Serrat 7a2a4817fe BUG: missing parameters in log statements 2017-11-27 23:11:22 -07:00
Victor Grau Serrat 105522e5ab DOC: fixed date from 0.3.9 release 2017-11-27 22:01:19 -07:00
Victor Grau Serrat a52e201f86 DOC: improved dual_moving_average.py example algo 2017-11-27 21:10:17 -07:00
Victor Grau Serrat 697ff54125 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-11-27 21:07:15 -07:00
Victor Grau Serrat 4bcd34bd78 DOC: improved beginner tutorial 2017-11-27 21:07:03 -07:00
fredfortier 64c52c7a3c BUG: fixed issue #72 with the buy_and_hodl sample algo 2017-11-27 18:44:50 -05:00
fredfortier 1c143eb9ea DOC: 0.3.9 release notes 2017-11-27 17:55:56 -05:00
fredfortier 1da4ccfb8c Merge remote-tracking branch 'origin/master' 2017-11-27 17:55:32 -05:00
fredfortier a61b22b821 DOC: 0.3.9 release notes 2017-11-27 17:55:22 -05:00
fredfortier d07e0edd88 DOC: 0.3.9 release notes 2017-11-27 17:54:34 -05:00
Victor Grau Serrat c2821ab77b DOC: added example_algo: Dual Moving Average Crossover 2017-11-27 15:43:57 -07:00
fredfortier 1696db930d Merge branch 'develop' 2017-11-27 17:37:15 -05:00
fredfortier 9397b3fd5a BLD: testing simple universe with Bitfinex 2017-11-27 17:36:44 -05:00
fredfortier 2bb11db412 BLD: modified sample algo for testing 2017-11-27 17:28:13 -05:00
fredfortier c2f3e00d99 BUG: Adding back missing constants 2017-11-27 17:27:43 -05:00
fredfortier 292fe66d3f Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	catalyst/constants.py
2017-11-27 17:17:05 -05:00
fredfortier ba46015bae BLD: completed implementation of issue #65, support for custom exchange data 2017-11-27 17:16:44 -05:00
Victor Grau Serrat 12d5915c8e ENH: DEBUG level can be easily overriden from the local environment 2017-11-27 15:02:13 -07:00
Victor Grau Serrat c6fe45371c ENH: removed default for --capital_base in backtesting 2017-11-27 13:20:00 -07:00
MaciekandGitHub 2fdb4dd0bd Decode poloniex_api.query with utf-8 2017-11-27 20:59:47 +11:00
fredfortier 968e70b69b BLD: implementing issue #65, implemented custom exchange data 2017-11-25 07:43:20 -05:00
fredfortier 6a7c47f3a9 BLD: implementing issue #65, adding local symbols definition 2017-11-24 01:58:33 -05:00
fredfortier 7daf295e63 BLD: refactoring to decrease reliance on the Exchange in preparation to support ad-hoc CSV bundles 2017-11-23 22:11:23 -05:00
fredfortier 7dddc0a85f BUG: fixed issue #80 but updated performance stats immediately after registering transactions in live mode 2017-11-22 21:11:16 -05:00
fredfortier 32523d474d Merge remote-tracking branch 'origin/develop' into develop 2017-11-22 16:03:29 -05:00
fredfortier 841acf0203 BLD: implemented issue #79, using the capital_base parameter to override the amount of base currency available for trading 2017-11-22 16:03:21 -05:00
Victor Grau Serrat b4ab1a5375 DOC: remake of beginner tutorial 2017-11-21 22:53:50 -07:00
fredfortier 3ec9853b75 BUG: in relation to issue #77, catching the remaining warnings 2017-11-21 20:42:44 -05:00
fredfortier c1d140a831 BUG: fixed issue #77, a sortino warning prevents analyze() from completing 2017-11-21 15:55:56 -05:00
fredfortier 02dc4d6a30 BUG: made some live-trading adjustments related to issue #71 2017-11-21 13:56:05 -05:00
fredfortier 0d366a350d BUG: fixed #75, adjusted the ruturn value of run_algorithm to support minute stats. 2017-11-20 21:53:48 -05:00
fredfortier 1e8b0c36a1 BUG: fixed #74, a problematic scenario when retrieving the history of multiple assets. 2017-11-20 20:00:48 -05:00
fredfortier 0af592a5f4 BUG: fixed issue #71 with the last candle of a resampled set 2017-11-20 17:52:29 -05:00
Victor Grau Serrat 86b2a5c772 DOC: videos: +3rd_install, +backtest 2017-11-20 09:31:47 -07:00
Victor Grau Serrat 9cfd50dc4f DOC: mean_reversion_simple.py minor edits, and added to doc website 2017-11-20 09:12:43 -07:00
Victor Grau Serrat 698b19c8fa DOC: updated examples/buy_and_hodl.py. Added Example Algos and Utilities pages to the documentation 2017-11-19 23:51:57 -07:00
Victor Grau Serrat 5d4bc99097 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-11-19 22:04:28 -07:00
Victor Grau Serrat cfb3f1ca42 DOC: restructured install page 2017-11-19 22:04:11 -07:00
MaciekandGitHub 01b02ccd84 Python 3 support
#catalyst/curate/poloniex.py:
Change 
`print url`
to
`print(url)`
2017-11-18 13:40:50 +11:00
fredfortier ee1605a5e6 Merge branch 'abnera-patch-2' into develop 2017-11-17 19:43:46 -05:00
fredfortier f3dca74e87 BUG: fixed a get_candles issue with the Poloniex exchange 2017-11-17 19:40:49 -05:00
Victor Grau Serrat d57b79427b BUG: enforced --capital base in backtesting 2017-11-17 10:43:20 -07:00
Victor Grau Serrat 8a89c0c53f BUG: enforced --base_currency in backtesting. Fixes #67. 2017-11-17 09:52:25 -07:00
Abner Ayala-AcevedoandGitHub a8a869dd49 Fix when to fetch data
Ensure to get data at the last minute of the candle.
2017-11-16 16:21:25 -08:00
fredfortier c260e188b0 BUG: looking a potential resampling issue 2017-11-16 18:14:24 -05:00
fredfortier 230b9c17eb Merge branch 'patch-2' of https://github.com/abnera/catalyst into abnera-patch-2 2017-11-16 16:58:15 -05:00
fredfortier 2a8b5cf911 Merge branch 'damo1884-talib_example' into develop 2017-11-16 16:56:07 -05:00
fredfortier 3fa88a3e56 BLD: misc housekeeping 2017-11-16 16:55:40 -05:00
fredfortier 64532c3d08 BLD: minor adjustments to the talib sample algo 2017-11-16 16:54:32 -05:00
fredfortier 5f86ab659e Merge branch 'talib_example' of https://github.com/damo1884/catalyst into damo1884-talib_example 2017-11-16 16:48:10 -05:00
Abner Ayala-AcevedoandGitHub df14a94918 Modified for examples consistency.
Fully tested on v0.3.8
2017-11-16 11:22:35 -08:00
fredfortier e087e48088 BLD: polishing a sample algorithm 2017-11-14 17:04:38 -05:00
fredfortier 5110b37a82 Merge remote-tracking branch 'origin/develop' into develop 2017-11-14 16:39:19 -05:00
Victor Grau Serrat a2bb231424 DOC: improving Win/Conda install instructions 2017-11-14 12:14:38 -07:00
fredfortier e939f742a8 Merge branch 'master' into develop 2017-11-14 13:59:17 -05:00
fredfortier 9093be748e BUG: fixed a warning filter issue 2017-11-14 13:58:24 -05:00
fredfortier e23a7e67a0 merging from the develop branch 2017-11-14 13:29:50 -05:00
fredfortier 273b4fb7a7 DOC: updated release notes 2017-11-14 13:27:20 -05:00
fredfortier 0b2684d532 BLD: polishing a sample algorithm 2017-11-14 13:22:27 -05:00
fredfortier 224192a1ee BUG: fixed issue #63 warnings with cumulative metrics 2017-11-14 11:50:38 -05:00
fredfortier 2d7202ac81 BUG: fixed issue #64 with SSL certificates 2017-11-14 11:40:11 -05:00
fredfortier 8d95428fa6 BLD: created a simpler mean-reversion algo for the video 2017-11-14 11:01:46 -05:00
fredfortier d678376d8d BLD: polishing a sample algorithm 2017-11-14 02:28:22 -05:00
fredfortier 9b5fa83da3 BLD: polishing a sample algorithm 2017-11-14 01:00:21 -05:00
fredfortier 0f1c3e1ace Merge remote-tracking branch 'origin/develop' into develop 2017-11-13 22:06:21 -05:00
fredfortier f3cb610748 BLD: polishing a sample algorithm 2017-11-13 22:06:06 -05:00
Victor Grau Serrat 1e6316d414 BUG: PoloniexCurator: connection retries when fetching data 2017-11-13 15:31:49 -07:00
fredfortier df51cbe21b Merge remote-tracking branch 'origin/develop' into develop 2017-11-13 16:57:46 -05:00
fredfortier dce31b212b BLD: polishing a sample algorithm 2017-11-13 16:57:37 -05:00
Victor Grau Serrat 00269d3dfb MAINT: PoloniexCurator PEP8 edits 2017-11-13 14:41:10 -07:00
fredfortier 648be3969a DOC: added release notes of upcoming 0.3.7 release 2017-11-11 18:09:24 -05:00
fredfortier a54325fdcf BLD: issue #62, the stats now align with the data_frequency selected in the algo 2017-11-10 19:59:42 -05:00
fredfortier b64e5929b4 BUG: resolve issue #61 by adjusting our perf conventions to match zipline exactly. 2017-11-10 17:39:36 -05:00
fredfortier 631cbcd352 BLD: Working on the sample algo for intro videos. Made auto-ingestion configurable. 2017-11-09 19:56:57 -05:00
fredfortier 24c5a5bd13 Merge remote-tracking branch 'origin/develop' into develop 2017-11-09 17:10:06 -05:00
fredfortier 1103947af0 BLD: created a new sample algo for instructional materials. Fixed some minor issues in the process. 2017-11-09 17:09:55 -05:00
damo1884 061de3c12f fix issue with candlestick chart 2017-11-08 19:28:44 -08:00
lacabra 207887a28d MAINT: PoloniexCurator cleanup 2017-11-08 20:43:40 +00:00
Victor Grau Serrat dc53f973e4 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-11-08 11:32:00 -07:00
Victor Grau Serrat 9a80a488cd MAINT: handful of coins with first tradeID > 1 and PEP8 2017-11-08 11:31:56 -07:00
fredfortier a85b6c798a BUG: fixes related to issue #47 and added a verbose ingestion option. 2017-11-07 13:42:47 -05:00
Victor Grau Serrat 9229809b05 DOC: fix broken documentation 2017-11-06 16:41:04 -07:00
fredfortier f81cf6b600 BUG: working on issue #57 2017-11-06 15:07:24 -05:00
fredfortier ba4ffc7272 BUG: working on issue #57 2017-11-06 15:04:44 -05:00
damo1884 12695474e3 Add TALib Simple Example 2017-11-05 01:14:09 -07:00
fredfortier 9d1dd5829d Merge branch 'develop' 2017-11-04 16:48:59 -04:00
fredfortier d4148891fc DOC: updated release notes prior to release 2017-11-04 16:43:27 -04:00
fredfortier c9c16f54b1 BUG: fixed on issue #55 with single history bar 2017-11-04 16:38:54 -04:00
fredfortier 7da72fe9cb BUG: working on issue #55 2017-11-04 16:22:10 -04:00
fredfortier 515c6e13f0 Merge branch 'develop' 2017-11-04 15:01:03 -04:00
fredfortier 5abdc063eb BLD: resolving conflict with algo 2017-11-04 14:57:54 -04:00
fredfortier 360e1adc22 BLD: resolving conflict with algo 2017-11-04 14:56:24 -04:00
fredfortier 8c6ac53a05 BLD: cleanup in algos and unit tests 2017-11-04 14:46:09 -04:00
fredfortier b636edb32f BLD: working on the sample algos 2017-11-04 14:16:25 -04:00
fredfortier d02c6d8ce9 BLD: optimize imports 2017-11-03 21:04:16 -04:00
fredfortier 88f6557aaf Merge remote-tracking branch 'origin/develop' into develop 2017-11-03 20:59:42 -04:00
fredfortier b476024612 BUG: work around for issue #53 and possibly fixed issue #47 2017-11-03 20:59:26 -04:00
Victor Grau Serrat a3808c31ef DOC: releases 2017-11-02 23:53:21 -05:00
fredfortier 5d251f6f9a BLD: modified algo for testing 2017-11-02 21:06:11 -04:00
fredfortier 117332d0b4 Merge branch 'develop' 2017-11-02 20:46:06 -04:00
fredfortier 2bbc0c00cc BLD: updating test algo 2017-11-02 20:44:16 -04:00
fredfortier 5e4ad9b338 BUG: accounting for daily historical bars with minute freq algo 2017-11-02 20:18:34 -04:00
fredfortier a9a422c892 DOC: updating the code docstrings 2017-11-01 23:10:31 -04:00
fredfortier 5b6bbacab0 DOC: updating the code docstrings 2017-11-01 21:31:51 -04:00
fredfortier 35677c553c BUG: fixed issues with data frequencies in data.history() which was particularly noticeable in live mode and minor adjustments around the commission model 2017-10-31 23:34:48 -04:00
fredfortier df357d2327 BUG: reduced the commission and slippage values to account for lower volume transactions. These models are still simple approximations. More work required to closely model exchange fees. (fixing previous commit) 2017-10-31 21:43:24 -04:00
fredfortier e6ff7ee4fc BUG: reduced the commission and slippage values to account for lower volume transactions. These models are still simple approximations. More work required to closely model exchange fees. 2017-10-31 21:40:12 -04:00
fredfortier 30eea4b8f7 Merge remote-tracking branch 'origin/develop' into develop 2017-10-31 19:22:46 -04:00
fredfortier 7ad047a432 BUG: fixed an issue with can_trade() 2017-10-31 19:20:39 -04:00
Victor Grau Serrat e291a260b2 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-31 13:56:27 -06:00
Victor Grau Serrat 9a9e66b43d DOC: jupyter notebook, expanded welcome & improved install notes 2017-10-31 13:56:16 -06:00
fredfortier 1c3deb648a DOC: updated release notes for 0.3.4 and 0.3 2017-10-31 15:21:40 -04:00
Victor Grau Serrat b39311de85 DOC: Resources page 2017-10-31 12:39:35 -06:00
Victor Grau Serrat 5417a0cdcf DOC: Release Notes 2017-10-31 12:12:28 -06:00
Victor Grau Serrat 6a4ea43d27 DOC: updated README 2017-10-31 09:51:37 -06:00
Victor Grau Serrat 7fc1ade46c Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-31 00:05:55 -06:00
Victor Grau Serrat 635fc80ef2 DOC: fix Windows conda install 2017-10-31 00:05:48 -06:00
fredfortier c59d805717 Merge remote-tracking branch 'origin/develop' into develop 2017-10-30 21:18:03 -04:00
fredfortier 3394614ecf BUG: Fixes issue #47. Made improvements around auto-ingestion. 2017-10-30 21:17:53 -04:00
Victor Grau Serrat 06c8ab9c37 DOC: troubleshooting Windows install 2017-10-30 15:57:19 -06:00
Victor Grau Serrat 6a0d0a0422 DOC: jupyter notebook fix 2017-10-30 15:14:27 -06:00
Victor Grau Serrat 9470771561 Merge branch 'master' into develop 2017-10-30 15:08:11 -06:00
Victor Grau Serrat 8132e1f5ea DOC: small fixes 2017-10-30 14:47:15 -06:00
Victor Grau Serrat 0b28bf0e96 DOC: live trading 2017-10-30 14:22:55 -06:00
Victor Grau Serrat 13f023d364 DOC: documenting the documentation 2017-10-30 13:54:27 -06:00
Victor Grau Serrat eaefe4a908 DOC: videos 2017-10-30 13:27:52 -06:00
Victor Grau Serrat f47b657c6f fix conda install 2017-10-30 11:57:26 -06:00
Victor Grau Serrat 800a2efa50 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-30 11:16:33 -06:00
Victor Grau Serrat 7465e8e432 DOC: videos 2017-10-30 11:16:20 -06:00
Victor Grau Serrat 86ab3804e5 DOC: install 2017-10-27 10:46:14 -06:00
Victor Grau Serrat b749d47a61 FIX: DOCS install 2017-10-27 10:31:14 -06:00
fredfortier 032c7fd16b Improved frequency support for data.history() in backtest, standardized class names, improved unit tests and working on new sample algo. 2017-10-27 00:57:52 -04:00
fredfortier b9579ab4b4 Improved frequency support for data.history() in backtest, standardized class names, improved unit tests and working on new sample algo. 2017-10-27 00:53:19 -04:00
fredfortier c7632b57a6 Merge remote-tracking branch 'origin/develop' into develop 2017-10-27 00:30:36 -04:00
fredfortier da17e66961 Fixed major issue with sell orders 2017-10-27 00:30:26 -04:00
Abner Ayala-AcevedoandGitHub cd0157347f Refactoring 2017-10-26 18:10:12 -07:00
Abner Ayala-AcevedoandGitHub 76a8362e3d Update simple_universe.py 2017-10-26 15:17:02 -07:00
Abner Ayala-AcevedoandGitHub c8cc2edd36 Convert to 30 minutes ohlcv data 2017-10-26 15:16:13 -07:00
Victor Grau Serrat 5f8016c67e improving buy_btc_simple.py example 2017-10-26 14:08:47 -06:00
Victor Grau Serrat 3d88d6a2c7 Merge branch 'develop' - Release 0.3.3 2017-10-26 13:13:42 -06:00
Victor Grau Serrat 9c3a9e233b Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-26 12:55:32 -06:00
Victor Grau Serrat c43509c28e catching missing -x in ingest-exchange 2017-10-26 12:55:18 -06:00
fredfortier 0e0bfc82b5 Fixed issues in the prepare_chunk logic 2017-10-26 14:04:30 -04:00
fredfortier 2f660db511 Fixed an issue with daily chunks end date 2017-10-26 13:52:37 -04:00
fredfortier fdc5a30060 Added data validation unit tests and minor fixes to the get_candles method of Poloniex. 2017-10-26 02:33:17 -04:00
fredfortier bb1d96ed5d Merge remote-tracking branch 'origin/develop' into develop 2017-10-25 19:44:05 -04:00
fredfortier 59501905ab Poloniex get_candles fix and created a unit test to validate data. 2017-10-25 19:43:57 -04:00
Abner Ayala-AcevedoandGitHub cb870422c3 Create simple_universe.py
This example aims to help users get familiar with catalyst API's to collect and handle data.
2017-10-25 12:11:40 -07:00
Victor Grau Serrat 2b85732e36 Merge branch 'develop' - Release 0.3.2 2017-10-24 21:59:53 -06:00
Victor Grau Serrat 284c749bb5 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-24 21:58:47 -06:00
VictorandGitHub d7f5e73f84 Merge pull request #43 from reinka/develop
[MIG] Migrated buy_and_hodl and buy_low_sell_high to version 0.3 to work with Poloniex exchange
2017-10-24 21:58:22 -06:00
Victor Grau Serrat cde69da173 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-24 21:55:25 -06:00
Victor Grau Serrat bcc75f6b00 FIX: Poloniex 1min curator 2017-10-24 21:54:59 -06:00
fredfortier f179381b64 Small python 3 fixes 2017-10-24 23:41:37 -04:00
fredfortier 10ba53b897 Merge remote-tracking branch 'origin/develop' into develop 2017-10-24 20:03:58 -04:00
fredfortier 1cfe3b1bb2 Fixed issues in the prepare_chunk logic 2017-10-24 20:03:50 -04:00
Victor Grau Serrat 268ff9c826 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-24 17:44:24 -06:00
Victor Grau Serrat 7eb184d946 exchange unit tests 2017-10-24 17:44:18 -06:00
fredfortier 1cc34a1485 Fixed urllib package for back compatibility 2017-10-24 19:01:30 -04:00
fredfortier aa2f2f3627 Filtered out starting dates before the calendar 2017-10-24 18:26:44 -04:00
fredfortier 7e373e2f9c Removing symbols.json in clean-exchange. 2017-10-24 18:02:58 -04:00
fredfortier 942e6f263c Fixed an issue with the bar reader. 2017-10-24 16:10:33 -04:00
fredfortier 2e6d7d28ba Fixed an issue with the bar reader. 2017-10-24 16:00:56 -04:00
fredfortier 3a823ea457 Python3 adjustments 2017-10-24 15:47:15 -04:00
fredfortier 4daba6cfb4 Added unit test 2017-10-24 15:46:14 -04:00
Victor Grau Serrat fa018e2e0c more bcolz unit tests 2017-10-24 13:42:53 -06:00
Victor Grau Serrat 315d25f7c0 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2017-10-24 12:32:00 -06:00
fredfortier cc7ffada96 Merge remote-tracking branch 'origin/develop' into develop 2017-10-24 14:23:57 -04:00
fredfortier 5394c1bc91 Fixed an issue with asset date in chunks 2017-10-24 14:23:47 -04:00
Victor Grau Serrat b230b73829 unit test bcolz writer 2017-10-24 11:28:31 -06:00
Victor Grau Serrat 930a68ab4a unit test for Bcolz writer expanded 2017-10-24 10:36:15 -06:00
Victor Grau Serrat 4e833981e4 unit test for Bcolz writer expanded 2017-10-24 09:55:37 -06:00
fredfortier 2ea402ff10 Modified bcolz unit test 2017-10-24 11:39:17 -04:00
Victor Grau Serrat da6b024edc unit test for Bcolz writer 2017-10-24 09:32:24 -06:00
Victor Grau Serrat 565e9a3cea Added param checking and help msg to clean bundle folders 2017-10-23 21:30:48 -06:00
fredfortier 3c10d19a7e Added method to clean bundle folders 2017-10-23 20:53:25 -04:00
fredfortier cf96e047cd Added method to clean bundle folders 2017-10-23 20:49:40 -04:00
fredfortier 6f6a8e1272 Merge remote-tracking branch 'origin/develop' into develop 2017-10-23 20:29:57 -04:00
fredfortier c2a02e7074 Fixed hash method to create sid numbers 2017-10-23 20:29:48 -04:00
Victor Grau Serrat 7d2cf97fbf FIX: Conda install for Windows 2017-10-23 16:02:28 -06:00
Victor Grau Serrat 195469897c FIX: Windows path 2017-10-23 14:43:56 -06:00
fredfortier c7b422d465 Fix to work around empty bundles 2017-10-22 18:14:35 -04:00
Victor Grau Serrat 2dbace37bb Merge branch 'develop' - Release 0.3.1
FIX: bundle start_dt cannot be earlier than asset_start
FIX: prior raise of AuthNotFound, now generates empty auth.json, and raises AuthEmpty when live
FIX: os.path.join to make BUNDLE_NAME_TEMPLATE compatible across OSes
2017-10-21 22:57:47 -06:00
Victor Grau Serrat 2e903fd42c FIX: bundle start_dt, empty auth, bundle_name_template->os.path.join 2017-10-21 22:56:22 -06:00
reinka 47a104b29c [MIG] Migrated to version 0.3 to work with Poloniex exchange. 2017-10-21 11:26:34 +02:00
fredfortier d248581523 Fixed an error message 2017-10-21 00:27:05 -04:00
fredfortier 48f6300e08 Optimized imports 2017-10-20 23:18:15 -04:00
VictorandGitHub f7a143cb78 Merge pull request #41 from abnera/patch-1
Fix issues with .yml file and incompatible packages.
2017-10-20 15:46:02 -06:00
Victor Grau Serrat 2f7cd97852 DOC: WIP fix tutorial 2017-10-20 15:37:04 -06:00
Abner Ayala-AcevedoandGitHub 73eca75ed9 Updated conda .yml file to work with enigma 0.3 or above.
Removed unnecessary libraries that were giving issues.
2017-10-20 14:30:06 -07:00
Victor Grau Serrat 2ade2989e8 Merge branch 'develop' -> release 0.3 2017-10-20 14:53:23 -06:00
Victor Grau Serrat b1d5acf2ad DOC: jupyter notebook in beginner tutorial 2017-10-20 14:51:01 -06:00
Victor Grau Serrat 5d5ec6b9be DOC: jupyter notebook in beginner tutorial 2017-10-20 14:49:54 -06:00
Victor Grau Serrat 1b84023c5d Merge branch 'concurrent-exchanges' into develop 2017-10-20 13:42:26 -06:00
Victor Grau Serrat 97f3329c1b centralizing LOG_LEVEL 2017-10-20 13:41:33 -06:00
fredfortier 493fc95a20 Fixed an issue with historical data in live mode 2017-10-20 15:17:29 -04:00
Victor Grau Serrat bdeb344999 constants.py, WIP: system-wide log level 2017-10-20 13:08:55 -06:00
Victor Grau Serrat 52e1de954f Resolving conflicts between branches 2017-10-20 12:15:58 -06:00
Victor Grau Serrat 7b9eafef4e Merge branch 'master' into develop 2017-10-20 12:09:51 -06:00
fredfortier f918fc97bc Fix an issue with data.history() in backtest mode 2017-10-20 13:36:39 -04:00
fredfortier 18e19bb1ae Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-20 13:17:10 -04:00
fredfortier f72074876d Misc small fixes 2017-10-20 13:17:02 -04:00
Victor Grau Serrat fadd4abe5a DOC: naming convention 2017-10-20 10:55:35 -06:00
Victor Grau Serrat 5fd4ca33d3 DOC: beginner tutorial 2017-10-20 10:14:31 -06:00
Victor Grau Serrat 653f4c2a5a DOC: Features 2017-10-20 08:27:36 -06:00
Victor Grau Serrat 3804af3813 DOC: welcome page w/ logo 2017-10-20 00:13:23 -06:00
Victor Grau Serrat f56abcfc3e DOC: welcome page 2017-10-19 23:54:02 -06:00
Victor Grau Serrat cb6432c395 docs: Catalyst Install 2017-10-19 23:32:55 -06:00
fredfortier 946d24bd7a Refactoring related to auto-ingestion 2017-10-19 23:23:37 -04:00
Victor Grau Serrat b1a247df6a gh-pages initial build: Installation (WIP) 2017-10-19 18:03:13 -06:00
Victor Grau Serrat 2c91decc1b WIP: docs build 2017-10-19 15:31:43 -06:00
Victor Grau Serrat 09f27e5880 WIP: build docs 2017-10-19 14:45:32 -06:00
Victor Grau Serrat 2dd8f54148 WIP: build docs 2017-10-19 14:35:25 -06:00
fredfortier 2d41f124f0 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-19 15:24:08 -04:00
fredfortier 619eb3cfa4 Fixed an issue with data.history more recent than the server 2017-10-19 15:24:00 -04:00
Victor Grau Serrat 331a31b25a WIP: docs build 2017-10-19 13:18:21 -06:00
Victor Grau Serrat 6f57660944 WIP: docs build 2017-10-19 12:55:42 -06:00
fredfortier 1a97111ceb Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-19 14:49:41 -04:00
fredfortier 2502c9a2bb Minor fixes 2017-10-19 14:49:32 -04:00
Victor Grau Serrat 675957b197 Open calendar starts on 2015-02-19 2017-10-19 10:04:17 -06:00
fredfortier 51172759d3 Fixed some issues and optimized data.history() in live mode 2017-10-19 05:19:01 -04:00
fredfortier 6097128d5c Fixed small issue with minute ingestion 2017-10-18 23:45:42 -04:00
fredfortier 5ccdd54274 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-18 23:26:31 -04:00
fredfortier b3dcb7a9ad Fixed issue with overlapping chunks 2017-10-18 23:26:24 -04:00
Victor Grau Serrat 8a6d0d7ca0 Catch NoData on Exchange + formatting of errors 2017-10-18 21:25:27 -06:00
fredfortier e5f7c63ebd Fixed an issue with minute bundles 2017-10-18 20:43:26 -04:00
fredfortier 2e46323a9e Fixed an issue with minute bundles 2017-10-18 20:30:24 -04:00
fredfortier 874a4bb682 Fixed an issue with reader array size 2017-10-18 18:33:37 -04:00
fredfortier 339fa21c35 Fixed an issue with the backtest get_history_window method. 2017-10-18 17:23:33 -04:00
fredfortier 1c5822bce9 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-18 16:41:37 -04:00
fredfortier b785c10036 Fixed misc issues with the bundle refactoring 2017-10-18 16:41:29 -04:00
Victor Grau Serrat b69e78b27d fixes ingestion of 'minute,daily' parameter 2017-10-18 14:10:03 -06:00
Victor Grau Serrat 6486744c66 fix exchange_bundle: period month padded 2017-10-18 13:55:39 -06:00
fredfortier 6f8fbc2b82 Fix issue with retrieving bundles 2017-10-18 15:33:15 -04:00
fredfortier 521484355a Minor fix to the bcolz writer 2017-10-18 14:45:56 -04:00
fredfortier 7147bfc51f Refactoring to use the updated bundles 2017-10-18 14:36:25 -04:00
fredfortier b357a0656a Added a modified bcolz writer / reader 2017-10-18 13:58:37 -04:00
fredfortier 86f892eade Unit tested a daily reader/writer based on the minute bundle 2017-10-18 04:29:22 -04:00
fredfortier 188a4a3f3d Unit testing an issue with the daily loader 2017-10-18 02:11:21 -04:00
Victor Grau Serrat fb32e1ce5d Fixing DailyBarReader for volume to 0 instead of NaN 2017-10-17 23:16:49 -06:00
fredfortier 733f2c3433 Fixed an issue with writer retry 2017-10-18 00:18:07 -04:00
fredfortier 74fd4a6a0f Trying to fix an issue with merging new candles in get_history() 2017-10-18 00:04:55 -04:00
fredfortier 1a4dfe8abb Fixed date range issues and issues retrieving the benchmark data 2017-10-17 21:01:00 -04:00
fredfortier 6c17bbf0c9 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges
# Conflicts:
#	catalyst/exchange/exchange_bundle.py
2017-10-17 18:31:38 -04:00
fredfortier 4649b31d89 Fixed issues with daily bundles 2017-10-17 18:29:48 -04:00
Victor Grau Serrat ead6769ea2 retrieve benchmark from ExchangeBundle 2017-10-17 15:39:20 -06:00
fredfortier d21cc36bef Fixes a start date issue 2017-10-17 16:49:04 -04:00
fredfortier 105fee0fb9 Fixes to the daily data 2017-10-17 15:35:49 -04:00
Victor Grau Serrat a4389ffea4 download symbols.json when older than 1 day 2017-10-17 10:17:35 -06:00
fredfortier 989ffc57f1 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-17 03:00:45 -04:00
fredfortier 9bdd8aba48 Implemented daily data loader and related fixes 2017-10-17 03:00:36 -04:00
Victor Grau Serrat dadf7bd108 Merge branch 'concurrent-exchanges' of github.com:enigmampc/catalyst into concurrent-exchanges 2017-10-16 22:35:57 -06:00
Victor Grau Serrat e98d10c41b Fix floats for volume in data.history 2017-10-16 22:29:33 -06:00
fredfortier 1263fdd995 Testing related adjustments 2017-10-16 15:38:07 -04:00
fredfortier 1732b4a985 Testing related adjustments 2017-10-16 03:09:13 -04:00
fredfortier 403f951c77 Added unit tests 2017-10-15 05:11:44 -04:00
fredfortier bdbaad1c91 Improvements and fixes to the ingestion component 2017-10-14 02:06:26 -04:00
fredfortier c52653c84e Tested ingestion of minute data with a single market 2017-10-13 21:00:47 -04:00
fredfortier 93f4d31399 Unit tested ingestion of bundle chunks. This may not be stable yet. 2017-10-13 16:29:43 -04:00
fredfortier c658d15fcb Unit testing ingestion of bundles logic 2017-10-13 00:50:25 -04:00
fredfortier e1c2f40ab9 Making some adjustments to the ingestion method after discussion with Victor 2017-10-12 14:06:47 -04:00
Victor Grau Serrat 1a87d5a0c0 Making errors more verbose and user-friendly 2017-10-12 09:15:45 -06:00
Victor Grau Serrat 1dcfd169fa FIX: Raising Exceptions without traceback 2017-10-11 23:40:06 -06:00
Victor Grau Serrat 1c7fd19652 FIX: Raising Exceptions without traceback 2017-10-11 23:33:12 -06:00
Victor Grau Serrat c67cbedfbf FIX: Raising Exceptions without traceback 2017-10-11 23:31:08 -06:00
fredfortier 73378962aa Bug fixes and housekeeping from ingestion testing 2017-10-12 01:24:21 -04:00
fredfortier 4895bef392 Bug fixes and housekeeping from ingestion testing 2017-10-12 00:51:18 -04:00
fredfortier 3f9b44f3e4 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-11 22:05:37 -04:00
fredfortier c24918e2c8 Bug fixes 2017-10-11 22:05:29 -04:00
Victor Grau Serrat 01aeb88e8f Raising Exceptions without traceback 2017-10-11 17:05:27 -06:00
fredfortier c33bab673f Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges 2017-10-11 00:13:34 -04:00
fredfortier d3e33c44bf Reading data from bundles first and other fixes 2017-10-11 00:13:22 -04:00
Victor Grau Serrat de409efd3e API uses Catalyst naming convention 2017-10-10 14:22:49 -06:00
fredfortier 83af12c52c Added exchange get_history method which merge historical bars from the Catalyst and exchange APIs 2017-10-09 16:23:02 -04:00
fredfortier 8811aa669a Naive integration with the consolidated exchanges api (minor fix) 2017-10-09 14:52:59 -04:00
fredfortier 4f80ebee57 Naive integration with the consolidated exchanges api 2017-10-09 14:50:51 -04:00
fredfortier 403be97143 Integrating with history api 2017-10-08 02:27:13 -04:00
fredfortier 16cdc196b0 Minor fixes after merging 2017-10-08 01:18:40 -04:00
fredfortier 1d79e88312 Merge remote-tracking branch 'origin/concurrent-exchanges' into concurrent-exchanges
# Conflicts:
#	catalyst/exchange/bundle_utils.py
2017-10-08 01:15:47 -04:00
fredfortier 3335ae0ea9 Refactored the data portal to use the exchange bundles 2017-10-08 01:13:47 -04:00
Victor Grau Serrat a1cf00e6fe updated symbols.json for 3 exchanges: end_daily, end_minute 2017-10-06 21:03:31 -06:00
fredfortier 0cc9d839d0 Optimize the existing data filter to filter by asset. 2017-10-06 15:13:40 -04:00
fredfortier a004a01cdb Skipping data chunks if they already exist (fix) 2017-10-06 14:33:05 -04:00
fredfortier 04fc7855d5 Skipping data chunks if they already exist 2017-10-06 14:29:25 -04:00
fredfortier 50f075792c Tested ingestion after refactoring 2017-10-05 21:03:39 -04:00
Victor Grau Serrat 14f8c25c89 get_history against AWS API 2017-10-05 17:28:19 -06:00
fredfortier 874968bbbb Refactoring the exchange bundle for incremental loading 2017-10-05 18:06:17 -04:00
fredfortier 751608c8ab Mocking Victor's history API service 2017-10-04 22:35:07 -04:00
Victor Grau Serrat 8b141a0c28 Fix floats for volume in data.history 2017-10-03 09:11:59 -06:00
Victor Grau Serrat e11ecf9d78 Added 'live' mode to CLI instead of option to 'run' 2017-09-29 13:38:58 -06:00
Victor Grau Serrat b45339692f poloniex autogeneration of symbols.json with optional sourcing of start_date 2017-09-28 16:14:27 -06:00
Victor Grau Serrat 336f062794 poloniex autogeneration of symbols.json with cached start_date 2017-09-28 14:42:47 -06:00
Victor Grau Serrat 6d8b8307a1 bitfinex autogeneration of symbols.json with optional sourcing of start_date 2017-09-28 14:14:34 -06:00
Victor Grau Serrat 3b681d197d Purge 5-min implementation 2017-09-28 11:03:47 -06:00
Victor Grau Serrat 15fa98420d Catching bitfinex Error: No JSON object could be decoded 2017-09-28 09:14:22 -06:00
fredfortier 9dfefec13c Merge branch 'concurrent-exchanges' of github.com:enigmampc/catalyst into concurrent-exchanges 2017-09-27 17:31:44 -04:00
fredfortier 6bfe0eecd2 Remove some 5-minute data and added example of extension.py. 2017-09-27 17:27:40 -04:00
Victor Grau Serrat 3362dbf95c WIP: Poloniex exchange - placing orders, executing transactions 2017-09-27 14:31:15 -06:00
Victor Grau Serrat 1d0faf693d WIP: Poloniex exchange - create order 2017-09-26 15:36:14 -06:00
Victor Grau Serrat 87ecf6114d adding min_trade_size in TradingPair 2017-09-26 13:32:23 -06:00
Victor Grau Serrat 2c2c861a8f WIP: Poloniex exchange - fix for multiple exchanges 2017-09-26 11:40:25 -06:00
Victor Grau Serrat fef08d1433 Merge branch 'poloniex-exchange' into concurrent-exchanges 2017-09-26 10:53:56 -06:00
Victor Grau Serrat cf20f78e55 WIP: Poloniex exchange - balances, candles & cancel 2017-09-25 22:01:04 -06:00
Victor Grau Serrat 5d1bdee4a6 WIP: Poloniex exchange - generating symbols.json 2017-09-25 14:35:58 -06:00
Victor Grau Serrat f60abcd636 WIP: Poloniex exchange class 2017-09-25 11:28:06 -06:00
fredfortier 4798fc75fb Housekeeping and documentation 2017-09-25 12:18:22 -04:00
fredfortier d6996b1e93 Refinements and documentation. 2017-09-23 04:49:13 -04:00
fredfortier bc65c10fc6 Implemented and tested the history() method in backtest mode. 2017-09-22 23:17:38 -04:00
Victor Grau Serrat 27f20a090a matplotlib imports inside init live_graph_clock (2) 2017-09-22 12:03:17 -06:00
Victor Grau Serrat 75c2753b98 matplotlib imports inside init live_graph_clock 2017-09-22 11:59:57 -06:00
Victor Grau Serrat a6b873508b Merge branch 'aws-symbols-json' into develop 2017-09-22 10:57:58 -06:00
Victor Grau Serrat daf3c4d285 Autogeneration of symbols.json for bittrex 2017-09-22 10:55:49 -06:00
Victor Grau Serrat 8cabb33372 Autogeneration of symbols.json for bitfinex 2017-09-22 09:54:51 -06:00
fredfortier ddecd6bb48 First working version with the backtest and live modes executing the same algorithm. 2017-09-21 19:05:16 -04:00
fredfortier 2f8768bb06 Merged Victor's hack for the minute writer precision 2017-09-21 16:36:10 -04:00
Victor Grau Serrat df8ba90236 {exchange}/symbols.json moved to AWS 2017-09-21 12:43:29 -06:00
VictorandGitHub 7f602d7fcc Update requirements.txt 2017-09-21 11:27:35 -06:00
Victor Grau Serrat 6baf4c2122 Merge branch 'master' into develop 2017-09-20 23:40:04 -06:00
Victor Grau Serrat 1f56325895 fix price resolution in 1-minute data bundle: 8 decimal places 2017-09-20 23:37:55 -06:00
fredfortier 7335810cc2 Defined the same commission model as with equities for now. We need to fix the data precision in the bundles. 2017-09-21 01:17:10 -04:00
fredfortier 10a5b5412e Testing the same algo in live and backtest mode. Most of it works well. We need a commission model for the TradingPair currency type. 2017-09-20 23:48:57 -04:00
Victor Grau Serrat c5cbbce8e1 Merge branch 'master' into develop 2017-09-20 16:23:21 -06:00
Victor Grau Serrat 7359cdc48f fix data.history error with tz-aware dataframe 2017-09-20 16:20:57 -06:00
fredfortier 4e2d092123 Trying to fix an issue with periodical bars 2017-09-20 18:00:08 -04:00
Victor Grau Serrat 42566ca92c Merge branch 'master' of github.com:enigmampc/catalyst 2017-09-20 15:37:44 -06:00
Victor Grau Serrat 09bf875d6c Merge branch 'develop': adds 1-min OHLCV data resolution, fractional coins and 9 decimals of price resolution 2017-09-20 15:20:30 -06:00
Victor Grau Serrat b354837b83 Merge branch 'poloniex-1min-curate' into develop 2017-09-20 15:17:06 -06:00
Victor Grau Serrat a1bc174740 Wrapping up 1min data for Poloniex in backtesting 2017-09-20 15:11:18 -06:00
VictorandGitHub ea27346876 Merge pull request #34 from abnera/patch-1
Added environment.yml for simpler conda installation
2017-09-20 12:50:56 -06:00
Victor Grau Serrat 81bd2d84f0 >=0.2.dev2 for catalyst, since we're in active dev and will change periodically 2017-09-20 12:47:31 -06:00
Abner Ayala-AcevedoandGitHub 06f48cf158 Update to include python-dev 2017-09-20 10:41:42 -07:00
Abner Ayala-AcevedoandGitHub 05a69cfc92 Added environment.yml for simpler conda installation
Simpler conda installation by using environment.yml requirements.
`conda env create -f python2.7-environment.yml`
`Linux or Mac: source activate catalyst`
`Windows: activate catalyst`
2017-09-20 10:16:29 -07:00
Victor Grau Serrat 36c2564bb0 splitting plot styles: dark for live, default for backtesting 2017-09-20 11:05:09 -06:00
Victor Grau Serrat 91e71c5e38 WIP: bundling 1min data 2017-09-20 09:15:39 -06:00
fredfortier 3b655d466e Unit tested exchange loader extension and backtest data portal refactoring 2017-09-20 05:11:54 -04:00
fredfortier 68546a0d8d Experimenting with simpler bundle and data portal approach (works in unit testing) 2017-09-19 03:49:34 -04:00
fredfortier b70ff3a740 Bug fixes and working on unit tests for the data portal 2017-09-18 22:19:27 -04:00
fredfortier 18bfaff7c9 Trying to stabilize refactoring an last few commits (still unstable) 2017-09-18 15:37:10 -04:00
fredfortier 555b7e95b5 Working on adjusted the DataPortal class (unstable) 2017-09-18 14:51:01 -04:00
fredfortier 1d6336afda Splitting the exchange_algorithm class to allow access to the symbol() method in backtesting mode 2017-09-18 14:48:24 -04:00
fredfortier 394777217d Splitting the exchange_algorithm class to allow access to the symbol() method in backtesting mode 2017-09-18 13:56:30 -04:00
Victor Grau Serrat e761433d06 Merge branch 'poloniex-1min-curate' of github.com:enigmampc/catalyst into poloniex-1min-curate 2017-09-18 09:45:12 -06:00
Victor Grau Serrat 6fddb92563 WIP: trades to disk - no append/no ingestion 2017-09-18 09:44:19 -06:00
Victor Grau Serrat 4a4277d9d1 WIP: curating 1min Poloniex data - no append 2017-09-18 09:44:19 -06:00
Victor Grau Serrat 3361b09ac2 Merge branch 'fractional-coins' into develop 2017-09-15 16:06:30 -06:00
fredfortier 5a345a3abb Documentation and cleanup from meeting with Victor 2017-09-15 18:00:15 -04:00
Victor Grau Serrat 01eefd67e0 ingestion switch to create_writers when ingesting locally 2017-09-15 10:51:04 -06:00
Victor Grau Serrat d124125258 WIP: trades to disk - no append/no ingestion 2017-09-15 09:32:01 -06:00
Victor Grau Serrat 72e07e242f WIP: curating 1min Poloniex data - no append 2017-09-15 09:32:01 -06:00
Victor Grau Serrat c3897cfa5a ENH: wrapping up asset min_trade_size for fractional coinsup to 1/100000000th of a coin 2017-09-14 15:22:51 -06:00
Andrew CampbellandVictor Grau Serrat e5a137f205 ENH: Bound trade amount with asset specific min trade size 2017-09-14 10:19:05 -06:00
Victor Grau Serrat 48143d3212 WIP: Fixes 1/1000 price issue in history, and works with full coins. Requires matching-version 'catalyst ingest' 2017-09-13 17:22:11 -06:00
fredfortier ff0dc5cff9 Polishing the sample arbitrage algo 2017-09-12 14:25:04 -04:00
fredfortier 41d9bbca1b Adjustments to the sample arbitrage algo 2017-09-11 18:20:28 -04:00
fredfortier 3e2a8dd78b Adjustments to the sample arbitrage algo 2017-09-11 18:03:58 -04:00
fredfortier 7e280aeb5c Working on multiple exchanges and a sample algo for arbitrage 2017-09-10 20:20:34 -04:00
fredfortier 36881b03e2 Working on multi-exchange implementation (not fully tested) 2017-09-07 23:54:11 -04:00
fredfortierandVictor Grau Serrat ad2d0e9253 Fixed path issue with obsolete branch 2017-09-07 13:26:09 -06:00
fredfortier 8850657f26 Fixed path issue with obsolete branch 2017-09-07 14:26:55 -04:00
fredfortier 6e6c62533b Fixes to the graph timeline axis 2017-09-05 11:15:34 -04:00
fredfortier 6a98a937dd Minor fix in the graph logic 2017-09-05 01:15:40 -04:00
fredfortier c4900af088 Minor fix in the graph logic 2017-09-05 00:58:11 -04:00
fredfortier 6e3017010f Added the initial version of a live graph 2017-09-05 00:51:38 -04:00
fredfortier 7247b761d5 Fixed an issue with failed orders 2017-09-04 11:33:21 -04:00
fredfortier a58e3522a9 Improved handling of insufficient funds on bittrex 2017-09-04 11:30:32 -04:00
fredfortier ad95369028 Improved handling of insufficient funds on bittrex 2017-09-04 11:23:40 -04:00
Victor Grau Serrat d64f5275ef Merge branch 'develop' 2017-09-03 11:46:57 -06:00
Victor Grau Serrat 4f4f6c050b Merge branch 'exchange-trading' into develop 2017-09-03 11:44:15 -06:00
fredfortier fdd6b62963 Removing run_algorithm() from examples 2017-09-03 13:25:47 -04:00
fredfortier bcb5fd2b14 Optimizing algorithm initialization 2017-09-03 13:05:18 -04:00
Victor Grau Serrat c5e1945558 fix command line run for exchange-trading 2017-09-01 22:35:24 -06:00
fredfortier 11144d83b8 Fixed issue with defining the exchange in run_algo 2017-09-01 11:34:38 -04:00
Victor Grau Serrat 85c2e9db4f Merge branch 'exchange-trading' of github.com:enigmampc/catalyst into exchange-trading 2017-09-01 09:28:48 -06:00
Victor Grau Serrat 817cb07bee minor fix reference-currency -> base-currency 2017-09-01 09:24:34 -06:00
fredfortier 8054d1d520 Fixed issue with spot price on Bittrex 2017-09-01 11:17:43 -04:00
fredfortier c1d7022846 Fixed issue with Bittrex order 2017-09-01 10:39:49 -04:00
fredfortier 8f3c440bac Created wiki documentation 2017-08-31 14:14:54 -04:00
fredfortier a785607d8f Fixed a bug with sell orders and added documentation 2017-08-31 13:15:43 -04:00
fredfortier 6e166383ed Fixed bug with order execution 2017-08-31 12:47:09 -04:00
fredfortier 1696c39912 Bug fix in symbol loader 2017-08-31 12:34:08 -04:00
fredfortier d79fdca561 Bug fix in symbol loader 2017-08-31 12:22:04 -04:00
fredfortier 8b6a48633d Poloshing unit tests and finalizing Bittrex implementation 2017-08-30 17:09:13 -04:00
fredfortier d03ce37f6e Poloshing unit tests and finalizing Bittrex implementation 2017-08-30 08:51:31 -04:00
fredfortier c47e88c26f More unit testing and refactoring related to the Bittrex addition 2017-08-29 16:05:38 -04:00
fredfortier f4db9f7b1e Working on Bittrex implementation and unit tests 2017-08-28 22:50:46 -04:00
fredfortier 753881bade Bug fixes and polishing stats 2017-08-28 22:00:31 -04:00
fredfortier 1be39f97a1 Refactoring to optimize multiple exchanges 2017-08-28 16:19:03 -04:00
Victor Grau Serrat 49bfd32341 Merge branch 'develop' 2017-08-28 13:20:20 -06:00
Victor Grau Serrat 01473e5146 Fixes 1000 scaling price issue 2017-08-28 13:15:31 -06:00
Victor Grau Serrat 16f9ab3ba5 Retrieve cryptobenchmark from bundle, instead of Polo 2017-08-28 12:20:54 -06:00
fredfortier b4755111a9 Extended Asset to create TradingPair allowing us to store leverage value 2017-08-28 11:25:01 -04:00
fredfortier cf54806843 Extended Asset to create TradingPair allowing us to store leverage value 2017-08-28 10:25:46 -04:00
fredfortier c01f2a39a4 Initial work on bittrex implementation 2017-08-28 00:27:10 -04:00
fredfortier 1cfcb1d96e Initial work on bittrex implementation 2017-08-27 23:26:48 -04:00
fredfortier c40fd98022 Adjusted common files 2017-08-27 18:06:48 -04:00
fredfortier b8d442cf89 Creating a clean branch for live trading 2017-08-27 15:19:13 -04:00
Victor Grau Serrat 3a44a3cc1f Date fix for treasury_data, starts 1990-01-02 2017-08-25 12:44:43 -06:00
Victor Grau Serrat 24fd0fa6f8 Fix issue #28: buy_and_hodl.py example 2017-08-25 09:29:25 -06:00
Victor Grau Serrat b2e5b5f73d Dropbox -> AWS switch for Poloniex bundle 2017-08-24 12:55:33 -06:00
Victor Grau Serrat 0ef8b341ca Merge branch 'develop' 2017-08-17 23:13:04 -06:00
Victor Grau Serrat 753ca1db5a fix issue #27: create_writers=False except when bundling 2017-08-17 23:06:32 -06:00
156 changed files with 36307 additions and 5602 deletions
+1
View File
@@ -40,6 +40,7 @@ develop-eggs
coverage.xml
htmlcov
nosetests.xml
.python-version
# C Extensions
*.o
+3 -3
View File
@@ -11,13 +11,13 @@
#
# https://127.0.0.1
#
# default password is jupyter. to provide another, see:
# Default password is 'jupyter'. To provide another, see:
# http://jupyter-notebook.readthedocs.org/en/latest/public_server.html#preparing-a-hashed-password
#
# once generated, you can pass the new value via `docker run --env` the first time
# Once generated, you can pass the new value via `docker run --env` the first time
# you start the container.
#
# You can also run an algo using the docker exec command. For example:
# You can also run an algo using the docker exec command. For example:
#
# docker exec -it catalyst catalyst run -f /projects/my_algo.py --start 2015-1-1 --end 2016-1-1 /projects/result.pickle
#
+4 -4
View File
@@ -5,7 +5,7 @@
#
# Note: the dev build requires a quantopian/catalyst image, which you can build as follows:
#
# docker build -t quantopian/catalyst -f Dockerfile
# docker build -t quantopian/catalyst -f Dockerfile .
#
# To run the container:
#
@@ -15,13 +15,13 @@
#
# https://127.0.0.1
#
# default password is jupyter. to provide another, see:
# Default password is 'jupyter'. To provide another, see:
# http://jupyter-notebook.readthedocs.org/en/latest/public_server.html#preparing-a-hashed-password
#
# once generated, you can pass the new value via `docker run --env` the first time
# Once generated, you can pass the new value via `docker run --env` the first time
# you start the container.
#
# You can also run an algo using the docker exec command. For example:
# You can also run an algo using the docker exec command. For example:
#
# docker exec -it catalystdev catalyst run -f /projects/my_algo.py --start 2015-1-1 --end 2016-1-1 /projects/result.pickle
#
+72 -1
View File
@@ -1 +1,72 @@
All the documentation for `Catalyst <https://github.com/enigmampc/catalyst>`_ can be found in the `catalyst-docs wiki <https://github.com/enigmampc/catalyst-docs/wiki>`_.
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
:target: https://enigmampc.github.io/catalyst
:align: center
:alt: Enigma | Catalyst
|version tag|
|version status|
|discord|
|twitter|
|
Catalyst is an algorithmic trading library for crypto-assets written in Python.
It allows trading strategies to be easily expressed and backtested against
historical data (with daily and minute resolution), providing analytics and
insights regarding a particular strategy's performance. Catalyst also supports
live-trading of crypto-assets starting with three exchanges (Bitfinex, Bittrex,
and Poloniex) with more being added over time. Catalyst empowers users to share
and curate data and build profitable, data-driven investment strategies. Please
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst, or
refer to the `whitepaper <https://www.enigma.co/enigma_catalyst.pdf>`_ for
further technical details.
Catalyst builds on top of the well-established
`Zipline <https://github.com/quantopian/zipline>`_ project. We did our best to
minimize structural changes to the general API to maximize compatibility with
existing trading algorithms, developer knowledge, and tutorials. Join us on
`Discord <https://discord.gg/SJK32GY>`_ where we have a *#catalyst_dev* channel
for questions around Catalyst, algorithmic trading and technical support.
Overview
========
- Ease of use: Catalyst tries to get out of your way so that you can
focus on algorithm development. See
`examples of trading strategies <https://github.com/enigmampc/catalyst/tree/master/catalyst/examples>`_
provided.
- Support for several of the top crypto-exchanges by trading volume:
`Bitfinex <https://www.bitfinex.com>`_, `Bittrex <http://www.bittrex.com>`_,
and `Poloniex <https://www.poloniex.com>`_.
- Secure: You and only you have access to each exchange API keys for your accounts.
- Input of historical pricing data of all crypto-assets by exchange,
with daily and minute resolution. See
`Catalyst Market Coverage Overview <https://www.enigma.co/catalyst/status>`_.
- Backtesting and live-trading functionality, with a seamless transition
between the two modes.
- Output of performance statistics are based on Pandas DataFrames to
integrate nicely into the existing PyData eco-system.
- Statistic and machine learning libraries like matplotlib, scipy,
statsmodels, and sklearn support development, analysis, and
visualization of state-of-the-art trading systems.
- Addition of Bitcoin price (btc_usdt) as a benchmark for comparing
performance across trading algorithms.
Go to our `Documentation Website <https://enigmampc.github.io/catalyst/>`_.
.. |version tag| image:: https://img.shields.io/pypi/v/enigma-catalyst.svg
:target: https://pypi.python.org/pypi/enigma-catalyst
.. |version status| image:: https://img.shields.io/pypi/pyversions/enigma-catalyst.svg
:target: https://pypi.python.org/pypi/enigma-catalyst
.. |discord| image:: https://img.shields.io/badge/discord-join%20chat-green.svg
:target: https://discordapp.com/invite/SJK32GY
.. |twitter| image:: https://img.shields.io/twitter/follow/enigmampc.svg?style=social&label=Follow&style=flat-square
:target: https://twitter.com/enigmampc
+4 -11
View File
@@ -29,11 +29,14 @@ from ._version import get_versions
from . algorithm import TradingAlgorithm
from . import api
from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher
__version__ = get_versions()['version']
del get_versions
# PERF: Fire a warning if calendars were instantiated during catalyst import.
# Having calendars doesn't break anything per-se, but it makes catalyst imports
# noticeably slower, which becomes particularly noticeable in the Zipline CLI.
from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher
if global_calendar_dispatcher._calendars:
import warnings
warnings.warn(
@@ -44,10 +47,6 @@ if global_calendar_dispatcher._calendars:
del global_calendar_dispatcher
__version__ = get_versions()['version']
del get_versions
def load_ipython_extension(ipython):
from .__main__ import catalyst_magic
ipython.register_magic_function(catalyst_magic, 'line_cell', 'catalyst')
@@ -69,7 +68,6 @@ if os.name == 'nt':
_()
del _
__all__ = [
'TradingAlgorithm',
'api',
@@ -79,9 +77,4 @@ __all__ = [
'gens',
'run_algorithm',
'utils',
'exchange',
]
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
+388 -66
View File
@@ -3,11 +3,14 @@ import os
from functools import wraps
import click
import sys
import logbook
import pandas as pd
from six import text_type
from catalyst.data import bundles as bundles_module
from catalyst.exchange.exchange_bundle import ExchangeBundle
from catalyst.exchange.utils.exchange_utils import delete_algo_folder
from catalyst.utils.cli import Date, Timestamp
from catalyst.utils.run_algo import _run, load_extensions
@@ -27,17 +30,19 @@ except NameError:
@click.option(
'--strict-extensions/--non-strict-extensions',
is_flag=True,
help='If --strict-extensions is passed then catalyst will not run if it'
' cannot load all of the specified extensions. If this is not passed or'
' --non-strict-extensions is passed then the failure will be logged but'
' execution will continue.',
help='If --strict-extensions is passed then catalyst will not run '
'if it cannot load all of the specified extensions. If this is '
'not passed or --non-strict-extensions is passed then the '
'failure will be logged but execution will continue.',
)
@click.option(
'--default-extension/--no-default-extension',
is_flag=True,
default=True,
help="Don't load the default catalyst extension.py file in $ZIPLINE_HOME.",
help="Don't load the default catalyst extension.py file "
"in $CATALYST_HOME.",
)
@click.version_option()
def main(extension, strict_extensions, default_extension):
"""Top level catalyst entry point.
"""
@@ -120,13 +125,13 @@ def ipython_only(option):
'--define',
multiple=True,
help="Define a name to be bound in the namespace before executing"
" the algotext. For example '-Dname=value'. The value may be any python"
" expression. These are evaluated in order so they may refer to previously"
" defined names.",
" the algotext. For example '-Dname=value'. The value may be"
" any python expression. These are evaluated in order so they"
" may refer to previously defined names.",
)
@click.option(
'--data-frequency',
type=click.Choice({'daily', '5-minute', 'minute'}),
type=click.Choice({'daily', 'minute'}),
default='daily',
show_default=True,
help='The data frequency of the simulation.',
@@ -134,7 +139,6 @@ def ipython_only(option):
@click.option(
'--capital-base',
type=float,
default=10e6,
show_default=True,
help='The starting capital for the simulation.',
)
@@ -172,8 +176,8 @@ def ipython_only(option):
default='-',
metavar='FILENAME',
show_default=True,
help="The location to write the perf data. If this is '-' the perf will"
" be written to stdout.",
help="The location to write the perf data. If this is '-' the perf"
" will be written to stdout.",
)
@click.option(
'--print-algo/--no-print-algo',
@@ -181,39 +185,26 @@ def ipython_only(option):
default=False,
help='Print the algorithm to stdout.',
)
@click.option(
'-s',
'--start',
type=Date(tz='utc', as_timestamp=True),
help='The start date of the simulation.',
)
@ipython_only(click.option(
'--local-namespace/--no-local-namespace',
is_flag=True,
default=None,
help='Should the algorithm methods be resolved in the local namespace.'
))
@click.option(
'--live/--no-live',
is_flag=True,
default=False,
help='Enable live trading.',
)
@click.option(
'-x',
'--exchange-name',
type=click.Choice({'bitfinex'}),
help='The name of the exchange (supported: bitfinex).',
help='The name of the targeted exchange.',
)
@click.option(
'-n',
'--algo-name',
help='A label assigned to the algorithm for tracking purposes.',
'--algo-namespace',
help='A label assigned to the algorithm for data storage purposes.'
)
@click.option(
'-c',
'--reference-currency',
help='The reference currency used to calculate statistics '
'--base-currency',
help='The base currency used to calculate statistics '
'(e.g. usd, btc, eth).',
)
@click.pass_context
@@ -230,43 +221,45 @@ def run(ctx,
output,
print_algo,
local_namespace,
live,
exchange_name,
algo_namespace,
base_currency):
"""Run a backtest for the given algorithm.
"""
if live:
if exchange_name is None:
ctx.fail("must specify an exchange name '-x' in live execution "
"mode '--live'")
if algo_namespace is None:
ctx.fail("must specify an algorithm name '-n' in live execution "
"mode '--live'")
if base_currency is None:
ctx.fail("must specify a reference currency '-c' in live "
"execution mode '--live'")
else:
# check that the start and end dates are passed correctly
if start is None and end is None:
# check both at the same time to avoid the case where a user
# does not pass either of these and then passes the first only
# to be told they need to pass the second argument also
ctx.fail(
"must specify dates with '-s' / '--start' and '-e' / '--end'",
)
if start is None:
ctx.fail("must specify a start date with '-s' / '--start'")
if end is None:
ctx.fail("must specify an end date with '-e' / '--end'")
if (algotext is not None) == (algofile is not None):
ctx.fail(
"must specify exactly one of '-f' / '--algofile' or"
" '-t' / '--algotext'",
)
# check that the start and end dates are passed correctly
if start is None and end is None:
# check both at the same time to avoid the case where a user
# does not pass either of these and then passes the first only
# to be told they need to pass the second argument also
ctx.fail(
"must specify dates with '-s' / '--start' and '-e' / '--end'"
" in backtest mode",
)
if start is None:
ctx.fail("must specify a start date with '-s' / '--start'"
" in backtest mode")
if end is None:
ctx.fail("must specify an end date with '-e' / '--end'"
" in backtest mode")
if exchange_name is None:
ctx.fail("must specify an exchange name '-x'")
if base_currency is None:
ctx.fail("must specify a base currency with '-c' in backtest mode")
if capital_base is None:
ctx.fail("must specify a capital base with '--capital-base'")
click.echo('Running in backtesting mode.', sys.stdout)
perf = _run(
initialize=None,
handle_data=None,
@@ -286,14 +279,19 @@ def run(ctx,
print_algo=print_algo,
local_namespace=local_namespace,
environ=os.environ,
live=live,
live=False,
exchange=exchange_name,
algo_namespace=algo_namespace,
base_currency=base_currency
base_currency=base_currency,
analyze_live=None,
live_graph=False,
simulate_orders=True,
auth_aliases=None,
stats_output=None,
)
if output == '-':
click.echo(str(perf))
click.echo(str(perf), sys.stdout)
elif output != os.devnull: # make the catalyst magic not write any data
perf.to_pickle(output)
@@ -333,15 +331,329 @@ def catalyst_magic(line, cell=None):
raise ValueError('main returned non-zero status code: %d' % e.code)
@main.command()
@click.option(
'-f',
'--algofile',
default=None,
type=click.File('r'),
help='The file that contains the algorithm to run.',
)
@click.option(
'--capital-base',
type=float,
show_default=True,
help='The amount of capital (in base_currency) allocated to trading.',
)
@click.option(
'-t',
'--algotext',
help='The algorithm script to run.',
)
@click.option(
'-D',
'--define',
multiple=True,
help="Define a name to be bound in the namespace before executing"
" the algotext. For example '-Dname=value'. The value may be"
" any python expression. These are evaluated in order so they"
" may refer to previously defined names.",
)
@click.option(
'-o',
'--output',
default='-',
metavar='FILENAME',
show_default=True,
help="The location to write the perf data. If this is '-' the perf will"
" be written to stdout.",
)
@click.option(
'--print-algo/--no-print-algo',
is_flag=True,
default=False,
help='Print the algorithm to stdout.',
)
@ipython_only(click.option(
'--local-namespace/--no-local-namespace',
is_flag=True,
default=None,
help='Should the algorithm methods be resolved in the local namespace.'
))
@click.option(
'-x',
'--exchange-name',
help='The name of the targeted exchange.',
)
@click.option(
'-n',
'--algo-namespace',
help='A label assigned to the algorithm for data storage purposes.'
)
@click.option(
'-c',
'--base-currency',
help='The base currency used to calculate statistics '
'(e.g. usd, btc, eth).',
)
@click.option(
'-e',
'--end',
type=Date(tz='utc', as_timestamp=True),
help='An optional end date at which to stop the execution.',
)
@click.option(
'--live-graph/--no-live-graph',
is_flag=True,
default=False,
help='Display live graph.',
)
@click.option(
'--simulate-orders/--no-simulate-orders',
is_flag=True,
default=True,
help='Simulating orders enable the paper trading mode. No orders will be '
'sent to the exchange unless set to false.',
)
@click.option(
'--auth-aliases',
default=None,
help='Authentication file aliases for the specified exchanges. By default,'
'each exchange uses the "auth.json" file in the exchange folder. '
'Specifying an "auth2" alias would use "auth2.json". It should be '
'specified like this: "[exchange_name],[alias],..." For example, '
'"binance,auth2" or "binance,auth2,bittrex,auth2".',
)
@click.pass_context
def live(ctx,
algofile,
capital_base,
algotext,
define,
output,
print_algo,
local_namespace,
exchange_name,
algo_namespace,
base_currency,
end,
live_graph,
auth_aliases,
simulate_orders):
"""Trade live with the given algorithm.
"""
if (algotext is not None) == (algofile is not None):
ctx.fail(
"must specify exactly one of '-f' / '--algofile' or"
" '-t' / '--algotext'",
)
if exchange_name is None:
ctx.fail("must specify an exchange name '-x'")
if algo_namespace is None:
ctx.fail("must specify an algorithm name '-n' in live execution mode")
if base_currency is None:
ctx.fail("must specify a base currency '-c' in live execution mode")
if capital_base is None:
ctx.fail("must specify a capital base with '--capital-base'")
if simulate_orders:
click.echo('Running in paper trading mode.', sys.stdout)
else:
click.echo('Running in live trading mode.', sys.stdout)
perf = _run(
initialize=None,
handle_data=None,
before_trading_start=None,
analyze=None,
algofile=algofile,
algotext=algotext,
defines=define,
data_frequency=None,
capital_base=capital_base,
data=None,
bundle=None,
bundle_timestamp=None,
start=None,
end=end,
output=output,
print_algo=print_algo,
local_namespace=local_namespace,
environ=os.environ,
live=True,
exchange=exchange_name,
algo_namespace=algo_namespace,
base_currency=base_currency,
live_graph=live_graph,
analyze_live=None,
simulate_orders=simulate_orders,
auth_aliases=auth_aliases,
stats_output=None,
)
if output == '-':
click.echo(str(perf), sys.stdout)
elif output != os.devnull: # make the catalyst magic not write any data
perf.to_pickle(output)
return perf
@main.command(name='ingest-exchange')
@click.option(
'-x',
'--exchange-name',
help='The name of the exchange bundle to ingest.',
)
@click.option(
'-f',
'--data-frequency',
type=click.Choice({'daily', 'minute', 'daily,minute', 'minute,daily'}),
default='daily',
show_default=True,
help='The data frequency of the desired OHLCV bars.',
)
@click.option(
'-s',
'--start',
default=None,
type=Date(tz='utc', as_timestamp=True),
help='The start date of the data range. (default: one year from end date)',
)
@click.option(
'-e',
'--end',
default=None,
type=Date(tz='utc', as_timestamp=True),
help='The end date of the data range. (default: today)',
)
@click.option(
'-i',
'--include-symbols',
default=None,
help='A list of symbols to ingest (optional comma separated list)',
)
@click.option(
'--exclude-symbols',
default=None,
help='A list of symbols to exclude from the ingestion '
'(optional comma separated list)',
)
@click.option(
'--csv',
default=None,
help='The path of a CSV file containing the data. If specified, start, '
'end, include-symbols and exclude-symbols will be ignored. Instead,'
'all data in the file will be ingested.',
)
@click.option(
'--show-progress/--no-show-progress',
default=True,
help='Print progress information to the terminal.'
)
@click.option(
'--verbose/--no-verbose`',
default=False,
help='Show a progress indicator for every currency pair.'
)
@click.option(
'--validate/--no-validate`',
default=False,
help='Report potential anomalies found in data bundles.'
)
@click.pass_context
def ingest_exchange(ctx, exchange_name, data_frequency, start, end,
include_symbols, exclude_symbols, csv, show_progress,
verbose, validate):
"""
Ingest data for the given exchange.
"""
if exchange_name is None:
ctx.fail("must specify an exchange name '-x'")
exchange_bundle = ExchangeBundle(exchange_name)
click.echo('Ingesting exchange bundle {}...'.format(exchange_name), sys.stdout)
exchange_bundle.ingest(
data_frequency=data_frequency,
include_symbols=include_symbols,
exclude_symbols=exclude_symbols,
start=start,
end=end,
show_progress=show_progress,
show_breakdown=verbose,
show_report=validate,
csv=csv
)
@main.command(name='clean-algo')
@click.option(
'-n',
'--algo-namespace',
help='The label of the algorithm to for which to clean the state.'
)
@click.pass_context
def clean_algo(ctx, algo_namespace):
click.echo(
'Cleaning algo state: {}'.format(algo_namespace),
sys.stdout
)
delete_algo_folder(algo_namespace)
click.echo('Done', sys.stdout)
@main.command(name='clean-exchange')
@click.option(
'-x',
'--exchange-name',
help='The name of the exchange bundle to ingest.',
)
@click.option(
'-f',
'--data-frequency',
type=click.Choice({'daily', 'minute'}),
default=None,
help='The bundle data frequency to remove. If not specified, it will '
'remove both daily and minute bundles.',
)
@click.pass_context
def clean_exchange(ctx, exchange_name, data_frequency):
"""Clean up bundles from 'ingest-exchange'.
"""
if exchange_name is None:
ctx.fail("must specify an exchange name '-x'")
exchange_bundle = ExchangeBundle(exchange_name)
click.echo('Cleaning exchange bundle {}...'.format(exchange_name), sys.stdout)
exchange_bundle.clean(
data_frequency=data_frequency,
)
click.echo('Done', sys.stdout)
@main.command()
@click.option(
'-b',
'--bundle',
default='poloniex',
metavar='BUNDLE-NAME',
show_default=True,
default=None,
show_default=False,
help='The data bundle to ingest.',
)
@click.option(
'-x',
'--exchange-name',
help='The name of the exchange bundle to ingest.',
)
@click.option(
'-c',
'--compile-locally',
@@ -360,9 +672,12 @@ def catalyst_magic(line, cell=None):
default=True,
help='Print progress information to the terminal.'
)
def ingest(bundle, compile_locally, assets_version, show_progress):
@click.pass_context
def ingest(ctx, bundle, exchange_name, compile_locally, assets_version,
show_progress):
"""Ingest the data for the given bundle.
"""
bundles_module.ingest(
bundle,
os.environ,
@@ -382,19 +697,26 @@ def ingest(bundle, compile_locally, assets_version, show_progress):
show_default=True,
help='The data bundle to clean.',
)
@click.option(
'-x',
'--exchange_name',
metavar='EXCHANGE-NAME',
show_default=True,
help='The exchange bundle name to clean.',
)
@click.option(
'-e',
'--before',
type=Timestamp(),
help='Clear all data before TIMESTAMP.'
' This may not be passed with -k / --keep-last',
' This may not be passed with -k / --keep-last',
)
@click.option(
'-a',
'--after',
type=Timestamp(),
help='Clear all data after TIMESTAMP'
' This may not be passed with -k / --keep-last',
' This may not be passed with -k / --keep-last',
)
@click.option(
'-k',
@@ -402,10 +724,10 @@ def ingest(bundle, compile_locally, assets_version, show_progress):
type=int,
metavar='N',
help='Clear all but the last N downloads.'
' This may not be passed with -e / --before or -a / --after',
' This may not be passed with -e / --before or -a / --after',
)
def clean(bundle, before, after, keep_last):
"""Clean up data downloaded with the ingest command.
"""Clean up bundles from 'ingest'.
"""
bundles_module.clean(
bundle,
@@ -436,7 +758,7 @@ def bundles():
# because there were no entries, print a single message indicating that
# no ingestions have yet been made.
for timestamp in ingestions or ["<no ingestions>"]:
click.echo("%s %s" % (bundle, timestamp))
click.echo("%s %s" % (bundle, timestamp), sys.stdout)
if __name__ == '__main__':
+21 -52
View File
@@ -124,7 +124,7 @@ from catalyst.utils.events import (
from catalyst.utils.factory import create_simulation_parameters
from catalyst.utils.math_utils import (
tolerant_equals,
round_if_near_integer,
round_nearest
)
from catalyst.utils.pandas_utils import clear_dataframe_indexer_caches
from catalyst.utils.preprocess import preprocess
@@ -133,15 +133,13 @@ from catalyst.utils.security_list import SecurityList
import catalyst.protocol
from catalyst.sources.requests_csv import PandasRequestsCSV
from catalyst.gens.sim_engine import (
MinuteSimulationClock,
FiveMinuteSimulationClock,
)
from catalyst.gens.sim_engine import MinuteSimulationClock
from catalyst.sources.benchmark_source import BenchmarkSource
from catalyst.catalyst_warnings import ZiplineDeprecationWarning
from catalyst.constants import LOG_LEVEL
log = logbook.Logger("ZiplineLog")
log = logbook.Logger("CatalystLog", level=LOG_LEVEL)
class TradingAlgorithm(object):
@@ -173,7 +171,7 @@ class TradingAlgorithm(object):
algo_filename : str, optional
The filename for the algoscript. This will be used in exception
tracebacks. default: '<string>'.
data_frequency : {'daily', '5-minute', 'minute'}, optional
data_frequency : {'daily', 'minute'}, optional
The duration of the bars.
instant_fill : bool, optional
Whether to fill orders immediately or on next bar. default: False
@@ -226,7 +224,7 @@ class TradingAlgorithm(object):
script : str
Algoscript that contains initialize and
handle_data function definition.
data_frequency : {'daily', '5-minute', 'minute'}
data_frequency : {'daily', 'minute'}
The duration of the bars.
capital_base : float <default: 1.0e5>
How much capital to start with.
@@ -434,8 +432,6 @@ class TradingAlgorithm(object):
if get_loader is not None:
if data_frequency == 'daily':
all_dates = self.trading_calendar.all_sessions
elif data_frequency == '5-minute':
all_dates = self.trading_calendar.all_five_minutes
elif data_frequency == 'minute':
all_dates = self.trading_calendar.all_minutes
else:
@@ -467,7 +463,7 @@ class TradingAlgorithm(object):
self._in_before_trading_start = True
with handle_non_market_minutes(data) if \
self.data_frequency in ('minute', '5-minute') else ExitStack():
self.data_frequency == 'minute' else ExitStack():
self._before_trading_start(self, data)
self._in_before_trading_start = False
@@ -523,11 +519,10 @@ class TradingAlgorithm(object):
market_closes = trading_o_and_c['market_close']
minutely_emission = False
if self.sim_params.data_frequency in set(('minute', '5-minute')):
if self.sim_params.data_frequency == 'minute':
market_opens = trading_o_and_c['market_open']
minutely_emission = self.sim_params.emission_rate in \
set(('minute', '5-minute'))
minutely_emission = self.sim_params.emission_rate == 'minute'
else:
# in daily mode, we want to have one bar per session, timestamped
# as the last minute of the session.
@@ -551,15 +546,6 @@ class TradingAlgorithm(object):
'UTC',
)
if self.sim_params.data_frequency == '5-minute':
return FiveMinuteSimulationClock(
self.sim_params.sessions,
execution_opens,
execution_closes,
before_trading_start_minutes,
minute_emission=minutely_emission,
)
return MinuteSimulationClock(
self.sim_params.sessions,
execution_opens,
@@ -691,8 +677,6 @@ class TradingAlgorithm(object):
time_count = times.nunique()
if time_count == 1:
self.sim_params.data_frequency = 'daily'
elif time_count == 288:
self.sim_params.data_frequency = '5-minute'
else:
self.sim_params.data_frequency = 'minute'
@@ -714,8 +698,6 @@ class TradingAlgorithm(object):
if self.sim_params.data_frequency == 'daily':
equity_reader_arg = 'equity_daily_reader'
elif self.sim_params.data_frequency == '5-minute':
equity_daily_reader = 'equity_5_minute_reader'
elif self.sim_params.data_frequency == 'minute':
equity_reader_arg = 'equity_minute_reader'
equity_reader = PanelBarReader(
@@ -959,9 +941,9 @@ class TradingAlgorithm(object):
The arena from the simulation parameters. This will normally
be ``'backtest'`` but some systems may use this distinguish
live trading from backtesting.
data_frequency : {'daily', '5-minute', 'minute'}
data_frequency : {'daily', 'minute'}
data_frequency tells the algorithm if it is running with
daily, minute, or five-minute mode.
daily or minute mode.
start : datetime
The start date for the simulation.
end : datetime
@@ -1135,21 +1117,12 @@ class TradingAlgorithm(object):
'date_rule. You should use keyword argument '
'time_rule= when calling schedule_function without '
'specifying a date_rule', stacklevel=3)
freq = self.sim_params.data_frequency
freq = self.sim_params.data_frequency
date_rule = date_rule or date_rules.every_day()
if freq is 'daily':
# ignore time rule in daily mode
time_rule = time_rules.every_minute()
else:
# use provided time rule or default to every minute or 5 minutes
# based on desired data frequency.
time_rule = time_rule or (time_rules.every_5_minutes()
if freq is '5-minute' else
time_rules.every_minute())
time_rule = ((time_rule or time_rules.every_minute())
if self.sim_params.data_frequency == 'minute' else
# If we are in daily mode the time_rule is ignored.
time_rules.every_minute())
# Check the type of the algorithm's schedule before pulling calendar
# Note that the ExchangeTradingSchedule is currently the only
@@ -1490,7 +1463,7 @@ class TradingAlgorithm(object):
def _calculate_order(self, asset, amount,
limit_price=None, stop_price=None, style=None):
amount = self.round_order(amount)
amount = self.round_order(amount, asset)
# Raises a ZiplineError if invalid parameters are detected.
self.validate_order_params(asset,
@@ -1507,16 +1480,12 @@ class TradingAlgorithm(object):
return amount, style
@staticmethod
def round_order(amount):
def round_order(amount, asset):
"""
Convert number of shares to an integer.
By default, truncates to the integer share count that's either within
.0001 of amount or closer to zero.
E.g. 3.9999 -> 4.0; 5.5 -> 5.0; -5.5 -> -5.0
Converts the number of shares to the smallest tradable lot size for
the asset being ordered.
"""
return int(round_if_near_integer(amount))
return round_nearest(amount, asset.min_trade_size)
def validate_order_params(self,
asset,
@@ -1824,7 +1793,7 @@ class TradingAlgorithm(object):
@data_frequency.setter
def data_frequency(self, value):
assert value in ('daily', '5-minute', 'minute')
assert value in ('daily', 'minute')
self.sim_params.data_frequency = value
@api_method
+286 -27
View File
@@ -17,34 +17,38 @@
"""
Cythonized Asset object.
"""
import hashlib
cimport cython
from cpython.number cimport PyNumber_Index
from cpython.object cimport (
Py_EQ,
Py_NE,
Py_GE,
Py_LE,
Py_GT,
Py_LT,
Py_EQ,
Py_NE,
Py_GE,
Py_LE,
Py_GT,
Py_LT,
)
from cpython cimport bool
import pandas as pd
from datetime import timedelta
import numpy as np
from numpy cimport int64_t
import warnings
cimport numpy as np
from catalyst.exchange.utils.exchange_utils import get_sid
from catalyst.utils.calendars import get_calendar
from catalyst.exchange.exchange_errors import InvalidSymbolError, SidHashError
# IMPORTANT NOTE: You must change this template if you change
# Asset.__reduce__, or else we'll attempt to unpickle an old version of this
# class
CACHE_FILE_TEMPLATE = '/tmp/.%s-%s.v7.cache'
cdef class Asset:
cdef readonly int sid
# Cached hash of self.sid
cdef int sid_hash
@@ -59,6 +63,7 @@ cdef class Asset:
cdef readonly object exchange
cdef readonly object exchange_full
cdef readonly object min_trade_size
_kwargnames = frozenset({
'sid',
@@ -70,18 +75,20 @@ cdef class Asset:
'auto_close_date',
'exchange',
'exchange_full',
'min_trade_size',
})
def __init__(self,
int sid, # sid is required
object exchange, # exchange is required
int sid, # sid is required
object exchange, # exchange is required
object symbol="",
object asset_name="",
object start_date=None,
object end_date=None,
object first_traded=None,
object auto_close_date=None,
object exchange_full=None):
object exchange_full=None,
object min_trade_size=None):
self.sid = sid
self.sid_hash = hash(sid)
@@ -94,6 +101,7 @@ cdef class Asset:
self.end_date = end_date
self.first_traded = first_traded
self.auto_close_date = auto_close_date
self.min_trade_size = min_trade_size
def __int__(self):
return self.sid
@@ -148,7 +156,8 @@ cdef class Asset:
def __repr__(self):
attrs = ('symbol', 'asset_name', 'exchange',
'start_date', 'end_date', 'first_traded', 'auto_close_date')
'start_date', 'end_date', 'first_traded', 'auto_close_date',
'min_trade_size')
tuples = ((attr, repr(getattr(self, attr, None)))
for attr in attrs)
strings = ('%s=%s' % (t[0], t[1]) for t in tuples)
@@ -170,7 +179,8 @@ cdef class Asset:
self.end_date,
self.first_traded,
self.auto_close_date,
self.exchange_full))
self.exchange_full,
self.min_trade_size))
cpdef to_dict(self):
"""
@@ -186,6 +196,7 @@ cdef class Asset:
'auto_close_date': self.auto_close_date,
'exchange': self.exchange,
'exchange_full': self.exchange_full,
'min_trade_size': self.min_trade_size
}
@classmethod
@@ -230,13 +241,11 @@ cdef class Asset:
calendar = get_calendar(self.exchange)
return calendar.is_open_on_minute(dt_minute)
cdef class Equity(Asset):
def __repr__(self):
attrs = ('symbol', 'asset_name', 'exchange',
'start_date', 'end_date', 'first_traded', 'auto_close_date',
'exchange_full')
'exchange_full', 'min_trade_size')
tuples = ((attr, repr(getattr(self, attr, None)))
for attr in attrs)
strings = ('%s=%s' % (t[0], t[1]) for t in tuples)
@@ -250,8 +259,8 @@ cdef class Equity(Asset):
"""
def __get__(self):
warnings.warn("The security_start_date property will soon be "
"retired. Please use the start_date property instead.",
DeprecationWarning)
"retired. Please use the start_date property instead.",
DeprecationWarning)
return self.start_date
property security_end_date:
@@ -261,8 +270,8 @@ cdef class Equity(Asset):
"""
def __get__(self):
warnings.warn("The security_end_date property will soon be "
"retired. Please use the end_date property instead.",
DeprecationWarning)
"retired. Please use the end_date property instead.",
DeprecationWarning)
return self.end_date
property security_name:
@@ -272,13 +281,11 @@ cdef class Equity(Asset):
"""
def __get__(self):
warnings.warn("The security_name property will soon be "
"retired. Please use the asset_name property instead.",
DeprecationWarning)
"retired. Please use the asset_name property instead.",
DeprecationWarning)
return self.asset_name
cdef class Future(Asset):
cdef readonly object root_symbol
cdef readonly object notice_date
cdef readonly object expiration_date
@@ -303,8 +310,8 @@ cdef class Future(Asset):
})
def __init__(self,
int sid, # sid is required
object exchange, # exchange is required
int sid, # sid is required
object exchange, # exchange is required
object symbol="",
object root_symbol="",
object asset_name="",
@@ -388,6 +395,258 @@ cdef class Future(Asset):
super_dict['multiplier'] = self.multiplier
return super_dict
cdef class TradingPair(Asset):
cdef readonly float leverage
cdef readonly object quote_currency
cdef readonly object base_currency
cdef readonly object end_daily
cdef readonly object end_minute
cdef readonly object exchange_symbol
cdef readonly float maker
cdef readonly float taker
cdef readonly int trading_state
cdef readonly object data_source
cdef readonly float max_trade_size
cdef readonly float lot
cdef readonly int decimals
_kwargnames = frozenset({
'sid',
'symbol',
'asset_name',
'start_date',
'end_date',
'first_traded',
'auto_close_date',
'exchange',
'exchange_full',
'leverage',
'quote_currency',
'base_currency',
'end_daily',
'end_minute',
'exchange_symbol',
'min_trade_size',
'max_trade_size',
'lot',
'maker',
'taker',
'trading_state',
'data_source',
'decimals'
})
def __init__(self,
object symbol,
object exchange,
object start_date=None,
object asset_name=None,
int sid=0,
float leverage=1.0,
object end_daily=None,
object end_minute=None,
object end_date=None,
object exchange_symbol=None,
object first_traded=None,
object auto_close_date=None,
object exchange_full=None,
float min_trade_size=0.0001,
float max_trade_size=1000000,
float maker=0.0015,
float taker=0.0025,
float lot=0,
int decimals = 8,
int trading_state=0,
object data_source='catalyst'):
"""
Replicates the Asset constructor with some built-in conventions
and adds properties for leverage and fees.
Symbol
------
Catalyst defines its own set of "universal" symbols to reference
trading pairs across exchanges. This is required because exchanges
are not adhering to a universal symbolism. For example, Bitfinex
uses the BTC symbol for Bitcon while Kraken uses XBT. In addition,
pairs are sometimes presented differently. For example, Bitfinex
puts the market currency before the base currency without a
separator, Bittrex puts the base currency first and uses a dash
seperator.
Here is the Catalyst convention: [Market Currency]_[Base Currency]
For example: btc_usd, eth_btc, neo_eth, ltc_eur.
The symbol for each currency (e.g. btc, eth, ltc) is generally
aligned with the Bittrex exchange.
Sid
---
The sid of each asset is calculated based on a numeric hash of the
universal symbol. This simple approach avoids maintaining a mapping
of sids.
Leverage
--------
In contrast with equities, crypto exchanges generally assign
leverage values to specific trading pairs. Pairs with the
highest volume and market cap generally benefit from high leverage.
New currencies from ICO generally cannot be leveraged.
Leverage allows you to open a larger position with a smaller amount
of funds. For example, if you open a $5,000 position in BTC/USD
with 5:1 leverage, only one-fifth of this amount, or $1000, will be
tied to the position from your balance. Your remaining balance will
be available for opening more positions. If you open this same
position with 2:1 leverage, $2,500 of your balance will be tied to
the position. If you open with 1:1 leverage, $5,000 of your balance
will be tied to the position.
Fees
----
Exchanges generally charge a taker (taking from the order book) or
maker (adding to the order book) fee.
:param symbol:
:param exchange:
:param start_date:
:param asset_name:
:param sid:
:param leverage:
:param end_daily
:param end_minute
:param end_date:
:param exchange_symbol:
:param first_traded:
:param auto_close_date:
:param exchange_full:
:param min_trade_size:
:param max_trade_size:
:param maker:
:param taker:
:param data_source
:param decimals
:param lot
"""
symbol = symbol.lower()
try:
self.base_currency, self.quote_currency = symbol.split('_')
except Exception as e:
raise InvalidSymbolError(symbol=symbol, error=e)
if sid == 0 or sid is None:
try:
sid = get_sid(symbol)
except Exception as e:
raise SidHashError(symbol=symbol)
if asset_name is None:
asset_name = ' / '.join(symbol.split('_')).upper()
if start_date is None:
start_date = pd.to_datetime('2009-1-1', utc=True)
if end_date is None:
end_date = pd.Timestamp.utcnow() + timedelta(days=365)
if lot == 0 and min_trade_size > 0:
lot = min_trade_size
super().__init__(
sid,
exchange,
symbol=symbol,
asset_name=asset_name,
start_date=start_date,
end_date=end_date,
first_traded=first_traded,
auto_close_date=auto_close_date,
exchange_full=exchange_full,
min_trade_size=min_trade_size,
)
self.maker = maker
self.taker = taker
self.leverage = leverage
self.end_daily = end_daily
self.end_minute = end_minute
self.exchange_symbol = exchange_symbol
self.trading_state = trading_state
self.data_source = data_source
self.max_trade_size = max_trade_size
self.lot = lot
self.decimals = decimals
def __repr__(self):
return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \
'Introduced On: {start_date}, ' \
'Base Currency: {base_currency}, ' \
'Quote Currency: {quote_currency}, ' \
'Exchange Leverage: {leverage}, ' \
'Minimum Trade Size: {min_trade_size} ' \
'Last daily ingestion: {end_daily} ' \
'Last minutely ingestion: {end_minute}'.format(
symbol=self.symbol,
sid=self.sid,
exchange=self.exchange,
start_date=self.start_date,
quote_currency=self.quote_currency,
base_currency=self.base_currency,
leverage=self.leverage,
min_trade_size=self.min_trade_size,
end_daily=self.end_daily,
end_minute=self.end_minute
)
cpdef to_dict(self):
"""
Convert to a python dict.
"""
#TODO: missing fields
super_dict = super(TradingPair, self).to_dict()
super_dict['end_daily'] = self.end_daily
super_dict['end_minute'] = self.end_minute
super_dict['leverage'] = self.leverage
super_dict['min_trade_size'] = self.min_trade_size
return super_dict
def is_exchange_open(self, dt_minute):
"""
Parameters
----------
dt_minute: pd.Timestamp (UTC, tz-aware)
The minute to check.
Returns
-------
boolean: whether the asset's exchange is open at the given minute.
"""
#TODO: make more dymanic to catch holds
return True
cpdef __reduce__(self):
"""
Function used by pickle to determine how to serialize/deserialize this
class. Should return a tuple whose first element is self.__class__,
and whose second element is a tuple of all the attributes that should
be serialized/deserialized during pickling.
"""
#TODO: make sure that all fields set there
return (self.__class__, (self.symbol,
self.exchange,
self.start_date,
self.asset_name,
self.sid,
self.leverage,
self.end_date,
self.first_traded,
self.auto_close_date,
self.exchange_full,
self.min_trade_size,
self.max_trade_size,
self.lot,
self.decimals,
self.taker,
self.maker))
def make_asset_array(int size, Asset asset):
cdef np.ndarray out = np.empty([size], dtype=object)
+2 -1
View File
@@ -39,7 +39,8 @@ equities = sa.Table(
sa.Column('first_traded', sa.Integer),
sa.Column('auto_close_date', sa.Integer),
sa.Column('exchange', sa.Text),
sa.Column('exchange_full', sa.Text)
sa.Column('exchange_full', sa.Text),
sa.Column('min_trade_size', sa.Float)
)
equity_symbol_mappings = sa.Table(
+3
View File
@@ -73,6 +73,7 @@ _equities_defaults = {
'exchange': None,
# optional, something like "New York Stock Exchange"
'exchange_full': None,
'min_trade_size': 1
}
# Default values for the futures DataFrame
@@ -390,6 +391,8 @@ class AssetDBWriter(object):
The date on which to close any positions in this asset.
exchange : str
The exchange where this asset is traded.
min_trade_size: float, optional
The minimum denomination this asset can be traded.
The index of this dataframe should contain the sids.
futures : pd.DataFrame, optional
+3 -1
View File
@@ -76,7 +76,9 @@ from catalyst.utils.numpy_utils import as_column
from catalyst.utils.preprocess import preprocess
from catalyst.utils.sqlite_utils import group_into_chunks, coerce_string_to_eng
log = Logger('assets.py')
from catalyst.constants import LOG_LEVEL
log = Logger('assets.py', level=LOG_LEVEL)
# A set of fields that need to be converted to strings before building an
# Asset to avoid unicode fields
+18
View File
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import os
import logbook
''' You can override the LOG level from your environment.
For example, if you want to see the DEBUG messages, run:
$ export CATALYST_LOG_LEVEL=10
'''
LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO))
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
'{exchange}/symbols.json'
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M'
DATE_FORMAT = '%Y-%m-%d'
AUTO_INGEST = False
+329 -91
View File
@@ -1,35 +1,47 @@
import json, time, csv
from datetime import datetime
import pandas as pd
import csv
import json
import os
import shutil
import time
import requests
import logbook
from datetime import datetime
DT_START = time.mktime(datetime(2010, 1, 1, 0, 0).timetuple())
CSV_OUT_FOLDER = '/var/tmp/catalyst/data/poloniex/'
CONN_RETRIES = 2
import logbook
import pandas as pd
import requests
from catalyst.exchange.utils.exchange_utils import \
get_exchange_symbols_filename
DT_START = int(time.mktime(datetime(2010, 1, 1, 0, 0).timetuple()))
DT_END = pd.to_datetime('today').value // 10 ** 9
CSV_OUT_FOLDER = os.environ.get('CSV_OUT_FOLDER', '/efs/exchanges/poloniex/')
CONN_RETRIES = 2
logbook.StderrHandler().push_application()
log = logbook.Logger(__name__)
class PoloniexCurator(object):
"""
OHLCV data feed generator for crypto data. Based on Poloniex market data
"""
_api_path = 'https://poloniex.com/public?'
currency_pairs = []
class PoloniexCurator(object):
'''
OHLCV data feed generator for crypto data. Based on Poloniex market data
'''
_api_path = 'https://poloniex.com/public?'
currency_pairs = []
def __init__(self):
if not os.path.exists(CSV_OUT_FOLDER):
try:
os.makedirs(CSV_OUT_FOLDER)
except Exception as e:
log.error('Failed to create data folder: %s' % CSV_OUT_FOLDER)
log.error('Failed to create data folder: {}'.format(
CSV_OUT_FOLDER))
log.exception(e)
def get_currency_pairs(self):
'''
Retrieves and returns all currency pairs from the exchange
'''
url = self._api_path + 'command=returnTicker'
try:
@@ -40,105 +52,331 @@ class PoloniexCurator(object):
return None
data = response.json()
self.currency_pairs = []
self.currency_pairs = []
for ticker in data:
self.currency_pairs.append(ticker)
self.currency_pairs.sort()
log.debug('Currency pairs retrieved successfully: %d' % (len(self.currency_pairs)))
log.debug('Currency pairs retrieved successfully: {}'.format(
len(self.currency_pairs)
))
def _get_start_date(self, csv_fn):
''' Function returns latest appended date, if the file has been previously written
the last line is an empty one, so we have to read the second to last line
def _retrieve_tradeID_date(self, row):
'''
Helper function that reads tradeID and date fields from CSV readline
'''
tId = int(row.split(',')[0])
d = pd.to_datetime(row.split(',')[1],
infer_datetime_format=True).value // 10 ** 9
return tId, d
def retrieve_trade_history(self, currencyPair, start=DT_START,
end=DT_END, temp=None):
'''
Retrieves TradeHistory from exchange for a given currencyPair
between start and end dates. If no start date is provided, uses
a system-wide one (beginning of time for cryptotrading).
If no end date is provided, 'now' is used.
Stores results in CSV file on disk.
This function is called recursively to work around the
limitations imposed by the provider API.
'''
csv_fn = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv'
'''
Check what data we already have on disk, reading first and last
lines from file. Data is stored on file from NEWEST to OLDEST.
'''
try:
with open(csv_fn, 'ab+') as f:
f.seek(0, os.SEEK_END) # First check file is not zero size
if(f.tell() > 2):
f.seek(-2, os.SEEK_END) # Jump to the second last byte.
with open(csv_fn, 'ab+') as f:
f.seek(0, os.SEEK_END)
if(f.tell() > 2): # Check file size is not 0
f.seek(0) # Go to start to read
last_tradeID, end_file = self._retrieve_tradeID_date(
f.readline())
f.seek(-2, os.SEEK_END) # Jump to the 2nd last byte
while f.read(1) != b"\n": # Until EOL is found...
f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more.
lastrow = f.readline()
return int(lastrow.split(',')[0]) + 300
# ...jump back the read byte plus one more.
f.seek(-2, os.SEEK_CUR)
first_tradeID, start_file = self._retrieve_tradeID_date(
f.readline())
if(end_file + 3600 * 6 > DT_END
and (first_tradeID == 1
or (currencyPair == 'BTC_HUC'
and first_tradeID == 2)
or (currencyPair == 'BTC_RIC'
and first_tradeID == 2)
or (currencyPair == 'BTC_XCP'
and first_tradeID == 2)
or (currencyPair == 'BTC_NAV'
and first_tradeID == 4569)
or (currencyPair == 'BTC_POT'
and first_tradeID == 23511))):
return
except Exception as e:
log.error('Error opening file: %s' % csv_fn)
log.error('Error opening file: {}'.format(csv_fn))
log.exception(e)
return DT_START
'''
Poloniex API limits querying TradeHistory to intervals smaller
than 1 month, so we make sure that start date is never more than
1 month apart from end date
'''
if(end - start > 2419200): # 60s/min * 60min/hr * 24hr/day * 28days
newstart = end - 2419200
else:
newstart = start
def get_data(self, currencyPair, start, end=9999999999, period=300):
url = self._api_path + 'command=returnChartData&currencyPair=' + currencyPair + '&start=' + str(start) + '&end=' + str(end) + '&period=' + str(period)
log.debug('{}: Retrieving from {} to {}\t {} - {}'.format(
currencyPair, str(newstart), str(end),
time.ctime(newstart), time.ctime(end)))
try:
response = requests.get(url)
except Exception as e:
log.error('Failed to retrieve candlestick chart data for %s' % currencyPair)
log.exception(e)
url = '{path}command=returnTradeHistory&currencyPair={pair}' \
'&start={start}&end={end}'.format(
path=self._api_path,
pair=currencyPair,
start=str(newstart),
end=str(end)
)
attempts = 0
success = 0
while attempts < CONN_RETRIES:
try:
response = requests.get(url)
except Exception as e:
log.error('Failed to retrieve trade history data'
'for {}'.format(currencyPair))
log.exception(e)
attempts += 1
else:
try:
if(isinstance(response.json(), dict)
and response.json()['error']):
log.error('Failed to to retrieve trade history data '
'for {}: {}'.format(
currencyPair,
response.json()['error']
))
attempts += 1
except Exception as e:
log.exception(e)
attempts += 1
else:
success = 1
break
if not success:
return None
return response.json()
'''
If we get to transactionId == 1, and we already have that on
disk, we got to the end of TradeHistory for this coin.
'''
if('first_tradeID' in locals()
and response.json()[-1]['tradeID'] == first_tradeID):
return
'''
Pulls latest data for a single pair
'''
def append_data_single_pair(self, currencyPair, repeat=0):
log.debug('Getting data for %s' % currencyPair)
csv_fn = CSV_OUT_FOLDER + 'crypto_prices-' + currencyPair + '.csv'
start = self._get_start_date(csv_fn)
# Only fetch data if more than 5min have passed since last fetch
if (time.time() > start):
data = self.get_data(currencyPair, start)
if data is not None:
try:
with open(csv_fn, 'ab') as csvfile:
csvwriter = csv.writer(csvfile)
for item in data:
if item['date'] == 0:
continue
csvwriter.writerow([
item['date'],
item['open'],
item['high'],
item['low'],
item['close'],
item['volume'],
])
except Exception as e:
log.error('Error opening %s' % csv_fn)
log.exception(e)
elif (repeat < CONN_RETRIES):
log.debug('Retrying: attemt %d' % (repeat+1) )
self.append_data_single_pair(currencyPair, repeat + 1)
'''
There are primarily two scenarios:
a) There is newer data available that we need to add at
the beginning of the file. We'll retrieve all what we
need until we get to what we already have, writing it
to a temporary file; and we will write that at the
beginning of our existing file.
b) We are going back in time, appending at the end of
our existing TradeHistory until the first transaction
for this currencyPair
'''
try:
if(temp is not None
or ('end_file' in locals() and end_file + 3600 < end)):
if (temp is None):
temp = os.tmpfile()
tempcsv = csv.writer(temp)
for item in response.json():
if(item['tradeID'] <= last_tradeID):
continue
tempcsv.writerow([
item['tradeID'],
item['date'],
item['type'],
item['rate'],
item['amount'],
item['total'],
item['globalTradeID'],
])
if(response.json()[-1]['tradeID'] > last_tradeID):
end = pd.to_datetime(response.json()[-1]['date'],
infer_datetime_format=True
).value // 10**9
self.retrieve_trade_history(currencyPair, start,
end, temp=temp)
else:
with open(csv_fn, 'rb+') as f:
shutil.copyfileobj(f, temp)
f.seek(0)
temp.seek(0)
shutil.copyfileobj(temp, f)
temp.close()
end = start_file
else:
with open(csv_fn, 'ab') as csvfile:
csvwriter = csv.writer(csvfile)
for item in response.json():
if('first_tradeID' in locals()
and item['tradeID'] >= first_tradeID):
continue
csvwriter.writerow([
item['tradeID'],
item['date'],
item['type'],
item['rate'],
item['amount'],
item['total'],
item['globalTradeID']
])
end = pd.to_datetime(response.json()[-1]['date'],
infer_datetime_format=True).value//10**9
'''
Pulls latest data for all currency pairs
'''
def append_data(self):
for currencyPair in self.currency_pairs:
self.append_data_single_pair(currencyPair)
# Rate limit is 6 calls per second, sleep 1sec/6 to be safe
time.sleep(0.17)
except Exception as e:
log.error('Error opening {}'.format(csv_fn))
log.exception(e)
'''
Returns a data frame for all pairs, or for the requests currency pair.
Makes sure data is up to date
'''
def to_dataframe(self, start, end, currencyPair=None):
csv_fn = CSV_OUT_FOLDER + 'crypto_prices-' + currencyPair + '.csv'
last_date = self._get_start_date(csv_fn)
if last_date + 300 < end or not os.path.exists(csv_fn):
# get latest data
self.append_data_single_pair(currencyPair)
'''
If we got here, we aren't done yet. Call recursively with
'end' times that go sequentially back in time.
'''
self.retrieve_trade_history(currencyPair, start, end)
# CSV holds the latest snapshot
df = pd.read_csv(csv_fn, names=['date', 'open', 'high', 'low', 'close', 'volume'])
df['date']=pd.to_datetime(df['date'],unit='s')
def generate_ohlcv(self, df):
'''
Generates OHLCV dataframe from a dataframe containing all TradeHistory
by resampling with 1-minute period
'''
df.set_index('date', inplace=True) # Index by date
vol = df['total'].to_frame('volume') # set Vol aside
df.drop('total', axis=1, inplace=True) # Drop volume data
ohlc = df.resample('T').ohlc() # Resample OHLC 1min
ohlc.columns = ohlc.columns.map(lambda t: t[1]) # Rename cols
closes = ohlc['close'].fillna(method='pad') # Pad fwd missing close
ohlc = ohlc.apply(lambda x: x.fillna(closes)) # Fill NA w/ last close
vol = vol.resample('T').sum().fillna(0) # Add volumes by bin
ohlcv = pd.concat([ohlc, vol], axis=1) # Concat OHLC + Vol
return ohlcv
def write_ohlcv_file(self, currencyPair):
'''
Generates OHLCV data file with 1minute bars from TradeHistory on disk
'''
csv_trades = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv'
csv_1min = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv'
if(os.path.getmtime(csv_1min) > time.time() - 7200):
log.debug(currencyPair+': 1min data file already up to date. '
'Delete the file if you want to rebuild it.')
else:
df = pd.read_csv(csv_trades,
names=['tradeID',
'date',
'type',
'rate',
'amount',
'total',
'globalTradeID'],
dtype={'tradeID': int,
'date': str,
'type': str,
'rate': float,
'amount': float,
'total': float,
'globalTradeID': int}
)
df.drop(['tradeID', 'type', 'amount', 'globalTradeID'],
axis=1, inplace=True)
df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True)
ohlcv = self.generate_ohlcv(df)
try:
with open(csv_1min, 'w') as csvfile:
csvwriter = csv.writer(csvfile)
for item in ohlcv.itertuples():
if item.Index == 0:
continue
csvwriter.writerow([
item.Index.value // 10 ** 9,
item.open,
item.high,
item.low,
item.close,
item.volume,
])
except Exception as e:
log.error('Error opening {}'.format(csv_1min))
log.exception(e)
log.debug('{}: Generated 1min OHLCV data.'.format(currencyPair))
def onemin_to_dataframe(self, currencyPair, start, end):
'''
Returns a data frame for a given currencyPair from data on disk
'''
csv_fn = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv'
df = pd.read_csv(csv_fn, names=['date',
'open',
'high',
'low',
'close',
'volume'])
df['date'] = pd.to_datetime(df['date'], unit='s')
df.set_index('date', inplace=True)
return df[start:end]
def generate_symbols_json(self, filename=None):
'''
Generates a symbols.json file with corresponding start_date
for each currencyPair
'''
symbol_map = {}
if(filename is None):
filename = get_exchange_symbols_filename('poloniex')
with open(filename, 'w') as symbols:
for currencyPair in self.currency_pairs:
start = None
csv_fn = '{}crypto_trades-{}.csv'.format(
CSV_OUT_FOLDER,
currencyPair)
with open(csv_fn, 'r') as f:
f.seek(0, os.SEEK_END)
if(f.tell() > 2): # Check file size is not 0
f.seek(-2, os.SEEK_END) # Jump to 2nd last byte
while f.read(1) != b"\n": # Until EOL is found...
# ...jump back the read byte plus one more.
f.seek(-2, os.SEEK_CUR)
start = pd.to_datetime(f.readline().split(',')[1],
infer_datetime_format=True)
if(start is None):
start = time.gmtime()
base, market = currencyPair.lower().split('_')
symbol = '{market}_{base}'.format(market=market, base=base)
symbol_map[currencyPair] = dict(
symbol=symbol,
start_date=start.strftime("%Y-%m-%d")
)
json.dump(symbol_map, symbols, sort_keys=True, indent=2,
separators=(',', ':'))
return df[datetime.fromtimestamp(start):datetime.fromtimestamp(end-1)]
if __name__ == '__main__':
pc = PoloniexCurator()
pc.get_currency_pairs()
pc.append_data()
# pc.generate_symbols_json()
for currencyPair in pc.currency_pairs:
pc.retrieve_trade_history(currencyPair)
log.debug('{} up to date.'.format(currencyPair))
pc.write_ohlcv_file(currencyPair)
+4 -4
View File
@@ -215,13 +215,13 @@ cpdef _read_bcolz_data(ctable_t table,
else:
continue
if column_name in ['open', 'high', 'low', 'close']:
if column_name in ['open', 'high', 'low', 'close', 'volume']:
where_nan = (outbuf == 0)
outbuf_as_float = outbuf.astype(float64) * .000001
outbuf_as_float = outbuf.astype(float64) * .000000001
outbuf_as_float[where_nan] = NAN
results.append(outbuf_as_float)
elif column_name != 'volume':
results.append(outbuf.astype(uint32))
elif column_name in ['volume']:
results.append(outbuf.astype(float64) * .000000001)
else:
results.append(outbuf)
return results
-78
View File
@@ -35,17 +35,6 @@ def minute_value(ndarray[long_t, ndim=1] market_opens,
return market_opens[q] + r
@cython.cdivision(True)
def five_minute_value(ndarray[long_t, ndim=1] market_opens,
Py_ssize_t pos,
short five_minutes_per_day):
cdef short q, r
q = cython.cdiv(pos, five_minutes_per_day)
r = cython.cmod(pos, five_minutes_per_day)
return market_opens[q] + r
def find_position_of_minute(ndarray[long_t, ndim=1] market_opens,
ndarray[long_t, ndim=1] market_closes,
long_t minute_val,
@@ -99,26 +88,6 @@ def find_position_of_minute(ndarray[long_t, ndim=1] market_opens,
return (market_open_loc * minutes_per_day) + delta
def find_position_of_five_minute(ndarray[long_t, ndim=1] market_opens,
ndarray[long_t, ndim=1] market_closes,
long_t five_minute_val,
short five_minutes_per_day,
bool forward_fill):
cdef Py_ssize_t market_open_loc, market_open, delta
market_open_loc = \
searchsorted(market_opens, five_minute_val, side='right') - 1
market_open = market_opens[market_open_loc]
market_close = market_closes[market_open_loc]
if not forward_fill and ((five_minute_val - market_open) >= five_minutes_per_day):
raise ValueError("Given five minutes is not between an open and a close")
delta = int_min(five_minute_val - market_open, market_close - market_open)
return (market_open_loc * five_minutes_per_day) + delta
def find_last_traded_position_internal(
ndarray[long_t, ndim=1] market_opens,
ndarray[long_t, ndim=1] market_closes,
@@ -189,50 +158,3 @@ def find_last_traded_position_internal(
# found a trade event
return -1
def find_last_traded_five_minute_position_internal(
ndarray[long_t, ndim=1] market_opens,
ndarray[long_t, ndim=1] market_closes,
long_t end_five_minute,
long_t start_five_minute,
volumes,
short five_minutes_per_day):
cdef Py_ssize_t minute_pos, current_minute, q
five_minute_pos = int_min(
find_position_of_five_minute(
market_opens,
market_closes,
end_five_minute,
five_minutes_per_day,
True,
),
len(volumes) - 1,
)
while five_minute_pos >= 0:
current_five_minute = five_minute_value(
market_opens, five_minute_pos, five_minutes_per_day
)
q = cython.cdiv(five_minute_pos, five_minutes_per_day)
if current_five_minute > market_closes[q]:
five_minute_pos = find_position_of_five_minute(
market_opens,
market_closes,
market_closes[q],
five_minutes_per_day,
False,
)
continue
if current_five_minute < start_five_minute:
return -1
if volumes[five_minute_pos] != 0:
return five_minute_pos
five_minute_pos -= 1
# we've gone to the beginning of this asset's range, and still haven't
# found a trade event
return -1
-1
View File
@@ -1,6 +1,5 @@
# These imports are necessary to force module-scope register calls to happen.
from . import quandl # noqa
from . import poloniex
from .core import (
UnknownBundle,
bundles,
+38 -62
View File
@@ -13,10 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from itertools import count
import tarfile
from time import time, sleep
from time import sleep
from abc import abstractmethod, abstractproperty
import logbook
@@ -30,11 +29,14 @@ from catalyst.utils.cli import (
)
from catalyst.utils.memoize import lazyval
from catalyst.constants import LOG_LEVEL
logbook.StderrHandler().push_application()
log = logbook.Logger(__name__)
log = logbook.Logger(__name__, level=LOG_LEVEL)
DEFAULT_RETRIES = 5
class BaseBundle(object):
def __init__(self, asset_filter=[]):
self._asset_filter = asset_filter
@@ -60,10 +62,6 @@ class BaseBundle(object):
def minutes_per_day(self):
raise NotImplementedError()
@lazyval
def five_minutes_per_day(self):
raise NotImplementedError()
@lazyval
def frequencies(self):
raise NotImplementedError()
@@ -106,16 +104,15 @@ class BaseBundle(object):
def post_process_symbol_metadata(self, metadata, data):
return metadata
@abstractmethod
def fetch_raw_symbol_frame(self, api_key, symbol, start_date, end_date):
raise NotImplementedError()
def ingest(self,
environ,
asset_db_writer,
minute_bar_writer,
five_minute_bar_writer,
daily_bar_writer,
adjustment_writer,
calendar,
@@ -131,7 +128,7 @@ class BaseBundle(object):
retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5)
if is_compile:
# User has instructed local compilation and ingestion of bundle.
# User has instructed local compilation & ingestion of bundle.
# Fetch raw metadata for all symbols.
raw_metadata = self._fetch_metadata_frame(
api_key,
@@ -160,9 +157,9 @@ class BaseBundle(object):
show_progress=show_progress,
)
# Post-process metadata using cached symbol frames, and write to
# disk. This metadata must be written before any attempt to write
# either minute or 5-minute data.
# Post-process metadata using cached symbol frames, and write
# to disk. This metadata must be written before any attempt
# to write minute data.
metadata = self._post_process_metadata(
raw_metadata,
cache,
@@ -170,26 +167,6 @@ class BaseBundle(object):
)
asset_db_writer.write(metadata)
# Compile 5-minute symbol data if bundle supports 5-minute mode and
# persist the dataset to disk.
'''
if '5-minute' in self.frequencies:
five_minute_bar_writer.write(
self._fetch_symbol_iter(
api_key,
cache,
symbol_map,
calendar,
start_session,
end_session,
'5-minute',
retries,
),
length=len(symbol_map),
show_progress=show_progress,
)
'''
# Compile minute symbol data if bundle supports minute mode and
# persist the dataset to disk.
if 'minute' in self.frequencies:
@@ -207,10 +184,11 @@ class BaseBundle(object):
show_progress=show_progress,
)
# For legacy purposes, this call is required to ensure the database
# contains an appropriately initialized file structure. We don't
# forsee a usecase for adjustments at this time, but may later
# choose to expose this functionality in the future.
# For legacy purposes, this call is required to ensure the
# database contains an appropriately initialized file
# structure. We don't forsee a usecase for adjustments at
# this time, but may later choose to expose this functionality
# in the future.
adjustment_writer.write(
splits=(
pd.concat(self.splits, ignore_index=True)
@@ -255,12 +233,12 @@ class BaseBundle(object):
tar.extractall(output_dir)
def _fetch_metadata_frame(self,
api_key,
cache,
retries=DEFAULT_RETRIES,
environ=None,
show_progress=False):
api_key,
cache,
retries=DEFAULT_RETRIES,
environ=None,
show_progress=False):
# Setup raw metadata iterator to fetch pages if necessary.
raw_iter = self._fetch_metadata_iter(api_key, cache, retries, environ)
@@ -274,7 +252,7 @@ class BaseBundle(object):
show_percent=False,
) as blocks:
metadata = pd.concat(blocks, ignore_index=True)
return metadata
def _fetch_metadata_iter(self, api_key, cache, retries, environ):
@@ -292,21 +270,20 @@ class BaseBundle(object):
page_number,
)
break
except ValueError as e:
except ValueError:
raw = pd.DataFrame([])
break
except Exception as e:
except Exception:
log.exception(
'Failed to load metadata from {}. '
'Retrying.'.format(self.name)
)
)
else:
raise ValueError(
'Failed to download metadata page {} after {} '
'attempts.'.format(page_number, retries)
)
if raw.empty:
# Empty DataFrame signals completion.
break
@@ -328,7 +305,7 @@ class BaseBundle(object):
columns=self.md_column_names,
index=metadata.index,
)
# Iterate over the available symbols, loading the asset's raw symbol
# data from the cache. The final metadata is computed and recorded in
# the appropriate row depending on the asset's id.
@@ -341,22 +318,22 @@ class BaseBundle(object):
show_percent=False,
) as symbols_map:
for asset_id, symbol in symbols_map:
# Attempt to load data from disk, the cache should have an entry
# for each symbol at this point of the execution. If one does
# not exist, we should fail.
# Attempt to load data from disk, the cache should have an
# entry for each symbol at this point of the execution. If one
# does not exist, we should fail.
key = '{sym}.daily.frame'.format(sym=symbol)
try:
raw_data = cache[key]
except KeyError:
raise ValueError(
'Unable to find cached data for symbol: {0}'.format(symbol)
)
'Unable to find cached data for symbol:'
' {0}'.format(symbol))
# Perform and require post-processing of metadata.
final_symbol_metadata = self.post_process_symbol_metadata(
asset_id,
metadata.iloc[asset_id],
raw_data,
raw_data,
)
# Record symbol's final metadata.
@@ -386,8 +363,8 @@ class BaseBundle(object):
# returns the cached data unaltered. The `should_sleep` flag
# indicates that an API call was attempted, and that we should be
# ensure aren't exceeding our rate limit before proceeding to the
# next symbol. If the raw_data is updated, it is cached before being
# returned.
# next symbol. If the raw_data is updated, it is cached before
# being returned.
raw_data, should_sleep = self._maybe_update_symbol_frame(
start_time,
api_key,
@@ -437,7 +414,7 @@ class BaseBundle(object):
last = start_session
if raw_data is not None and len(raw_data) > 0:
last = raw_data.index[-1].tz_localize('UTC')
should_sleep = False
# Determine time at which cached data will be considered stale.
@@ -478,7 +455,7 @@ class BaseBundle(object):
retries=DEFAULT_RETRIES):
# Data for symbol is old enough to attempt an update or is not
# present in the cache. Fetch raw data for a single symbol
# present in the cache. Fetch raw data for a single symbol
# with requested intervals and frequency. Retry as necessary.
for _ in range(retries):
try:
@@ -491,7 +468,6 @@ class BaseBundle(object):
data_frequency,
)
raw_data.index = pd.to_datetime(raw_data.index, utc=True)
raw_data.index = raw_data.index.tz_localize('UTC')
# Filter incoming data to fit start and end sessions.
raw_data = raw_data[
@@ -505,7 +481,7 @@ class BaseBundle(object):
return raw_data
except Exception as e:
except Exception:
log.exception(
'Exception raised fetching {name} data. Retrying.'
.format(name=self.name)
+4 -8
View File
@@ -16,6 +16,7 @@
from catalyst.data.bundles.base import BaseBundle
from catalyst.utils.memoize import lazyval
class BasePricingBundle(BaseBundle):
@lazyval
def md_dtypes(self):
@@ -24,6 +25,7 @@ class BasePricingBundle(BaseBundle):
('start_date', 'datetime64[ns]'),
('end_date', 'datetime64[ns]'),
('ac_date', 'datetime64[ns]'),
('min_trade_size', 'float'),
]
@lazyval
@@ -37,6 +39,7 @@ class BasePricingBundle(BaseBundle):
('volume', 'float64'),
]
class BaseCryptoPricingBundle(BasePricingBundle):
@lazyval
def calendar_name(self):
@@ -46,10 +49,6 @@ class BaseCryptoPricingBundle(BasePricingBundle):
def minutes_per_day(self):
return 1440
@lazyval
def five_minutes_per_day(self):
return 288
@property
def splits(self):
return []
@@ -58,6 +57,7 @@ class BaseCryptoPricingBundle(BasePricingBundle):
def dividends(self):
return []
class BaseEquityPricingBundle(BasePricingBundle):
@lazyval
def calendar_name(self):
@@ -67,10 +67,6 @@ class BaseEquityPricingBundle(BasePricingBundle):
def minutes_per_day(self):
return 390
@lazyval
def five_minutes_per_day(self):
return 78
@property
def splits(self):
return self._splits
+5 -32
View File
@@ -17,10 +17,6 @@ from ..us_equity_pricing import (
SQLiteAdjustmentReader,
SQLiteAdjustmentWriter,
)
from ..five_minute_bars import (
BcolzFiveMinuteBarReader,
BcolzFiveMinuteBarWriter,
)
from ..minute_bars import (
BcolzMinuteBarReader,
BcolzMinuteBarWriter,
@@ -41,6 +37,7 @@ from catalyst.utils.cli import maybe_show_progress
ONE_MEGABYTE = 1024 * 1024
def asset_db_path(bundle_name, timestr, environ=None, db_version=None):
return pth.data_path(
asset_db_relative(bundle_name, timestr, environ, db_version),
@@ -54,11 +51,6 @@ def minute_path(bundle_name, timestr, environ=None):
environ=environ,
)
def five_minute_path(bundle_name, timestr, environ=None):
return pth.data_path(
five_minute_relative(bundle_name, timestr, environ),
environ=environ,
)
def daily_path(bundle_name, timestr, environ=None):
return pth.data_path(
@@ -92,8 +84,6 @@ def cache_relative(bundle_name, timestr, environ=None):
def daily_relative(bundle_name, timestr, environ=None):
return bundle_name, timestr, 'daily_equities.bcolz'
def five_minute_relative(bundle_name, timestr, environ=None):
return bundle_name, timestr, 'five_minute.bcolz'
def minute_relative(bundle_name, timestr, environ=None):
return bundle_name, timestr, 'minute_equities.bcolz'
@@ -146,6 +136,7 @@ def ingestions_for_bundle(bundle, environ=None):
reverse=True,
)
def download_with_progress(url, chunk_size, **progress_kwargs):
"""
Download streaming data from a URL, printing progress information to the
@@ -206,14 +197,13 @@ RegisteredBundle = namedtuple(
'start_session',
'end_session',
'minutes_per_day',
'five_minutes_per_day',
'ingest',
'create_writers']
)
BundleData = namedtuple(
'BundleData',
'asset_finder minute_bar_reader five_minute_bar_reader daily_bar_reader '
'asset_finder minute_bar_reader daily_bar_reader '
'adjustment_reader',
)
@@ -303,7 +293,6 @@ def _make_bundle_core():
bundle.ingest,
calendar_name=bundle.calendar_name,
minutes_per_day=bundle.minutes_per_day,
five_minutes_per_day=bundle.five_minutes_per_day,
start_session=start_session,
end_session=end_session,
create_writers=create_writers,
@@ -316,7 +305,6 @@ def _make_bundle_core():
start_session=None,
end_session=None,
minutes_per_day=1440,
five_minutes_per_day=288,
create_writers=True):
"""Register a data bundle ingest function.
@@ -397,7 +385,6 @@ def _make_bundle_core():
start_session=start_session,
end_session=end_session,
minutes_per_day=minutes_per_day,
five_minutes_per_day=five_minutes_per_day,
ingest=f,
create_writers=create_writers,
)
@@ -496,16 +483,6 @@ def _make_bundle_core():
# that it can compute the adjustment ratios for the dividends.
daily_bar_writer.write(())
five_minute_bar_writer = BcolzFiveMinuteBarWriter(
wd.ensure_dir(*five_minute_relative(
name, timestr, environ=environ)
),
calendar,
start_session,
end_session,
five_minutes_per_day=bundle.five_minutes_per_day,
)
minute_bar_writer = BcolzMinuteBarWriter(
wd.ensure_dir(*minute_relative(
name, timestr, environ=environ)
@@ -532,7 +509,6 @@ def _make_bundle_core():
)
else:
daily_bar_writer = None
five_minute_bar_writer = None
minute_bar_writer = None
asset_db_writer = None
adjustment_db_writer = None
@@ -544,7 +520,6 @@ def _make_bundle_core():
environ,
asset_db_writer,
minute_bar_writer,
five_minute_bar_writer,
daily_bar_writer,
adjustment_db_writer,
calendar,
@@ -631,9 +606,6 @@ def _make_bundle_core():
minute_bar_reader=BcolzMinuteBarReader(
minute_path(name, timestr, environ=environ),
),
five_minute_bar_reader=BcolzFiveMinuteBarReader(
five_minute_path(name, timestr, environ=environ),
),
daily_bar_reader=BcolzDailyBarReader(
daily_path(name, timestr, environ=environ),
),
@@ -735,4 +707,5 @@ def _make_bundle_core():
)
bundles, register_bundle, register, unregister, ingest, load, clean = _make_bundle_core()
bundles, register_bundle, register, unregister, ingest, load, clean = \
_make_bundle_core()
+46 -27
View File
@@ -13,16 +13,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
import sys
from six.moves.urllib.parse import urlencode
import pandas as pd
from six.moves.urllib.parse import urlencode
from catalyst.data.bundles.core import register_bundle
from catalyst.data.bundles.base_pricing import BaseCryptoPricingBundle
from catalyst.utils.memoize import lazyval
from catalyst.curate.poloniex import PoloniexCurator
class PoloniexBundle(BaseCryptoPricingBundle):
@lazyval
def name(self):
@@ -36,14 +38,14 @@ class PoloniexBundle(BaseCryptoPricingBundle):
def frequencies(self):
return set((
'daily',
#'5-minute',
'minute',
))
@lazyval
def tar_url(self):
return (
'https://www.dropbox.com/s/9naqffawnq8o4r2/'
'poloniex-bundle.tar?dl=1'
'https://s3.amazonaws.com/enigmaco/catalyst-bundles/'
'poloniex/poloniex-bundle.tar.gz'
)
@lazyval
@@ -64,24 +66,25 @@ class PoloniexBundle(BaseCryptoPricingBundle):
raw = raw.sort_index().reset_index()
raw.rename(
columns={'index':'symbol'},
columns={'index': 'symbol'},
inplace=True,
)
raw = raw[raw['isFrozen'] == 0]
return raw
def post_process_symbol_metadata(self, asset_id, sym_md, sym_data):
start_date = sym_data.index[0]
end_date = sym_data.index[-1]
ac_date = end_date + pd.Timedelta(days=1)
min_trade_size = 0.00000001
return (
sym_md.symbol,
start_date,
end_date,
ac_date,
min_trade_size,
)
def fetch_raw_symbol_frame(self,
@@ -91,18 +94,31 @@ class PoloniexBundle(BaseCryptoPricingBundle):
start_date,
end_date,
frequency):
raw = pd.read_json(
self._format_data_url(
api_key,
symbol,
start_date,
end_date,
frequency,
),
orient='records',
)
raw.set_index('date', inplace=True)
# TODO: replace this with direct exchange call
# The end date and frequency should be used to
# calculate the number of bars
if(frequency == 'minute'):
pc = PoloniexCurator()
raw = pc.onemin_to_dataframe(symbol, start_date, end_date)
else:
raw = pd.read_json(
self._format_data_url(
api_key,
symbol,
start_date,
end_date,
frequency,
),
orient='records',
)
raw.set_index('date', inplace=True)
# BcolzDailyBarReader introduces a 1/1000 factor in the way
# pricing is stored on disk, which we compensate here to get
# the right pricing amounts
# ref: data/us_equity_pricing.py
scale = 1
raw.loc[:, 'open'] /= scale
raw.loc[:, 'high'] /= scale
@@ -123,7 +139,6 @@ class PoloniexBundle(BaseCryptoPricingBundle):
return self._format_polo_query(query_params)
def _format_data_url(self,
api_key,
symbol,
@@ -132,7 +147,6 @@ class PoloniexBundle(BaseCryptoPricingBundle):
data_frequency):
period_map = {
'daily': 86400,
# '5-minute': 300,
}
try:
@@ -147,21 +161,26 @@ class PoloniexBundle(BaseCryptoPricingBundle):
('end', end_date.value / 10**9),
('period', period),
]
return self._format_polo_query(query_params)
def _format_polo_query(self, query_params):
# TODO: got against the exchange object
return 'https://poloniex.com/public?{query}'.format(
query=urlencode(query_params),
)
'''
As a second parameter, you can pass an array of currency pairs
that will be processed as an asset_filter to only process that
'''
As a second parameter, you can pass an array of currency pairs
that will be processed as an asset_filter to only process that
subset of assets in the bundle, such as:
register_bundle(PoloniexBundle, ['USDT_BTC',])
For a production environment make sure to use (to bundle all pairs):
register_bundle(PoloniexBundle)
'''
register_bundle(PoloniexBundle)
if 'ingest' in sys.argv and '-c' in sys.argv:
register_bundle(PoloniexBundle)
else:
register_bundle(PoloniexBundle, create_writers=False)
+8 -17
View File
@@ -16,7 +16,6 @@
from datetime import datetime
import pandas as pd
from six.moves.urllib.parse import urlencode
from catalyst.data.bundles.core import register_bundle
@@ -26,23 +25,16 @@ from catalyst.utils.memoize import lazyval
"""
Module for building a complete daily dataset from Quandl's WIKI dataset.
"""
from itertools import count
import tarfile
from time import time, sleep
from datetime import datetime
from logbook import Logger
import pandas as pd
from six.moves.urllib.parse import urlencode
from catalyst.constants import LOG_LEVEL
from catalyst.utils.calendars import register_calendar_alias
from catalyst.utils.cli import maybe_show_progress
from . import core as bundles
log = Logger(__name__)
log = Logger(__name__, level=LOG_LEVEL)
seconds_per_call = (pd.Timedelta('10 minutes') / 2000).total_seconds()
class QuandlBundle(BaseEquityPricingBundle):
@lazyval
def name(self):
@@ -107,8 +99,8 @@ class QuandlBundle(BaseEquityPricingBundle):
# Filter out invalid symbols
raw = raw[~raw.symbol.isin(self._excluded_symbols)]
# cut out all the other stuff in the name column
# we need to escape the paren because it is actually splitting on a regex
# cut out all the other stuff in the name column. We need to
# escape the paren because it is actually splitting on a regex
raw.asset_name = raw.asset_name.str.split(r' \(', 1).str.get(0)
return raw
@@ -173,7 +165,6 @@ class QuandlBundle(BaseEquityPricingBundle):
df['sid'] = asset_id
self.splits.append(df)
def _update_dividends(self, asset_id, raw_data):
divs = raw_data.ex_dividend
df = pd.DataFrame({'amount': divs[divs != 0]})
@@ -184,7 +175,6 @@ class QuandlBundle(BaseEquityPricingBundle):
df['record_date'] = df['declared_date'] = df['pay_date'] = pd.NaT
self.dividends.append(df)
def _format_metadata_url(self, api_key, page_number):
"""Build the query RL for the quandl WIKI metadata.
"""
@@ -198,10 +188,10 @@ class QuandlBundle(BaseEquityPricingBundle):
query_params = [('api_key', api_key)] + query_params
return (
'https://www.quandl.com/api/v3/datasets.csv?' + urlencode(query_params)
'https://www.quandl.com/api/v3/datasets.csv?'
+ urlencode(query_params)
)
def _format_wiki_url(self,
api_key,
symbol,
@@ -227,5 +217,6 @@ class QuandlBundle(BaseEquityPricingBundle):
)
)
register_calendar_alias('QUANDL', 'NYSE')
register_bundle(QuandlBundle)
+9 -38
View File
@@ -42,7 +42,6 @@ from catalyst.assets.roll_finder import (
)
from catalyst.data.dispatch_bar_reader import (
AssetDispatchMinuteBarReader,
AssetDispatchFiveMinuteBarReader,
AssetDispatchSessionBarReader
)
from catalyst.data.resample import (
@@ -69,7 +68,9 @@ from catalyst.errors import (
HistoryWindowStartsBeforeData,
)
log = Logger('DataPortal')
from catalyst.constants import LOG_LEVEL
log = Logger('DataPortal', level=LOG_LEVEL)
BASE_FIELDS = frozenset([
"open",
@@ -120,10 +121,6 @@ class DataPortal(object):
daily data backtests or daily history calls in a minute backetest.
If a daily bar reader is not provided but a minute bar reader is,
the minutes will be rolled up to serve the daily requests.
five_minute_reader : BcolzFiveMinuteBarReader, optional
The five minute bar reader for equities. This will be used to service
5-minute data backtests or five-minute history calls. This can be used
to serve daily calls if no daily bar reader is provided.
minute_reader : BcolzMinuteBarReader, optional
The minute bar reader for equities. This will be used to service
minute data backtests or minute history calls. This can be used
@@ -150,7 +147,6 @@ class DataPortal(object):
trading_calendar,
first_trading_day,
daily_reader=None,
five_minute_reader=None,
minute_reader=None,
future_daily_reader=None,
future_minute_reader=None,
@@ -202,7 +198,6 @@ class DataPortal(object):
reader.last_available_dt
for reader in [
minute_reader,
five_minute_reader,
future_minute_reader,
]
if reader is not None
@@ -214,8 +209,6 @@ class DataPortal(object):
aligned_minute_reader = self._ensure_reader_aligned(
minute_reader)
aligned_five_minute_reader = self._ensure_reader_aligned(
five_minute_reader)
aligned_session_reader = self._ensure_reader_aligned(
daily_reader)
aligned_future_minute_reader = self._ensure_reader_aligned(
@@ -229,13 +222,10 @@ class DataPortal(object):
}
aligned_minute_readers = {}
aligned_five_minute_readers = {}
aligned_session_readers = {}
if aligned_minute_reader is not None:
aligned_minute_readers[Equity] = aligned_minute_reader
if aligned_five_minute_reader is not None:
aligned_five_minute_readers[Equity] = aligned_five_minute_reader
if aligned_session_reader is not None:
aligned_session_readers[Equity] = aligned_session_reader
@@ -267,13 +257,6 @@ class DataPortal(object):
self._last_available_minute,
)
_dispatch_five_minute_reader = AssetDispatchFiveMinuteBarReader(
self.trading_calendar,
self.asset_finder,
aligned_five_minute_readers,
self._last_available_minute,
)
_dispatch_session_reader = AssetDispatchSessionBarReader(
self.trading_calendar,
self.asset_finder,
@@ -283,7 +266,6 @@ class DataPortal(object):
self._pricing_readers = {
'minute': _dispatch_minute_reader,
'5-minute': _dispatch_five_minute_reader,
'daily': _dispatch_session_reader,
}
@@ -674,11 +656,11 @@ class DataPortal(object):
return spot_value
def _get_minutely_spot_value(self,
asset,
column,
dt,
data_frequency,
ffill=False):
asset,
column,
dt,
data_frequency,
ffill=False):
reader = self._get_pricing_reader(data_frequency)
@@ -719,23 +701,12 @@ class DataPortal(object):
spot_value=result
)
def _get_five_minute_spot_value(self, asset, column, dt, ffill=False):
return self._get_minutely_spot_value(
asset,
column,
dt,
ffill,
'5-minute',
)
def _get_minute_spot_value(self, asset, column, dt, ffill=False):
return self._get_minutely_spot_value(
asset,
column,
dt,
ffill,
ffill,
'minute',
)
+7 -8
View File
@@ -18,6 +18,7 @@ from numpy import (
full,
nan,
int64,
float64,
zeros
)
from six import iteritems, with_metaclass
@@ -70,7 +71,9 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)):
return self._dt_window_size(start_dt, end_dt), num_sids
def _make_raw_array_out(self, field, shape):
if field != 'volume' and field != 'sid':
if field == 'volume':
out = zeros(shape, dtype=float64)
elif field != 'sid':
out = full(shape, nan)
else:
out = zeros(shape, dtype=int64)
@@ -85,11 +88,11 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)):
if self._last_available_dt is not None:
return self._last_available_dt
else:
return min(r.last_available_dt for r in self._readers.values())
return min(r.last_available_dt for r in list(self._readers.values()))
@lazyval
def first_trading_day(self):
return max(r.first_trading_day for r in self._readers.values())
return max(r.first_trading_day for r in list(self._readers.values()))
def get_value(self, sid, dt, field):
asset = self._asset_finder.retrieve_asset(sid)
@@ -130,17 +133,13 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)):
return results
class AssetDispatchMinuteBarReader(AssetDispatchBarReader):
def _dt_window_size(self, start_dt, end_dt):
return len(self.trading_calendar.minutes_in_range(start_dt, end_dt))
class AssetDispatchFiveMinuteBarReader(AssetDispatchBarReader):
def _dt_window_size(self, start_dt, end_dt):
return len(self.trading_calendar.five_minutes_in_range(start_dt, end_dt))
class AssetDispatchSessionBarReader(AssetDispatchBarReader):
def _dt_window_size(self, start_dt, end_dt):
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -38,7 +38,7 @@ from catalyst.utils.numpy_utils import float64_dtype
from catalyst.utils.pandas_utils import find_in_sorted_index
# Default number of decimal places used for rounding asset prices.
DEFAULT_ASSET_PRICE_DECIMALS = 3
DEFAULT_ASSET_PRICE_DECIMALS = 9
class HistoryCompatibleUSEquityAdjustmentReader(object):
+134 -129
View File
@@ -17,36 +17,31 @@ from collections import OrderedDict
import logbook
import pandas as pd
import numpy as np
from pandas_datareader.data import DataReader
import datetime
import time
import pytz
from pandas_datareader.data import DataReader
from six import iteritems
from six.moves.urllib_error import HTTPError
from .benchmarks import get_benchmark_returns
from catalyst.constants import LOG_LEVEL
from catalyst.utils.calendars import get_calendar
from . import treasuries, treasuries_can
from .benchmarks import get_benchmark_returns
from ..utils.deprecate import deprecated
from ..utils.paths import (
cache_root,
data_root,
)
from ..utils.deprecate import deprecated
from catalyst.data.bundles.poloniex import PoloniexBundle
from catalyst.utils.calendars import get_calendar
logger = logbook.Logger('Loader')
logger = logbook.Logger('Loader', level=LOG_LEVEL)
# Mapping from index symbol to appropriate bond data
INDEX_MAPPING = {
'SPY':
(treasuries, 'treasury_curves.csv', 'www.federalreserve.gov'),
(treasuries, 'treasury_curves.csv', 'www.federalreserve.gov'),
'^GSPTSE':
(treasuries_can, 'treasury_curves_can.csv', 'bankofcanada.ca'),
(treasuries_can, 'treasury_curves_can.csv', 'bankofcanada.ca'),
'^FTSE': # use US treasuries until UK bonds implemented
(treasuries, 'treasury_curves.csv', 'www.federalreserve.gov'),
(treasuries, 'treasury_curves.csv', 'www.federalreserve.gov'),
}
ONE_HOUR = pd.Timedelta(hours=1)
@@ -94,19 +89,27 @@ def has_data_for_dates(series_or_df, first_date, last_date):
if not isinstance(dts, pd.DatetimeIndex):
raise TypeError("Expected a DatetimeIndex, but got %s." % type(dts))
first, last = dts[[0, -1]].tz_localize(None)
return (first <= first_date.tz_localize(None)) and (last >= last_date.tz_localize(None))
return (first <= first_date.tz_localize(None)) and (
last >= last_date.tz_localize(None))
def load_crypto_market_data(trading_day=None,
trading_days=None,
bm_symbol='USDT_BTC',
environ=None):
def load_crypto_market_data(trading_day=None, trading_days=None,
bm_symbol=None, bundle=None, bundle_data=None,
environ=None, exchange=None, start_dt=None,
end_dt=None):
if trading_day is None:
trading_day = get_calendar('OPEN').trading_day
if trading_days is None:
trading_days = get_calendar('OPEN').all_sessions
first_date = trading_days[0]
now = pd.Timestamp.utcnow()
# TODO: consider making configurable
bm_symbol = 'btc_usd'
# if trading_days is None:
# trading_days = get_calendar('OPEN').schedule
# if start_dt is None:
start_dt = get_calendar('OPEN').first_trading_session
if end_dt is None:
end_dt = pd.Timestamp.utcnow()
# We expect to have benchmark and treasury data that's current up until
# **two** full trading days prior to the most recently completed trading
@@ -122,30 +125,59 @@ def load_crypto_market_data(trading_day=None,
# We'll attempt to download new data if the latest entry in our cache is
# before this date.
last_date = trading_days[trading_days.get_loc(now, method='ffill') - 2]
'''
if(bundle_data):
# If we are using the bundle to retrieve the cryptobenchmark, find
# the last date for which there is trading data in the bundle
asset = bundle_data.asset_finder.lookup_symbol(
symbol=bm_symbol,as_of_date=None)
ix = bundle_data.daily_bar_reader._last_rows[asset.sid]
last_date = pd.to_datetime(
bundle_data.daily_bar_reader._spot_col('day')[ix],unit='s')
else:
last_date = trading_days[trading_days.get_loc(now, method='ffill') - 2]
'''
last_date = trading_days[trading_days.get_loc(end_dt, method='ffill') - 1]
br = ensure_crypto_benchmark_data(
bm_symbol,
first_date,
last_date,
now,
# We need the trading_day to figure out the close prior to the first
# date so that we can compute returns for the first date.
trading_day,
environ,
)
# Override first_date for treasury data since we have it for many more years
# and is independent of crypto data
first_date_treasury = pd.Timestamp('1990-01-01', tz='UTC')
if exchange is None:
# This is exceptional, since placing the import at the module scope
# breaks things and it's only needed here
from catalyst.exchange.utils.factory import get_exchange
exchange = get_exchange(
exchange_name='bitfinex', base_currency='usd'
)
exchange.init()
benchmark_asset = exchange.get_asset(bm_symbol)
# exchange.get_history_window() already ensures that we have the right data
# for the right dates
br = exchange.get_history_window_with_bundle(
assets=[benchmark_asset],
end_dt=last_date,
bar_count=pd.Timedelta(last_date - start_dt).days,
frequency='1d',
field='close',
data_frequency='daily',
force_auto_ingest=True)
br.columns = ['close']
br = br.pct_change(1).iloc[1:]
br.loc[start_dt] = 0
br = br.sort_index()
# Override first_date for treasury data since we have it for many more
# years and is independent of crypto data
first_date_treasury = pd.Timestamp('1990-01-02', tz='UTC')
tc = ensure_treasury_data(
bm_symbol,
first_date_treasury,
last_date,
now,
end_dt,
environ,
)
benchmark_returns = br[br.index.slice_indexer(first_date, last_date)]
treasury_curves = tc[tc.index.slice_indexer(first_date_treasury, last_date)]
benchmark_returns = br[br.index.slice_indexer(start_dt, last_date)]
treasury_curves = tc[
tc.index.slice_indexer(first_date_treasury, last_date)]
return benchmark_returns, treasury_curves
@@ -240,15 +272,16 @@ def ensure_crypto_benchmark_data(symbol,
last_date,
now,
trading_day,
bundle,
bundle_data,
environ=None):
filename = get_benchmark_filename(symbol)
logger.info(
('Loading benchmark data for {symbol!r} '
'from {first_date} to {last_date}'),
'from {first_date} to {last_date}'),
symbol=symbol,
first_date=first_date - trading_day,
first_date=first_date,
last_date=last_date
)
@@ -261,34 +294,66 @@ def ensure_crypto_benchmark_data(symbol,
environ,
)
if data is not None:
return data
# If no cached data was found or it was missing any dates then download the
# necessary data.
logger.info(
('Downloading benchmark data for {symbol!r} '
'from {first_date} to {last_date}'),
symbol=symbol,
first_date=first_date - trading_day,
last_date=last_date
)
# Load benchmark symbol from Poloniex API
try:
bundle = PoloniexBundle()
bench_raw = bundle._fetch_symbol_frame(
None,
symbol,
get_calendar(bundle.calendar_name),
first_date - trading_day,
last_date,
'daily',
)
except (OSError, IOError, HTTPError):
logger.exception('Failed to fetch new crypto benchmark returns')
raise
if (bundle == 'poloniex'):
'''
If we're using the Poloniex bundle, we'll get the benchmark from the
bundle instead of downloading it from Poloniex every time we need it.
Poloniex has a captcha for API queries originating from outside the US
that prevents users abroad from getting Catalyst to work
'''
logger.info(
('Retrieving benchmark data from bundle for {symbol!r}'
' from {first_date} to {last_date}'),
symbol=symbol, first_date=first_date, last_date=last_date)
asset = bundle_data.asset_finder.lookup_symbol(symbol=symbol,
as_of_date=None)
fields = ['day', 'close']
raw = bundle_data.daily_bar_reader.load_raw_arrays(
columns=fields,
start_date=first_date - trading_day,
end_date=last_date,
assets=[asset, ])
bench_raw = pd.concat([pd.DataFrame(raw[0], columns=['date']),
pd.DataFrame(raw[1], columns=['close'])],
axis=1)
bench_raw['date'] = pd.to_datetime(bench_raw['date'], unit='s')
bench_raw.set_index('date', inplace=True)
bench_raw.sort_index(inplace=True)
bench_raw = bench_raw[
pd.to_datetime(first_date - trading_day):pd.to_datetime(
last_date)]
else:
# This is how it used to be: downloading the benchmark everytime.
# Leaving this code here to be repurposed in the future for
# other bundles.
logger.info(
('Downloading benchmark data for {symbol!r}'
' from {first_date} to {last_date}'),
symbol=symbol, first_date=first_date, last_date=last_date)
raise DeprecationWarning('poloniex bundle deprecated')
# Load benchmark symbol from Poloniex API
# try:
# bundle = PoloniexBundle()
# bench_raw = bundle._fetch_symbol_frame(
# None,
# symbol,
# get_calendar(bundle.calendar_name),
# first_date - trading_day,
# last_date,
# 'daily',
# )
# except (OSError, IOError, HTTPError):
# logger.exception('Failed to fetch new crypto benchmark returns')
# raise
# select close column and compute percent change between days
daily_close = bench_raw[['close']]
@@ -347,68 +412,7 @@ def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day,
# necessary data.
logger.info(
('Downloading benchmark data for {symbol!r} '
'from {first_date} to {last_date}'),
symbol=symbol,
first_date=first_date - trading_day,
last_date=last_date
)
try:
data = get_benchmark_returns(
symbol,
first_date - trading_day,
last_date,
)
data.to_csv(get_data_filepath(filename, environ))
except (OSError, IOError, HTTPError):
logger.exception('Failed to cache the new benchmark returns')
raise
if not has_data_for_dates(data, first_date, last_date):
logger.warn("Still don't have expected data after redownload!")
return data
def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day,
environ=None):
"""
Ensure we have benchmark data for `symbol` from `first_date` to `last_date`
Parameters
----------
symbol : str
The symbol for the benchmark to load.
first_date : pd.Timestamp
First required date for the cache.
last_date : pd.Timestamp
Last required date for the cache.
now : pd.Timestamp
The current time. This is used to prevent repeated attempts to
re-download data that isn't available due to scheduling quirks or other
failures.
trading_day : pd.CustomBusinessDay
A trading day delta. Used to find the day before first_date so we can
get the close of the day prior to first_date.
We attempt to download data unless we already have data stored at the data
cache for `symbol` whose first entry is before or on `first_date` and whose
last entry is on or after `last_date`.
If we perform a download and the cache criteria are not satisfied, we wait
at least one hour before attempting a redownload. This is determined by
comparing the current time to the result of os.path.getmtime on the cache
path.
"""
filename = get_benchmark_filename(symbol)
data = _load_cached_data(filename, first_date, last_date, now, 'benchmark',
environ)
if data is not None:
return data
# If no cached data was found or it was missing any dates then download the
# necessary data.
logger.info(
('Downloading benchmark data for {symbol!r} '
'from {first_date} to {last_date}'),
'from {first_date} to {last_date}'),
symbol=symbol,
first_date=first_date - trading_day,
last_date=last_date
@@ -492,7 +496,8 @@ def _load_cached_data(filename, first_date, last_date, now, resource_name,
data = pd.DataFrame.from_csv(path)
if data.empty:
raise ValueError("File is empty.")
data.index = pd.to_datetime(data.index, infer_datetime_format=True, errors='coerce' ).tz_localize('UTC')
data.index = pd.to_datetime(data.index, infer_datetime_format=True,
errors='coerce').tz_localize('UTC')
if has_data_for_dates(data, first_date, last_date):
return data
@@ -518,7 +523,7 @@ def _load_cached_data(filename, first_date, last_date, now, resource_name,
)
logger.info(
"Cache at {path} does not have data from {start} to {end}.\n",
"Cache at {path} does not have data from {start} to {end}.",
start=first_date,
end=last_date,
path=path,
+57 -58
View File
@@ -39,20 +39,21 @@ from catalyst.data._minute_bar_internal import (
from catalyst.gens.sim_engine import NANOS_IN_MINUTE
from catalyst.data.bar_reader import BarReader, NoDataOnDate
from catalyst.data.us_equity_pricing import check_uint32_safe
from catalyst.data.us_equity_pricing import check_uint64_safe
from catalyst.utils.calendars import get_calendar
from catalyst.utils.cli import maybe_show_progress
from catalyst.utils.memoize import lazyval
from catalyst.constants import LOG_LEVEL
logger = logbook.Logger('MinuteBars')
logger = logbook.Logger('MinuteBars', level=LOG_LEVEL)
US_EQUITIES_MINUTES_PER_DAY = 390
FUTURES_MINUTES_PER_DAY = 1440
DEFAULT_EXPECTEDLEN = US_EQUITIES_MINUTES_PER_DAY * 252 * 15
OHLC_RATIO = 1000
OHLC_RATIO = 100000000
class BcolzMinuteOverlappingData(Exception):
@@ -114,15 +115,15 @@ def _sid_subdir_path(sid):
def convert_cols(cols, scale_factor, sid, invalid_data_behavior):
"""Adapt OHLCV columns into uint32 columns.
"""Adapt OHLCV columns into uint64 columns.
Parameters
----------
cols : dict
A dict mapping each column name (open, high, low, close, volume)
to a float column to convert to uint32.
to a float column to convert to uint64.
scale_factor : int
Factor to use to scale float values before converting to uint32.
Factor to use to scale float values before converting to uint64.
sid : int
Sid of the relevant asset, for logging.
invalid_data_behavior : str
@@ -135,6 +136,7 @@ def convert_cols(cols, scale_factor, sid, invalid_data_behavior):
scaled_highs = np.nan_to_num(cols['high']) * scale_factor
scaled_lows = np.nan_to_num(cols['low']) * scale_factor
scaled_closes = np.nan_to_num(cols['close']) * scale_factor
scaled_volumes = np.nan_to_num(cols['volume']) * scale_factor
exclude_mask = np.zeros_like(scaled_opens, dtype=bool)
@@ -143,11 +145,12 @@ def convert_cols(cols, scale_factor, sid, invalid_data_behavior):
('high', scaled_highs),
('low', scaled_lows),
('close', scaled_closes),
('volume', scaled_volumes),
]:
max_val = scaled_col.max()
try:
check_uint32_safe(max_val, col_name)
check_uint64_safe(max_val, col_name)
except ValueError:
if invalid_data_behavior == 'raise':
raise
@@ -155,20 +158,20 @@ def convert_cols(cols, scale_factor, sid, invalid_data_behavior):
if invalid_data_behavior == 'warn':
logger.warn(
'Values for sid={}, col={} contain some too large for '
'uint32 (max={}), filtering them out',
'uint64 (max={}), filtering them out',
sid, col_name, max_val,
)
# We want to exclude all rows that have an unsafe value in
# this column.
exclude_mask &= (scaled_col >= np.iinfo(np.uint32).max)
exclude_mask &= (scaled_col >= np.iinfo(np.uint64).max)
# Convert all cols to uint32.
opens = scaled_opens.astype(np.uint32)
highs = scaled_highs.astype(np.uint32)
lows = scaled_lows.astype(np.uint32)
closes = scaled_closes.astype(np.uint32)
volumes = cols['volume'].astype(np.uint32)
opens = scaled_opens.astype(np.uint64)
highs = scaled_highs.astype(np.uint64)
lows = scaled_lows.astype(np.uint64)
closes = scaled_closes.astype(np.uint64)
volumes = scaled_volumes.astype(np.uint64)
# Exclude rows with unsafe values by setting to zero.
opens[exclude_mask] = 0
@@ -260,14 +263,14 @@ class BcolzMinuteBarMetadata(object):
)
def __init__(
self,
default_ohlc_ratio,
ohlc_ratios_per_sid,
calendar,
start_session,
end_session,
minutes_per_day,
version=FORMAT_VERSION,
self,
default_ohlc_ratio,
ohlc_ratios_per_sid,
calendar,
start_session,
end_session,
minutes_per_day,
version=FORMAT_VERSION,
):
self.calendar = calendar
self.start_session = start_session
@@ -288,7 +291,7 @@ class BcolzMinuteBarMetadata(object):
ohlc_ratio : int
The default ratio by which to multiply the pricing data to
convert the floats from floats to an integer to fit within
the np.uint32. If ohlc_ratios_per_sid is None or does not
the np.uint64. If ohlc_ratios_per_sid is None or does not
contain a mapping for a given sid, this ratio is used.
ohlc_ratios_per_sid : dict
A dict mapping each sid in the output to the factor by
@@ -338,12 +341,10 @@ class BcolzMinuteBarMetadata(object):
'end_session': str(self.end_session.date()),
# Write these values for backwards compatibility
'first_trading_day': str(self.start_session.date()),
'market_opens': (
market_opens.values.astype('datetime64[m]').
astype(np.int64).tolist()),
'market_closes': (
market_closes.values.astype('datetime64[m]').
astype(np.int64).tolist()),
'market_opens': (market_opens.values.astype('datetime64[m]').
astype(np.int64).tolist()),
'market_closes': (market_closes.values.astype('datetime64[m]').
astype(np.int64).tolist()),
}
with open(self.metadata_path(rootdir), 'w+') as fp:
json.dump(metadata, fp)
@@ -372,13 +373,13 @@ class BcolzMinuteBarWriter(object):
The last trading session in the data set.
default_ohlc_ratio : int, optional
The default ratio by which to multiply the pricing data to
convert from floats to integers that fit within np.uint32. If
convert from floats to integers that fit within np.uint64. If
ohlc_ratios_per_sid is None or does not contain a mapping for a
given sid, this ratio is used. Default is OHLC_RATIO (1000).
given sid, this ratio is used. Default is OHLC_RATIO (10^8).
ohlc_ratios_per_sid : dict, optional
A dict mapping each sid in the output to the ratio by which to
multiply the pricing data to convert the floats from floats to
an integer to fit within the np.uint32.
an integer to fit within the np.uint64.
expectedlen : int, optional
The expected length of the dataset, used when creating the initial
bcolz ctable.
@@ -401,11 +402,9 @@ class BcolzMinuteBarWriter(object):
Each individual asset's data is stored as a bcolz table with a column for
each pricing field: (open, high, low, close, volume)
The open, high, low, and close columns are integers which are 1000 times
The open, high, low, close and volume columns are integers which are 10^8 times
the quoted price, so that the data can represented and stored as an
np.uint32, supporting market prices quoted up to the thousands place.
volume is a np.uint32 with no mutation of the tens place.
np.uint64, supporting market prices quoted up to the 1/10^8-th place.
The 'index' for each individual asset are a repeating period of minutes of
length `minutes_per_day` starting from each market open.
@@ -573,7 +572,7 @@ class BcolzMinuteBarWriter(object):
if not os.path.exists(sid_containing_dirname):
# Other sids may have already created the containing directory.
os.makedirs(sid_containing_dirname)
initial_array = np.empty(0, np.uint32)
initial_array = np.empty(0, np.uint64)
table = ctable(
rootdir=path,
columns=[
@@ -610,7 +609,7 @@ class BcolzMinuteBarWriter(object):
minute_offset = len(table) % self._minutes_per_day
num_to_prepend = numdays * self._minutes_per_day - minute_offset
prepend_array = np.zeros(num_to_prepend, np.uint32)
prepend_array = np.zeros(num_to_prepend, np.uint64)
# Fill all OHLCV with zeros.
table.append([prepend_array] * 5)
table.flush()
@@ -815,11 +814,11 @@ class BcolzMinuteBarWriter(object):
minutes_count = all_minutes_in_window.size
open_col = np.zeros(minutes_count, dtype=np.uint32)
high_col = np.zeros(minutes_count, dtype=np.uint32)
low_col = np.zeros(minutes_count, dtype=np.uint32)
close_col = np.zeros(minutes_count, dtype=np.uint32)
vol_col = np.zeros(minutes_count, dtype=np.uint32)
open_col = np.zeros(minutes_count, dtype=np.uint64)
high_col = np.zeros(minutes_count, dtype=np.uint64)
low_col = np.zeros(minutes_count, dtype=np.uint64)
close_col = np.zeros(minutes_count, dtype=np.uint64)
vol_col = np.zeros(minutes_count, dtype=np.uint64)
dt_ixs = np.searchsorted(all_minutes_in_window.values,
dts.astype('datetime64[ns]'))
@@ -914,10 +913,10 @@ class BcolzMinuteBarReader(MinuteBarReader):
)
self._schedule = self.calendar.schedule[slicer]
self._market_opens = self._schedule.market_open
self._market_open_values = self._market_opens.values.\
self._market_open_values = self._market_opens.values. \
astype('datetime64[m]').astype(np.int64)
self._market_closes = self._schedule.market_close
self._market_close_values = self._market_closes.values.\
self._market_close_values = self._market_closes.values. \
astype('datetime64[m]').astype(np.int64)
self._default_ohlc_inverse = 1.0 / metadata.default_ohlc_ratio
@@ -1125,8 +1124,8 @@ class BcolzMinuteBarReader(MinuteBarReader):
else:
return np.nan
if field != 'volume':
value *= self._ohlc_ratio_inverse_for_sid(sid)
# if field != 'volume':
value *= self._ohlc_ratio_inverse_for_sid(sid)
return value
def get_last_traded_dt(self, asset, dt):
@@ -1248,25 +1247,25 @@ class BcolzMinuteBarReader(MinuteBarReader):
if field != 'volume':
out = np.full(shape, np.nan)
else:
out = np.zeros(shape, dtype=np.uint32)
out = np.zeros(shape, dtype=np.float64)
for i, sid in enumerate(sids):
carray = self._open_minute_file(field, sid)
values = carray[start_idx:end_idx + 1]
if indices_to_exclude is not None:
for excl_start, excl_stop in indices_to_exclude[::-1]:
excl_slice = np.s_[
excl_start - start_idx:excl_stop - start_idx + 1]
excl_slice = np.s_[excl_start - start_idx:excl_stop
- start_idx + 1]
values = np.delete(values, excl_slice)
where = values != 0
# first slice down to len(where) because we might not have
# written data for all the minutes requested
if field != 'volume':
out[:len(where), i][where] = (
values[where] * self._ohlc_ratio_inverse_for_sid(sid))
else:
out[:len(where), i][where] = values[where]
# if field != 'volume':
out[:len(where), i][where] = (
values[where] * self._ohlc_ratio_inverse_for_sid(sid))
# else:
# out[:len(where), i][where] = values[where]
results.append(out)
return results
@@ -1319,9 +1318,8 @@ class H5MinuteBarUpdateWriter(object):
def __init__(self, path, complevel=None, complib=None):
self._complevel = complevel if complevel \
is not None else self._COMPLEVEL
self._complib = complib if complib \
is not None else self._COMPLIB
is not None else self._COMPLEVEL
self._complib = complib if complib is not None else self._COMPLIB
self._path = path
def write(self, frames):
@@ -1353,6 +1351,7 @@ class H5MinuteBarUpdateReader(MinuteBarUpdateReader):
path : str
The path of the HDF5 file from which to source data.
"""
def __init__(self, path):
self._panel = pd.read_hdf(path)
+4 -1
View File
@@ -156,7 +156,10 @@ class DailyHistoryAggregator(object):
cache = self._caches[field] = (session, market_open, {})
_, market_open, entries = cache
market_open = market_open.tz_localize('UTC')
try:
market_open = market_open.tz_localize('UTC')
except TypeError:
market_open = market_open.tz_convert('UTC')
if dt != market_open:
prev_dt = dt_value - self._one_min
else:
+20 -15
View File
@@ -11,6 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import division # Python2 req for division of ints yield float
from errno import ENOENT
from functools import partial
from os import remove
@@ -80,8 +83,9 @@ from catalyst.utils.cli import (
from ._equities import _compute_row_slices, _read_bcolz_data
from ._adjustments import load_adjustments_from_sqlite
from catalyst.constants import LOG_LEVEL
logger = logbook.Logger('UsEquityPricing')
logger = logbook.Logger('UsEquityPricing', level=LOG_LEVEL)
OHLC = frozenset(['open', 'high', 'low', 'close'])
OHLCV = frozenset(['open', 'high', 'low', 'close', 'volume'])
@@ -116,6 +120,9 @@ SQLITE_STOCK_DIVIDEND_PAYOUT_COLUMN_DTYPES = {
UINT32_MAX = iinfo(uint32).max
UINT64_MAX = iinfo(uint64).max
# Provides 9 decimals resolution. Also affects _equities.pyx L220
PRICE_ADJUSTMENT_FACTOR = 1000000000
def check_uint32_safe(value, colname):
if value >= UINT32_MAX:
@@ -124,6 +131,7 @@ def check_uint32_safe(value, colname):
"for uint32" % (value, colname)
)
def check_uint64_safe(value, colname):
if value >= UINT64_MAX:
raise ValueError(
@@ -316,8 +324,8 @@ class BcolzDailyBarWriter(object):
# Maps column name -> output carray.
columns = {
k: carray(array([], dtype=uint64))
if k in OHLCV
else carray(array([], dtype=uint32))
if k in OHLCV
else carray(array([], dtype=uint32))
for k in US_EQUITY_PRICING_BCOLZ_COLUMNS
}
@@ -433,11 +441,13 @@ class BcolzDailyBarWriter(object):
return raw_data
winsorise_uint64(raw_data, invalid_data_behavior, 'volume', *OHLC)
processed = (raw_data[list(OHLC)] * 1000000).astype('uint64')
processed = (raw_data[list(OHLC)]
* PRICE_ADJUSTMENT_FACTOR).astype('uint64')
dates = raw_data.index.values.astype('datetime64[s]')
check_uint32_safe(dates.max().view(np.int64), 'day')
processed['day'] = dates.astype('uint32')
processed['volume'] = raw_data.volume.astype('uint64')
processed['volume'] = (raw_data.volume
* PRICE_ADJUSTMENT_FACTOR).astype('uint64')
return ctable.fromdataframe(processed)
@@ -490,9 +500,8 @@ class BcolzDailyBarReader(SessionBarReader):
The data in these columns is interpreted as follows:
- Price columns ('open', 'high', 'low', 'close') are interpreted as 1000 *
as-traded dollar value.
- Volume is interpreted as as-traded volume.
- Price columns ('open', 'high', 'low', 'close') and Volume are interpreted
as 10^9 * as-traded dollar value.
- Day is interpreted as seconds since midnight UTC, Jan 1, 1970.
- Id is the asset id of the row.
@@ -519,7 +528,6 @@ class BcolzDailyBarReader(SessionBarReader):
# Need to test keeping the entire array in memory for the course of a
# process first.
self._spot_cols = {}
self.PRICE_ADJUSTMENT_FACTOR = 0.001
self._read_all_threshold = read_all_threshold
@lazyval
@@ -759,13 +767,10 @@ class BcolzDailyBarReader(SessionBarReader):
"""
ix = self.sid_day_index(sid, dt)
price = self._spot_col(field)[ix]
if field != 'volume':
if price == 0:
return nan
else:
return price * 0.001
if field != 'volume' and price == 0:
return nan
else:
return price
return price / PRICE_ADJUSTMENT_FACTOR
class PanelBarReader(SessionBarReader):
+3
View File
@@ -0,0 +1,3 @@
An overview of most of the trading strategies in this folder can be found in the
`Examples Algorithms <https://enigmampc.github.io/catalyst/example-algos.html>`_
section of our documentation website.
@@ -0,0 +1,282 @@
from logbook import Logger
from catalyst.api import (
record,
order,
symbol,
get_open_orders
)
from catalyst.exchange.utils.stats_utils import get_pretty_stats
from catalyst.utils.run_algo import run_algorithm
algo_namespace = 'arbitrage_eth_btc'
log = Logger(algo_namespace)
def initialize(context):
log.info('initializing arbitrage algorithm')
# The context contains a new "exchanges" attribute which is a dictionary
# of exchange objects by exchange name. This allow easy access to the
# exchanges.
context.buying_exchange = context.exchanges['poloniex']
context.selling_exchange = context.exchanges['bitfinex']
context.trading_pair_symbol = 'eth_btc'
context.trading_pairs = dict()
# Note the second parameter of the symbol() method
# Passing the exchange name here returns a TradingPair object including
# the exchange information. This allow all other operations using
# the TradingPair to target the correct exchange.
context.trading_pairs[context.buying_exchange] = \
symbol('eth_btc', context.buying_exchange.name)
context.trading_pairs[context.selling_exchange] = \
symbol(context.trading_pair_symbol, context.selling_exchange.name)
context.entry_points = [
dict(gap=0.03, amount=0.05),
dict(gap=0.04, amount=0.1),
dict(gap=0.05, amount=0.5),
]
context.exit_points = [
dict(gap=-0.02, amount=0.5),
]
context.SLIPPAGE_ALLOWED = 0.02
pass
def place_orders(context, amount, buying_price, selling_price, action):
"""
This method will always place two orders of the same amount to keep
the currency position the same as it moves between the two exchanges.
:param context: TradingAlgorithm
:param amount: float
The trading pair amount to trade on both exchanges.
:param buying_price: float
The current trading pair price on the buying exchange.
:param selling_price: float
The current trading pair price on the selling exchange.
:param action: string
"enter": buys on the buying exchange and sells on the selling exchange
"exit": buys on the selling exchange and sells on the buying exchange
:return:
"""
if action == 'enter':
enter_exchange = context.buying_exchange
entry_price = buying_price
exit_exchange = context.selling_exchange
exit_price = selling_price
elif action == 'exit':
enter_exchange = context.selling_exchange
entry_price = selling_price
exit_exchange = context.buying_exchange
exit_price = buying_price
else:
raise ValueError('invalid order action')
quote_currency = enter_exchange.quote_currency
quote_currency_amount = enter_exchange.portfolio.cash
exit_balances = exit_exchange.get_balances()
exit_currency = context.trading_pairs[
context.selling_exchange].quote_currency
if exit_currency in exit_balances:
quote_currency_amount = exit_balances[exit_currency]
else:
log.warn(
'the selling exchange {exchange_name} does not hold '
'currency {currency}'.format(
exchange_name=exit_exchange.name,
currency=exit_currency
)
)
return
if quote_currency_amount < (amount * entry_price):
adj_amount = quote_currency_amount / entry_price
log.warn(
'not enough {quote_currency} ({quote_currency_amount}) to buy '
'{amount}, adjusting the amount to {adj_amount}'.format(
quote_currency=quote_currency,
quote_currency_amount=quote_currency_amount,
amount=amount,
adj_amount=adj_amount
)
)
amount = adj_amount
elif quote_currency_amount < amount:
log.warn(
'not enough {currency} ({currency_amount}) to sell '
'{amount}, aborting'.format(
currency=exit_currency,
currency_amount=quote_currency_amount,
amount=amount
)
)
return
adj_buy_price = entry_price * (1 + context.SLIPPAGE_ALLOWED)
log.info(
'buying {amount} {trading_pair} on {exchange_name} with price '
'limit {limit_price}'.format(
amount=amount,
trading_pair=context.trading_pair_symbol,
exchange_name=enter_exchange.name,
limit_price=adj_buy_price
)
)
order(
asset=context.trading_pairs[enter_exchange],
amount=amount,
limit_price=adj_buy_price
)
adj_sell_price = exit_price * (1 - context.SLIPPAGE_ALLOWED)
log.info(
'selling {amount} {trading_pair} on {exchange_name} with price '
'limit {limit_price}'.format(
amount=-amount,
trading_pair=context.trading_pair_symbol,
exchange_name=exit_exchange.name,
limit_price=adj_sell_price
)
)
order(
asset=context.trading_pairs[exit_exchange],
amount=-amount,
limit_price=adj_sell_price
)
pass
def handle_data(context, data):
log.info('handling bar {}'.format(data.current_dt))
buying_price = data.current(
context.trading_pairs[context.buying_exchange], 'price')
log.info('price on buying exchange {exchange}: {price}'.format(
exchange=context.buying_exchange.name.upper(),
price=buying_price,
))
selling_price = data.current(
context.trading_pairs[context.selling_exchange], 'price')
log.info('price on selling exchange {exchange}: {price}'.format(
exchange=context.selling_exchange.name.upper(),
price=selling_price,
))
# If for example,
# selling price = 50
# buying price = 25
# expected gap = 1
# If follows that,
# selling price - buying price / buying price
# 50 - 25 / 25 = 1
gap = (selling_price - buying_price) / buying_price
log.info(
'the price gap: {gap} ({gap_percent}%)'.format(
gap=gap,
gap_percent=gap * 100
)
)
record(buying_price=buying_price, selling_price=selling_price, gap=gap)
# Waiting for orders to close before initiating new ones
for exchange in context.trading_pairs:
asset = context.trading_pairs[exchange]
orders = get_open_orders(asset)
if orders:
log.info(
'found {order_count} open orders on {exchange_name} '
'skipping bar until all open orders execute'.format(
order_count=len(orders),
exchange_name=exchange.name
)
)
return
# Consider the least ambitious entry point first
# Override of wider gap is found
entry_points = sorted(
context.entry_points,
key=lambda point: point['gap'],
)
buy_amount = None
for entry_point in entry_points:
if gap > entry_point['gap']:
buy_amount = entry_point['amount']
if buy_amount:
log.info('found buy trigger for amount: {}'.format(buy_amount))
place_orders(
context=context,
amount=buy_amount,
buying_price=buying_price,
selling_price=selling_price,
action='enter'
)
else:
# Consider the narrowest exit gap first
# Override of wider gap is found
exit_points = sorted(
context.exit_points,
key=lambda point: point['gap'],
reverse=True
)
sell_amount = None
for exit_point in exit_points:
if gap < exit_point['gap']:
sell_amount = exit_point['amount']
if sell_amount:
log.info('found sell trigger for amount: {}'.format(sell_amount))
place_orders(
context=context,
amount=sell_amount,
buying_price=buying_price,
selling_price=selling_price,
action='exit'
)
def analyze(context, stats):
log.info('the daily stats:\n{}'.format(get_pretty_stats(stats)))
pass
if __name__ == '__main__':
# The execution mode: backtest or live
MODE = 'live'
if MODE == 'live':
run_algorithm(
capital_base=0.1,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex,bitfinex',
live=True,
algo_namespace=algo_namespace,
base_currency='btc',
live_graph=False,
simulate_orders=True,
stats_output=None,
)
+52 -38
View File
@@ -14,36 +14,28 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pandas as pd
import matplotlib.pyplot as plt
from catalyst.api import (
order_target_value,
symbol,
record,
cancel_order,
get_open_orders,
)
from catalyst import run_algorithm
from catalyst.api import (order_target_value, symbol, record,
cancel_order, get_open_orders, )
def initialize(context):
context.ASSET_NAME = 'USDT_BTC'
context.ASSET_NAME = 'btc_usdt'
context.TARGET_HODL_RATIO = 0.8
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
# For all trading pairs in the poloniex bundle, the default denomination
# currently supported by Catalyst is 1/1000th of a full coin. Use this
# constant to scale the price of up to that of a full coin if desired.
context.TICK_SIZE = 1000.0
context.is_buying = True
context.asset = symbol(context.ASSET_NAME)
context.i = 0
def handle_data(context, data):
context.i += 1
print 'i:', context.i
starting_cash = context.portfolio.starting_cash
target_hodl_value = context.TARGET_HODL_RATIO * starting_cash
reserve_value = context.RESERVE_RATIO * starting_cash
@@ -52,60 +44,63 @@ def handle_data(context, data):
orders = get_open_orders(context.asset) or []
for order in orders:
cancel_order(order)
# Stop buying after passing the reserve threshold
cash = context.portfolio.cash
if cash <= reserve_value:
context.is_buying = False
# Retrieve current asset price from pricing data
price = data[context.asset].price
price = data.current(context.asset, 'price')
# Check if still buying and could (approximately) afford another purchase
if context.is_buying and cash > price:
print('buying')
# Place order to make position in asset equal to target_hodl_value
order_target_value(
context.asset,
target_hodl_value,
limit_price=price*1.1,
stop_price=price*0.9,
limit_price=price * 1.1,
)
record(
price=price,
volume=data.current(context.asset, 'volume'),
cash=cash,
starting_cash=context.portfolio.starting_cash,
leverage=context.account.leverage,
)
def analyze(context=None, results=None):
import matplotlib.pyplot as plt
# Plot the portfolio and asset data.
ax1 = plt.subplot(511)
results[['portfolio_value']].plot(ax=ax1)
ax1.set_ylabel('Portfolio Value (USD)')
ax2 = plt.subplot(512, sharex=ax1)
ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME))
(context.TICK_SIZE * results[['price']]).plot(ax=ax2)
def analyze(context=None, results=None):
# Plot the portfolio and asset data.
ax1 = plt.subplot(611)
results[['portfolio_value']].plot(ax=ax1)
ax1.set_ylabel('Portfolio\nValue\n(USD)')
ax2 = plt.subplot(612, sharex=ax1)
ax2.set_ylabel('{asset}\n(USD)'.format(asset=context.ASSET_NAME))
results[['price']].plot(ax=ax2)
trans = results.ix[[t != [] for t in results.transactions]]
buys = trans.ix[
[t[0]['amount'] > 0 for t in trans.transactions]
]
ax2.plot(
buys.index,
context.TICK_SIZE * results.price[buys.index],
'^',
markersize=10,
color='g',
ax2.scatter(
buys.index.to_pydatetime(),
results.price[buys.index],
marker='^',
s=100,
c='g',
label=''
)
ax3 = plt.subplot(513, sharex=ax1)
ax3 = plt.subplot(613, sharex=ax1)
results[['leverage', 'alpha', 'beta']].plot(ax=ax3)
ax3.set_ylabel('Leverage ')
ax4 = plt.subplot(514, sharex=ax1)
ax4 = plt.subplot(614, sharex=ax1)
results[['starting_cash', 'cash']].plot(ax=ax4)
ax4.set_ylabel('Cash (USD)')
@@ -119,16 +114,35 @@ def analyze(context=None, results=None):
'benchmark_period_return',
]]
ax5 = plt.subplot(515, sharex=ax1)
ax5 = plt.subplot(615, sharex=ax1)
results[[
'treasury',
'algorithm',
'benchmark',
]].plot(ax=ax5)
ax5.set_ylabel('Percent Change')
ax5.set_ylabel('Percent\nChange')
ax6 = plt.subplot(616, sharex=ax1)
results[['volume']].plot(ax=ax6)
ax6.set_ylabel('Volume')
plt.legend(loc=3)
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
if __name__ == '__main__':
run_algorithm(
capital_base=10000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace='buy_and_hodl',
base_currency='usdt',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
-79
View File
@@ -1,79 +0,0 @@
from catalyst.utils.run_algo import run_algorithm
from datetime import datetime
import pytz
from catalyst.api import (
order_target_value,
symbol,
record,
cancel_order,
get_open_orders,
)
def initialize(context):
context.ASSET_NAME = 'USDT_BTC'
context.TARGET_HODL_RATIO = 0.8
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
# For all trading pairs in the poloniex bundle, the default denomination
# currently supported by Catalyst is 1/1000th of a full coin. Use this
# constant to scale the price of up to that of a full coin if desired.
context.TICK_SIZE = 1000.0
context.is_buying = True
context.asset = symbol(context.ASSET_NAME)
context.i = 0
def handle_data(context, data):
context.i += 1
print 'i:', context.i
starting_cash = context.portfolio.starting_cash
target_hodl_value = context.TARGET_HODL_RATIO * starting_cash
reserve_value = context.RESERVE_RATIO * starting_cash
# Cancel any outstanding orders
orders = get_open_orders(context.asset) or []
for order in orders:
cancel_order(order)
# Stop buying after passing the reserve threshold
cash = context.portfolio.cash
if cash <= reserve_value:
context.is_buying = False
# Retrieve current asset price from pricing data
price = data[context.asset].price
# Check if still buying and could (approximately) afford another purchase
if context.is_buying and cash > price:
# Place order to make position in asset equal to target_hodl_value
order_target_value(
context.asset,
target_hodl_value,
limit_price=price * 1.1,
stop_price=price * 0.9,
)
record(
price=price,
cash=cash,
starting_cash=context.portfolio.starting_cash,
leverage=context.account.leverage,
)
start = datetime(2015, 3, 1, 0, 0, 0, 0, pytz.utc)
end = datetime(2017, 6, 28, 0, 0, 0, 0, pytz.utc)
run_algorithm(
initialize=initialize,
handle_data=handle_data,
start=start,
end=end,
capital_base=100000,
bundle='poloniex'
)
+49
View File
@@ -0,0 +1,49 @@
'''
This is a very simple example referenced in the beginner's tutorial:
https://enigmampc.github.io/catalyst/beginner-tutorial.html
Run this example, by executing the following from your terminal:
catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 \
--end 2017-9-30 -o buy_btc_simple_out.pickle
If you want to run this code using another exchange, make sure that
the asset is available on that exchange. For example, if you were to run
it for exchange Poloniex, you would need to edit the following line:
context.asset = symbol('btc_usdt') # note 'usdt' instead of 'usd'
and specify exchange poloniex as follows:
catalyst ingest-exchange -x poloniex -f daily -i btc_usdt
catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 \
--end 2017-9-30 -o buy_btc_simple_out.pickle
To see which assets are available on each exchange, visit:
https://www.enigma.co/catalyst/status
'''
from catalyst import run_algorithm
from catalyst.api import order, record, symbol
import pandas as pd
def initialize(context):
context.asset = symbol('btc_usdt')
def handle_data(context, data):
order(context.asset, 1)
record(btc=data.current(context.asset, 'price'))
if __name__ == '__main__':
run_algorithm(
capital_base=10000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
exchange_name='poloniex',
algo_namespace='buy_and_hodl',
base_currency='usdt',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
@@ -1,4 +1,5 @@
import talib
import pandas as pd
from logbook import Logger
from catalyst.api import (
@@ -8,55 +9,52 @@ from catalyst.api import (
record,
get_open_orders,
)
from catalyst.exchange.utils.stats_utils import get_pretty_stats
from catalyst.utils.run_algo import run_algorithm
algo_namespace = 'buy_the_dip_live'
log = Logger(algo_namespace)
log = Logger('buy low sell high')
def initialize(context):
log.info('initializing algo')
context.ASSET_NAME = 'XRP_USD'
context.ASSET_NAME = 'btc_usdt'
context.asset = symbol(context.ASSET_NAME)
context.TARGET_POSITIONS = 5000
context.TARGET_POSITIONS = 30
context.PROFIT_TARGET = 0.1
context.SLIPPAGE_ALLOWED = 0.02
context.retry_check_open_orders = 10
context.retry_update_portfolio = 10
context.retry_order = 5
context.errors = []
pass
def _handle_data(context, data):
price = data.current(context.asset, 'price')
log.info('got price {price}'.format(price=price))
prices = data.history(
context.asset,
fields='price',
bar_count=20,
frequency='15m'
frequency='1D'
)
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
log.info('got rsi: {}'.format(rsi))
# Buying more when RSI is low, this should lower our cost basis
if rsi <= 30:
buy_increment = 50
buy_increment = 1
elif rsi <= 40:
buy_increment = 20
buy_increment = 0.5
elif rsi <= 70:
buy_increment = 5
buy_increment = 0.2
else:
buy_increment = None
buy_increment = 0.1
cash = context.portfolio.cash
log.info('base currency available: {cash}'.format(cash=cash))
price = data.current(context.asset, 'price')
log.info('got price {price}'.format(price=price))
record(
price=price,
rsi=rsi,
@@ -86,8 +84,8 @@ def _handle_data(context, data):
if price < cost_basis:
is_buy = True
elif position.amount > 0 and \
price > cost_basis * (1 + context.PROFIT_TARGET):
elif (position.amount > 0
and price > cost_basis * (1 + context.PROFIT_TARGET)):
profit = (price * position.amount) - (cost_basis * position.amount)
log.info('closing position, taking profit: {}'.format(profit))
order_target_percent(
@@ -140,16 +138,34 @@ def handle_data(context, data):
def analyze(context, stats):
log.info('the full stats:\n{}'.format(stats.head()))
log.info('the daily stats:\n{}'.format(get_pretty_stats(stats)))
pass
run_algorithm(
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
live=True,
algo_namespace=algo_namespace,
base_currency='usd'
)
if __name__ == '__main__':
live = True
if live:
run_algorithm(
capital_base=0.001,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='binance',
live=True,
algo_namespace=algo_namespace,
base_currency='btc',
simulate_orders=True,
)
else:
run_algorithm(
capital_base=10000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace='buy_and_hodl',
base_currency='usdt',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
+163
View File
@@ -0,0 +1,163 @@
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import (record, symbol, order_target_percent,
get_open_orders)
from catalyst.exchange.utils.stats_utils import extract_transactions
NAMESPACE = 'dual_moving_average'
log = Logger(NAMESPACE)
def initialize(context):
context.i = 0
context.asset = symbol('ltc_usd')
context.base_price = None
def handle_data(context, data):
# define the windows for the moving averages
short_window = 50
long_window = 200
# Skip as many bars as long_window to properly compute the average
context.i += 1
if context.i < long_window:
return
# Compute moving averages calling data.history() for each
# moving average with the appropriate parameters. We choose to use
# minute bars for this simulation -> freq="1m"
# Returns a pandas dataframe.
short_mavg = data.history(context.asset,
'price',
bar_count=short_window,
frequency="1m",
).mean()
long_mavg = data.history(context.asset,
'price',
bar_count=long_window,
frequency="1m",
).mean()
# Let's keep the price of our asset in a more handy variable
price = data.current(context.asset, 'price')
# If base_price is not set, we use the current value. This is the
# price at the first bar which we reference to calculate price_change.
if context.base_price is None:
context.base_price = price
price_change = (price - context.base_price) / context.base_price
# Save values for later inspection
record(price=price,
cash=context.portfolio.cash,
price_change=price_change,
short_mavg=short_mavg,
long_mavg=long_mavg)
# Since we are using limit orders, some orders may not execute immediately
# we wait until all orders are executed before considering more trades.
orders = get_open_orders(context.asset)
if len(orders) > 0:
return
# Exit if we cannot trade
if not data.can_trade(context.asset):
return
# We check what's our position on our portfolio and trade accordingly
pos_amount = context.portfolio.positions[context.asset].amount
# Trading logic
if short_mavg > long_mavg and pos_amount == 0:
# we buy 100% of our portfolio for this asset
order_target_percent(context.asset, 1)
elif short_mavg < long_mavg and pos_amount > 0:
# we sell all our positions for this asset
order_target_percent(context.asset, 0)
def analyze(context, perf):
# Get the base_currency that was passed as a parameter to the simulation
exchange = list(context.exchanges.values())[0]
base_currency = exchange.base_currency.upper()
# First chart: Plot portfolio value using base_currency
ax1 = plt.subplot(411)
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
ax1.legend_.remove()
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
start, end = ax1.get_ylim()
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
# Second chart: Plot asset price, moving averages and buys/sells
ax2 = plt.subplot(412, sharex=ax1)
perf.loc[:, ['price', 'short_mavg', 'long_mavg']].plot(
ax=ax2,
label='Price')
ax2.legend_.remove()
ax2.set_ylabel('{asset}\n({base})'.format(
asset=context.asset.symbol,
base=base_currency
))
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
transaction_df = extract_transactions(perf)
if not transaction_df.empty:
buy_df = transaction_df[transaction_df['amount'] > 0]
sell_df = transaction_df[transaction_df['amount'] < 0]
ax2.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index, 'price'],
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index, 'price'],
marker='v',
s=100,
c='red',
label=''
)
# Third chart: Compare percentage change between our portfolio
# and the price of the asset
ax3 = plt.subplot(413, sharex=ax1)
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
ax3.legend_.remove()
ax3.set_ylabel('Percent Change')
start, end = ax3.get_ylim()
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
# Fourth chart: Plot our cash
ax4 = plt.subplot(414, sharex=ax1)
perf.cash.plot(ax=ax4)
ax4.set_ylabel('Cash\n({})'.format(base_currency))
start, end = ax4.get_ylim()
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
plt.show()
if __name__ == '__main__':
run_algorithm(
capital_base=1000,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
base_currency='usd',
start=pd.to_datetime('2017-9-22', utc=True),
end=pd.to_datetime('2017-9-23', utc=True),
)
-188
View File
@@ -1,188 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2017 Enigma MPC, Inc.
# Copyright 2014 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from catalyst.api import (
order_target_percent,
record,
symbol,
get_open_orders,
set_max_leverage,
schedule_function,
date_rules,
attach_pipeline,
pipeline_output,
)
from catalyst.pipeline import Pipeline
from catalyst.pipeline.data import CryptoPricing
from catalyst.pipeline.factors.crypto import VWAP
def initialize(context):
context.ASSET_NAME = 'USDT_BTC'
context.TARGET_INVESTMENT_RATIO = 0.8
context.SHORT_WINDOW = 30
context.LONG_WINDOW = 100
# For all trading pairs in the poloniex bundle, the default denomination
# currently supported by Catalyst is 1/1000th of a full coin. Use this
# constant to scale the price of up to that of a full coin if desired.
context.TICK_SIZE = 1000.0
context.i = 0
context.asset = symbol(context.ASSET_NAME)
set_max_leverage(1.0)
attach_pipeline(make_pipeline(context), 'vwap_pipeline')
schedule_function(
rebalance,
time_rules=times_rules.every_minute(),
)
def before_trading_start(context, data):
context.pipeline_data = pipeline_output('vwap_pipeline')
def make_pipeline(context):
return Pipeline(
columns={
'price': CryptoPricing.open.latest,
'volume': CryptoPricing.volume.latest,
'short_mavg': VWAP(window_length=context.SHORT_WINDOW),
'long_mavg': VWAP(window_length=context.LONG_WINDOW),
}
)
def rebalance(context, data):
context.i += 1
# skip first LONG_WINDOW bars to fill windows
if context.i < context.LONG_WINDOW:
return
# get pipeline data for asset of interest
pipeline_data = context.pipeline_data
pipeline_data = pipeline_data[pipeline_data.index == context.asset].iloc[0]
# retrieve long and short moving averages from pipeline
short_mavg = pipeline_data.short_mavg
long_mavg = pipeline_data.long_mavg
price = pipeline_data.price
volume = pipeline_data.volume
# check that order has not already been placed
open_orders = get_open_orders()
if context.asset not in open_orders:
# check that the asset of interest can currently be traded
if data.can_trade(context.asset):
# adjust portfolio based on comparison of long and short vwap
if short_mavg > long_mavg:
order_target_percent(
context.asset,
context.TARGET_INVESTMENT_RATIO,
)
elif short_mavg < long_mavg:
order_target_percent(
context.asset,
0.0,
)
record(
price=price,
cash=context.portfolio.cash,
leverage=context.account.leverage,
short_mavg=short_mavg,
long_mavg=long_mavg,
volume=volume,
)
def analyze(context=None, results=None):
import matplotlib.pyplot as plt
# Plot the portfolio and asset data.
ax1 = plt.subplot(611)
results[['portfolio_value']].plot(ax=ax1)
ax1.set_ylabel('Portfolio value (USD)')
ax2 = plt.subplot(612, sharex=ax1)
ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME))
(context.TICK_SIZE*results[['price', 'short_mavg', 'long_mavg']]).plot(ax=ax2)
trans = results.ix[[t != [] for t in results.transactions]]
amounts = [t[0]['amount'] for t in trans.transactions]
buys = trans.ix[
[t[0]['amount'] > 0 for t in trans.transactions]
]
sells = trans.ix[
[t[0]['amount'] < 0 for t in trans.transactions]
]
ax2.plot(
buys.index,
context.TICK_SIZE * results.price[buys.index],
'^',
markersize=10,
color='g',
)
ax2.plot(
sells.index,
context.TICK_SIZE * results.price[sells.index],
'v',
markersize=10,
color='r',
)
ax3 = plt.subplot(613, sharex=ax1)
results[['leverage', 'alpha', 'beta']].plot(ax=ax3)
ax3.set_ylabel('Leverage (USD)')
ax4 = plt.subplot(614, sharex=ax1)
results[['cash']].plot(ax=ax4)
ax4.set_ylabel('Cash (USD)')
results[[
'treasury',
'algorithm',
'benchmark',
]] = results[[
'treasury_period_return',
'algorithm_period_return',
'benchmark_period_return',
]]
ax5 = plt.subplot(615, sharex=ax1)
results[[
'treasury',
'algorithm',
'benchmark',
]].plot(ax=ax5)
ax5.set_ylabel('Percent Change')
ax6 = plt.subplot(616, sharex=ax1)
results[['volume']].plot(ax=ax6)
ax6.set_ylabel('Volume (mBTC/day)')
plt.legend(loc=3)
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
+289
View File
@@ -0,0 +1,289 @@
# For this example, we're going to write a simple momentum script. When the
# stock goes up quickly, we're going to buy; when it goes down quickly, we're
# going to sell. Hopefully we'll ride the waves.
import os
import tempfile
import time
import numpy as np
import pandas as pd
import talib
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import symbol, record, order_target_percent, get_open_orders
from catalyst.exchange.utils.stats_utils import extract_transactions
# We give a name to the algorithm which Catalyst will use to persist its state.
# In this example, Catalyst will create the `.catalyst/data/live_algos`
# directory. If we stop and start the algorithm, Catalyst will resume its
# state using the files included in the folder.
from catalyst.utils.paths import ensure_directory
NAMESPACE = 'mean_reversion_simple'
log = Logger(NAMESPACE)
# To run an algorithm in Catalyst, you need two functions: initialize and
# handle_data.
def initialize(context):
# This initialize function sets any data or variables that you'll use in
# your algorithm. For instance, you'll want to define the trading pair (or
# trading pairs) you want to backtest. You'll also want to define any
# parameters or values you're going to use.
# In our example, we're looking at Neo in Ether.
context.market = symbol('eth_btc')
context.base_price = None
context.current_day = None
context.RSI_OVERSOLD = 55
context.RSI_OVERBOUGHT = 60
context.CANDLE_SIZE = '15T'
context.start_time = time.time()
context.set_commission(maker=0.001, taker=0.002)
context.set_slippage(spread=0.001)
def handle_data(context, data):
# This handle_data function is where the real work is done. Our data is
# minute-level tick data, and each minute is called a frame. This function
# runs on each frame of the data.
# We flag the first period of each day.
# Since cryptocurrencies trade 24/7 the `before_trading_starts` handle
# would only execute once. This method works with minute and daily
# frequencies.
today = data.current_dt.floor('1D')
if today != context.current_day:
context.traded_today = False
context.current_day = today
# We're computing the volume-weighted-average-price of the security
# defined above, in the context.market variable. For this example, we're
# using three bars on the 15 min bars.
# The frequency attribute determine the bar size. We use this convention
# for the frequency alias:
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
prices = data.history(
context.market,
fields='close',
bar_count=50,
frequency=context.CANDLE_SIZE
)
# Ta-lib calculates various technical indicator based on price and
# volume arrays.
# In this example, we are comp
rsi = talib.RSI(prices.values, timeperiod=14)
# We need a variable for the current price of the security to compare to
# the average. Since we are requesting two fields, data.current()
# returns a DataFrame with
current = data.current(context.market, fields=['close', 'volume'])
price = current['close']
# If base_price is not set, we use the current value. This is the
# price at the first bar which we reference to calculate price_change.
if context.base_price is None:
context.base_price = price
price_change = (price - context.base_price) / context.base_price
cash = context.portfolio.cash
# Now that we've collected all current data for this frame, we use
# the record() method to save it. This data will be available as
# a parameter of the analyze() function for further analysis.
record(
volume=current['volume'],
price=price,
price_change=price_change,
rsi=rsi[-1],
cash=cash
)
# We are trying to avoid over-trading by limiting our trades to
# one per day.
if context.traded_today:
return
# TODO: retest with open orders
# Since we are using limit orders, some orders may not execute immediately
# we wait until all orders are executed before considering more trades.
orders = context.blotter.open_orders
if len(orders) > 0:
log.info('exiting because orders are open: {}'.format(orders))
return
# Exit if we cannot trade
if not data.can_trade(context.market):
return
# Another powerful built-in feature of the Catalyst backtester is the
# portfolio object. The portfolio object tracks your positions, cash,
# cost basis of specific holdings, and more. In this line, we calculate
# how long or short our position is at this minute.
pos_amount = context.portfolio.positions[context.market].amount
if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0:
log.info(
'{}: buying - price: {}, rsi: {}'.format(
data.current_dt, price, rsi[-1]
)
)
# Set a style for limit orders,
limit_price = price * 1.005
order_target_percent(
context.market, 1, limit_price=limit_price
)
context.traded_today = True
elif rsi[-1] >= context.RSI_OVERBOUGHT and pos_amount > 0:
log.info(
'{}: selling - price: {}, rsi: {}'.format(
data.current_dt, price, rsi[-1]
)
)
limit_price = price * 0.995
order_target_percent(
context.market, 0, limit_price=limit_price
)
context.traded_today = True
def analyze(context=None, perf=None):
end = time.time()
log.info('elapsed time: {}'.format(end - context.start_time))
import matplotlib.pyplot as plt
# The base currency of the algo exchange
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
perf.loc[:, 'portfolio_value'].plot(ax=ax1)
ax1.set_ylabel('Portfolio\nValue\n({})'.format(base_currency))
# Plot the price increase or decrease over time.
ax2 = plt.subplot(612, sharex=ax1)
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
ax2.set_ylabel('{asset}\n({base})'.format(
asset=context.market.symbol, base=base_currency
))
transaction_df = extract_transactions(perf)
if not transaction_df.empty:
buy_df = transaction_df[transaction_df['amount'] > 0]
sell_df = transaction_df[transaction_df['amount'] < 0]
ax2.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index.floor('1 min'), 'price'],
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index.floor('1 min'), 'price'],
marker='v',
s=100,
c='red',
label=''
)
ax4 = plt.subplot(613, sharex=ax1)
perf.loc[:, 'cash'].plot(
ax=ax4, label='Base Currency ({})'.format(base_currency)
)
ax4.set_ylabel('Cash\n({})'.format(base_currency))
perf['algorithm'] = perf.loc[:, 'algorithm_period_return']
ax5 = plt.subplot(614, sharex=ax1)
perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
ax5.set_ylabel('Percent\nChange')
ax6 = plt.subplot(615, sharex=ax1)
perf.loc[:, 'rsi'].plot(ax=ax6, label='RSI')
ax6.set_ylabel('RSI')
ax6.axhline(context.RSI_OVERBOUGHT, color='darkgoldenrod')
ax6.axhline(context.RSI_OVERSOLD, color='darkgoldenrod')
if not transaction_df.empty:
ax6.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index.floor('1 min'), 'rsi'],
marker='^',
s=100,
c='green',
label=''
)
ax6.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index.floor('1 min'), 'rsi'],
marker='v',
s=100,
c='red',
label=''
)
plt.legend(loc=3)
start, end = ax6.get_ylim()
ax6.yaxis.set_ticks(np.arange(0, end, end / 5))
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
pass
if __name__ == '__main__':
# The execution mode: backtest or live
live = True
if live:
run_algorithm(
capital_base=0.01,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
live=True,
algo_namespace=NAMESPACE,
base_currency='btc',
live_graph=False,
simulate_orders=False,
stats_output=None,
# auth_aliases=dict(poloniex='auth2')
)
else:
folder = os.path.join(
tempfile.gettempdir(), 'catalyst', NAMESPACE
)
ensure_directory(folder)
timestr = time.strftime('%Y%m%d-%H%M%S')
out = os.path.join(folder, '{}.p'.format(timestr))
# catalyst run -f catalyst/examples/mean_reversion_simple.py \
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
# --data-frequency minute --capital-base 10000
run_algorithm(
capital_base=0.1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
base_currency='btc',
start=pd.to_datetime('2017-10-01', utc=True),
end=pd.to_datetime('2017-11-10', utc=True),
output=out
)
log.info('saved perf stats: {}'.format(out))
@@ -0,0 +1,288 @@
# For this example, we're going to write a simple momentum script. When the
# stock goes up quickly, we're going to buy; when it goes down quickly, we're
# going to sell. Hopefully we'll ride the waves.
import os
import tempfile
import time
import numpy as np
import pandas as pd
import talib
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import symbol, record, order_target_percent, get_open_orders
from catalyst.exchange.utils.stats_utils import extract_transactions
# We give a name to the algorithm which Catalyst will use to persist its state.
# In this example, Catalyst will create the `.catalyst/data/live_algos`
# directory. If we stop and start the algorithm, Catalyst will resume its
# state using the files included in the folder.
from catalyst.utils.paths import ensure_directory
NAMESPACE = 'mean_reversion_simple'
log = Logger(NAMESPACE)
# To run an algorithm in Catalyst, you need two functions: initialize and
# handle_data.
def initialize(context):
# This initialize function sets any data or variables that you'll use in
# your algorithm. For instance, you'll want to define the trading pair (or
# trading pairs) you want to backtest. You'll also want to define any
# parameters or values you're going to use.
# In our example, we're looking at Neo in Ether.
context.market = symbol('eth_btc')
context.base_price = None
context.current_day = None
context.RSI_OVERSOLD = 50
context.RSI_OVERBOUGHT = 60
context.CANDLE_SIZE = '5T'
context.start_time = time.time()
context.set_commission(maker=0.001, taker=0.002)
# context.set_slippage(spread=0.001)
def handle_data(context, data):
# This handle_data function is where the real work is done. Our data is
# minute-level tick data, and each minute is called a frame. This function
# runs on each frame of the data.
# We flag the first period of each day.
# Since cryptocurrencies trade 24/7 the `before_trading_starts` handle
# would only execute once. This method works with minute and daily
# frequencies.
today = data.current_dt.floor('1D')
if today != context.current_day:
context.traded_today = False
context.current_day = today
# We're computing the volume-weighted-average-price of the security
# defined above, in the context.market variable. For this example, we're
# using three bars on the 15 min bars.
# The frequency attribute determine the bar size. We use this convention
# for the frequency alias:
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
prices = data.history(
context.market,
fields='close',
bar_count=50,
frequency=context.CANDLE_SIZE
)
# Ta-lib calculates various technical indicator based on price and
# volume arrays.
# In this example, we are comp
rsi = talib.RSI(prices.values, timeperiod=14)
# We need a variable for the current price of the security to compare to
# the average. Since we are requesting two fields, data.current()
# returns a DataFrame with
current = data.current(context.market, fields=['close', 'volume'])
price = current['close']
# If base_price is not set, we use the current value. This is the
# price at the first bar which we reference to calculate price_change.
if context.base_price is None:
context.base_price = price
price_change = (price - context.base_price) / context.base_price
cash = context.portfolio.cash
# Now that we've collected all current data for this frame, we use
# the record() method to save it. This data will be available as
# a parameter of the analyze() function for further analysis.
record(
volume=current['volume'],
price=price,
price_change=price_change,
rsi=rsi[-1],
cash=cash
)
# We are trying to avoid over-trading by limiting our trades to
# one per day.
if context.traded_today:
return
# TODO: retest with open orders
# Since we are using limit orders, some orders may not execute immediately
# we wait until all orders are executed before considering more trades.
orders = get_open_orders(context.market)
if len(orders) > 0:
log.info('exiting because orders are open: {}'.format(orders))
return
# Exit if we cannot trade
if not data.can_trade(context.market):
return
# Another powerful built-in feature of the Catalyst backtester is the
# portfolio object. The portfolio object tracks your positions, cash,
# cost basis of specific holdings, and more. In this line, we calculate
# how long or short our position is at this minute.
pos_amount = context.portfolio.positions[context.market].amount
if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0:
log.info(
'{}: buying - price: {}, rsi: {}'.format(
data.current_dt, price, rsi[-1]
)
)
# Set a style for limit orders,
limit_price = price * 1.005
order_target_percent(
context.market, 1, limit_price=limit_price
)
context.traded_today = True
elif rsi[-1] >= context.RSI_OVERBOUGHT and pos_amount > 0:
log.info(
'{}: selling - price: {}, rsi: {}'.format(
data.current_dt, price, rsi[-1]
)
)
limit_price = price * 0.995
order_target_percent(
context.market, 0, limit_price=limit_price
)
context.traded_today = True
def analyze(context=None, perf=None):
end = time.time()
log.info('elapsed time: {}'.format(end - context.start_time))
import matplotlib.pyplot as plt
# The base currency of the algo exchange
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
perf.loc[:, 'portfolio_value'].plot(ax=ax1)
ax1.set_ylabel('Portfolio\nValue\n({})'.format(base_currency))
# Plot the price increase or decrease over time.
ax2 = plt.subplot(612, sharex=ax1)
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
ax2.set_ylabel('{asset}\n({base})'.format(
asset=context.market.symbol, base=base_currency
))
transaction_df = extract_transactions(perf)
if not transaction_df.empty:
buy_df = transaction_df[transaction_df['amount'] > 0]
sell_df = transaction_df[transaction_df['amount'] < 0]
ax2.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index.floor('1 min'), 'price'],
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index.floor('1 min'), 'price'],
marker='v',
s=100,
c='red',
label=''
)
ax4 = plt.subplot(613, sharex=ax1)
perf.loc[:, 'cash'].plot(
ax=ax4, label='Base Currency ({})'.format(base_currency)
)
ax4.set_ylabel('Cash\n({})'.format(base_currency))
perf['algorithm'] = perf.loc[:, 'algorithm_period_return']
ax5 = plt.subplot(614, sharex=ax1)
perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
ax5.set_ylabel('Percent\nChange')
ax6 = plt.subplot(615, sharex=ax1)
perf.loc[:, 'rsi'].plot(ax=ax6, label='RSI')
ax6.set_ylabel('RSI')
ax6.axhline(context.RSI_OVERBOUGHT, color='darkgoldenrod')
ax6.axhline(context.RSI_OVERSOLD, color='darkgoldenrod')
if not transaction_df.empty:
ax6.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index.floor('1 min'), 'rsi'],
marker='^',
s=100,
c='green',
label=''
)
ax6.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index.floor('1 min'), 'rsi'],
marker='v',
s=100,
c='red',
label=''
)
plt.legend(loc=3)
start, end = ax6.get_ylim()
ax6.yaxis.set_ticks(np.arange(0, end, end / 5))
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
pass
if __name__ == '__main__':
# The execution mode: backtest or live
live = False
if live:
run_algorithm(
capital_base=0.025,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
live=True,
algo_namespace=NAMESPACE,
base_currency='btc',
live_graph=False,
simulate_orders=False,
stats_output=None,
)
else:
folder = os.path.join(
tempfile.gettempdir(), 'catalyst', NAMESPACE
)
ensure_directory(folder)
timestr = time.strftime('%Y%m%d-%H%M%S')
out = os.path.join(folder, '{}.p'.format(timestr))
# catalyst run -f catalyst/examples/mean_reversion_simple.py \
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
# --data-frequency minute --capital-base 10000
run_algorithm(
capital_base=0.1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
base_currency='eth',
start=pd.to_datetime('2017-10-01', utc=True),
end=pd.to_datetime('2017-11-10', utc=True),
output=out
)
log.info('saved perf stats: {}'.format(out))
+149
View File
@@ -0,0 +1,149 @@
'''Use this code to execute a portfolio optimization model. This code
will select the portfolio with the maximum Sharpe Ratio. The parameters
are set to use 180 days of historical data and rebalance every 30 days.
This is the code used in the following article:
https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556
You can run this code using the Python interpreter:
$ python portfolio_optimization.py
'''
from __future__ import division
import os
import pytz
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
from catalyst.api import record, symbols, order_target_percent
from catalyst.utils.run_algo import run_algorithm
np.set_printoptions(threshold='nan', suppress=True)
def initialize(context):
# Portfolio assets list
context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt',
'xmr_usdt')
context.nassets = len(context.assets)
# Set the time window that will be used to compute expected return
# and asset correlations
context.window = 180
# Set the number of days between each portfolio rebalancing
context.rebalance_period = 30
context.i = 0
def handle_data(context, data):
# Only rebalance at the beggining of the algorithm execution and
# every multiple of the rebalance period
if context.i == 0 or context.i % context.rebalance_period == 0:
n = context.window
prices = data.history(context.assets, fields='price',
bar_count=n + 1, frequency='1d')
pr = np.asmatrix(prices)
t_prices = prices.iloc[1:n + 1]
t_val = t_prices.values
tminus_prices = prices.iloc[0:n]
tminus_val = tminus_prices.values
# Compute daily returns (r)
r = np.asmatrix(t_val / tminus_val - 1)
# Compute the expected returns of each asset with the average
# daily return for the selected time window
m = np.asmatrix(np.mean(r, axis=0))
# ###
stds = np.std(r, axis=0)
# Compute excess returns matrix (xr)
xr = r - m
# Matrix algebra to get variance-covariance matrix
cov_m = np.dot(np.transpose(xr), xr) / n
# Compute asset correlation matrix (informative only)
corr_m = cov_m / np.dot(np.transpose(stds), stds)
# Define portfolio optimization parameters
n_portfolios = 50000
results_array = np.zeros((3 + context.nassets, n_portfolios))
for p in xrange(n_portfolios):
weights = np.random.random(context.nassets)
weights /= np.sum(weights)
w = np.asmatrix(weights)
p_r = np.sum(np.dot(w, np.transpose(m))) * 365
p_std = np.sqrt(np.dot(np.dot(w, cov_m),
np.transpose(w))) * np.sqrt(365)
# store results in results array
results_array[0, p] = p_r
results_array[1, p] = p_std
# store Sharpe Ratio (return / volatility) - risk free rate element
# excluded for simplicity
results_array[2, p] = results_array[0, p] / results_array[1, p]
i = 0
for iw in weights:
results_array[3 + i, p] = weights[i]
i += 1
# convert results array to Pandas DataFrame
results_frame = pd.DataFrame(np.transpose(results_array),
columns=['r', 'stdev', 'sharpe']
+ context.assets)
# locate position of portfolio with highest Sharpe Ratio
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
# locate positon of portfolio with minimum standard deviation
# min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
# order optimal weights for each asset
for asset in context.assets:
if data.can_trade(asset):
order_target_percent(asset, max_sharpe_port[asset])
# create scatter plot coloured by Sharpe Ratio
plt.scatter(results_frame.stdev,
results_frame.r,
c=results_frame.sharpe,
cmap='RdYlGn')
plt.xlabel('Volatility')
plt.ylabel('Returns')
plt.colorbar()
# plot red star to highlight position of portfolio
# with highest Sharpe Ratio
plt.scatter(max_sharpe_port[1],
max_sharpe_port[0],
marker='o',
color='b',
s=200)
# plot green star to highlight position of minimum variance portfolio
plt.show()
print(max_sharpe_port)
record(pr=pr,
r=r,
m=m,
stds=stds,
max_sharpe_port=max_sharpe_port,
corr_m=corr_m)
context.i += 1
def analyze(context=None, results=None):
# Form DataFrame with selected data
data = results[['pr', 'r', 'm', 'stds', 'max_sharpe_port', 'corr_m',
'portfolio_value']]
# Save results in CSV file
filename = os.path.splitext(os.path.basename(__file__))[0]
data.to_csv(filename + '.csv')
if __name__ == '__main__':
# Bitcoin data is available from 2015-3-2. Dates vary for other tokens.
start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc)
end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc)
results = run_algorithm(initialize=initialize,
handle_data=handle_data,
analyze=analyze,
start=start,
end=end,
exchange_name='poloniex',
capital_base=100000, )
+265
View File
@@ -0,0 +1,265 @@
from datetime import timedelta
import pandas as pd
import numpy as np
import talib
from logbook import Logger
from catalyst.api import (
order,
symbol,
record,
get_open_orders,
)
from catalyst.utils.run_algo import run_algorithm
algo_namespace = 'rsi'
log = Logger(algo_namespace)
def initialize(context):
log.info('initializing algo')
context.asset = symbol('eth_btc')
context.base_price = None
context.MAX_HOLDINGS = 0.2
context.RSI_OVERSOLD = 30
context.RSI_OVERSOLD_BBANDS = 45
context.RSI_OVERBOUGHT_BBANDS = 55
context.SLIPPAGE_ALLOWED = 0.03
context.TARGET = 0.15
context.STOP_LOSS = 0.1
context.STOP = 0.03
context.position = None
context.last_bar = None
context.errors = []
pass
def _handle_buy_sell_decision(context, data, signal, price):
orders = get_open_orders(context.asset)
if len(orders) > 0:
log.info('skipping bar until all open orders execute')
return
positions = context.portfolio.positions
if context.position is None and context.asset in positions:
position = positions[context.asset]
context.position = dict(
cost_basis=position['cost_basis'],
amount=position['amount'],
stop=None
)
# action = None
if context.position is not None:
cost_basis = context.position['cost_basis']
amount = context.position['amount']
log.info(
'found {amount} positions with cost basis {cost_basis}'.format(
amount=amount,
cost_basis=cost_basis
)
)
stop = context.position['stop']
target = cost_basis * (1 + context.TARGET)
if price >= target:
context.position['cost_basis'] = price
context.position['stop'] = context.STOP
stop_target = context.STOP_LOSS if stop is None else context.STOP
if price < cost_basis * (1 - stop_target):
log.info('executing stop loss')
order(
asset=context.asset,
amount=-amount,
limit_price=price * (1 - context.SLIPPAGE_ALLOWED),
)
# action = 0
context.position = None
else:
if signal == 'long':
log.info('opening position')
buy_amount = context.MAX_HOLDINGS / price
order(
asset=context.asset,
amount=buy_amount,
limit_price=price * (1 + context.SLIPPAGE_ALLOWED),
)
context.position = dict(
cost_basis=price,
amount=buy_amount,
stop=None
)
# action = 0
def _handle_data_rsi_only(context, data):
price = data.current(context.asset, 'close')
log.info('got price {price}'.format(price=price))
if price is np.nan:
log.warn('no pricing data')
return
if context.base_price is None:
context.base_price = price
try:
prices = data.history(
context.asset,
fields='price',
bar_count=20,
frequency='30T'
)
except Exception as e:
log.warn('historical data not available: '.format(e))
return
rsi = talib.RSI(prices.values, timeperiod=16)[-1]
log.info('got rsi {}'.format(rsi))
signal = None
if rsi < context.RSI_OVERSOLD:
signal = 'long'
# Making sure that the price is still current
price = data.current(context.asset, 'close')
cash = context.portfolio.cash
log.info(
'base currency available: {cash}, cap: {cap}'.format(
cash=cash,
cap=context.MAX_HOLDINGS
)
)
volume = data.current(context.asset, 'volume')
price_change = (price - context.base_price) / context.base_price
record(
price=price,
price_change=price_change,
rsi=rsi,
volume=volume,
cash=cash,
starting_cash=context.portfolio.starting_cash,
leverage=context.account.leverage,
)
_handle_buy_sell_decision(context, data, signal, price)
def handle_data(context, data):
dt = data.current_dt
if context.last_bar is None or (
context.last_bar + timedelta(minutes=15)) <= dt:
context.last_bar = dt
else:
return
log.info('BAR {}'.format(dt))
try:
_handle_data_rsi_only(context, data)
except Exception as e:
log.warn('aborting the bar on error {}'.format(e))
context.errors.append(e)
if len(context.errors) > 0:
log.info('the errors:\n{}'.format(context.errors))
def analyze(context=None, results=None):
import matplotlib.pyplot as plt
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio and asset data.
ax1 = plt.subplot(611)
results.loc[:, 'portfolio_value'].plot(ax=ax1)
ax1.set_ylabel('Portfolio Value ({})'.format(base_currency))
ax2 = plt.subplot(612, sharex=ax1)
results.loc[:, 'price'].plot(ax=ax2)
ax2.set_ylabel('{asset} ({base})'.format(
asset=context.asset.symbol, base=base_currency
))
trans = results.loc[[t != [] for t in results.transactions], :]
buys = trans.loc[[t[0]['amount'] > 0 for t in trans.transactions], :]
sells = trans.loc[[t[0]['amount'] < 0 for t in trans.transactions], :]
# buys = results.loc[results['action'] == 1, :]
# sells = results.loc[results['action'] == 0, :]
ax2.plot(
buys.index,
results.loc[buys.index, 'price'],
'^',
markersize=10,
color='g',
)
ax2.plot(
sells.index,
results.loc[sells.index, 'price'],
'v',
markersize=10,
color='r',
)
ax3 = plt.subplot(613, sharex=ax1)
results.loc[:, ['alpha', 'beta']].plot(ax=ax3)
ax3.set_ylabel('Alpha / Beta ')
ax4 = plt.subplot(614, sharex=ax1)
results.loc[:, ['starting_cash', 'cash']].plot(ax=ax4)
ax4.set_ylabel('Base Currency ({})'.format(base_currency))
results['algorithm'] = results.loc[:, 'algorithm_period_return']
ax5 = plt.subplot(615, sharex=ax1)
results.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
ax5.set_ylabel('Percent Change')
ax6 = plt.subplot(616, sharex=ax1)
results.loc[:, 'rsi'].plot(ax=ax6)
ax6.set_ylabel('RSI')
ax6.plot(
buys.index,
results.loc[buys.index, 'rsi'],
'^',
markersize=10,
color='g',
)
ax6.plot(
sells.index,
results.loc[sells.index, 'rsi'],
'v',
markersize=10,
color='r',
)
plt.legend(loc=3)
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
pass
if __name__ == '__main__':
# Backtest
run_algorithm(
capital_base=0.5,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace=algo_namespace,
base_currency='btc',
start=pd.to_datetime('2017-9-1', utc=True),
end=pd.to_datetime('2017-10-1', utc=True),
)
File diff suppressed because one or more lines are too long
+144
View File
@@ -0,0 +1,144 @@
import pandas as pd
import talib
from logbook import Logger, INFO
from catalyst import run_algorithm
from catalyst.api import symbol, record
from catalyst.exchange.utils.stats_utils import get_pretty_stats, \
extract_transactions
log = Logger('simple_loop', level=INFO)
def initialize(context):
log.info('initializing')
context.asset = symbol('eth_btc')
context.base_price = None
def handle_data(context, data):
log.info('handling bar: {}'.format(data.current_dt))
price = data.current(context.asset, 'close')
log.info('got price {price}'.format(price=price))
prices = data.history(
context.asset,
fields='price',
bar_count=20,
frequency='30T'
)
last_traded = prices.index[-1]
log.info('last candle date: {}'.format(last_traded))
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
log.info('got rsi: {}'.format(rsi))
# If base_price is not set, we use the current value. This is the
# price at the first bar which we reference to calculate price_change.
if context.base_price is None:
context.base_price = price
price_change = (price - context.base_price) / context.base_price
cash = context.portfolio.cash
# Now that we've collected all current data for this frame, we use
# the record() method to save it. This data will be available as
# a parameter of the analyze() function for further analysis.
record(
price=price,
price_change=price_change,
cash=cash
)
def analyze(context, perf):
import matplotlib.pyplot as plt
log.info('the stats: {}'.format(get_pretty_stats(perf)))
# The base currency of the algo exchange
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
perf.loc[:, 'portfolio_value'].plot(ax=ax1)
ax1.set_ylabel('Portfolio Value ({})'.format(base_currency))
# Plot the price increase or decrease over time.
ax2 = plt.subplot(612, sharex=ax1)
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
ax2.set_ylabel('{asset} ({base})'.format(
asset=context.asset.symbol, base=base_currency
))
transaction_df = extract_transactions(perf)
if not transaction_df.empty:
buy_df = transaction_df[transaction_df['amount'] > 0]
sell_df = transaction_df[transaction_df['amount'] < 0]
ax2.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index, 'price'],
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index, 'price'],
marker='v',
s=100,
c='red',
label=''
)
ax4 = plt.subplot(613, sharex=ax1)
perf.loc[:, 'cash'].plot(
ax=ax4, label='Base Currency ({})'.format(base_currency)
)
ax4.set_ylabel('Cash ({})'.format(base_currency))
perf['algorithm'] = perf.loc[:, 'algorithm_period_return']
ax5 = plt.subplot(614, sharex=ax1)
perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
ax5.set_ylabel('Percent Change')
plt.legend(loc=3)
# Show the plot.
plt.gcf().set_size_inches(18, 8)
plt.show()
pass
if __name__ == '__main__':
mode = 'backtest'
if mode == 'backtest':
run_algorithm(
capital_base=1,
initialize=initialize,
handle_data=handle_data,
analyze=None,
exchange_name='poloniex',
algo_namespace='simple_loop',
base_currency='eth',
data_frequency='minute',
start=pd.to_datetime('2017-9-1', utc=True),
end=pd.to_datetime('2017-12-1', utc=True),
)
else:
run_algorithm(
capital_base=1,
initialize=initialize,
handle_data=handle_data,
analyze=None,
exchange_name='binance',
live=True,
algo_namespace='simple_loop',
base_currency='eth',
live_graph=False,
simulate_orders=True
)
+171
View File
@@ -0,0 +1,171 @@
"""
Requires Catalyst version 0.3.0 or above
Tested on Catalyst version 0.3.3
This example aims to provide an easy way for users to learn how to
collect data from any given exchange and select a subset of the available
currency pairs for trading. You simply need to specify the exchange and
the market (base_currency) that you want to focus on. You will then see
how to create a universe of assets, and filter it based the market you
desire.
The example prints out the closing price of all the pairs for a given
market in a given exchange every 30 minutes. The example also contains
the OHLCV data with minute-resolution for the past seven days which
could be used to create indicators. Use this code as the backbone to
create your own trading strategy.
The lookback_date variable is used to ensure data for a coin existed on
the lookback period specified.
To run, execute the following two commands in a terminal (inside catalyst
environment). The first one retrieves all the pricing data needed for this
script to run (only needs to be run once), and the second one executes this
script with the parameters specified in the run_algorithm() call at the end
of the file:
catalyst ingest-exchange -x bitfinex -f minute
python simple_universe.py
"""
from datetime import timedelta
import numpy as np
import pandas as pd
from catalyst import run_algorithm
from catalyst.api import (symbols, )
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols
def initialize(context):
context.i = -1 # minute counter
context.exchange = list(context.exchanges.values())[0].name.lower()
context.base_currency = list(context.exchanges.values())[0].base_currency.lower()
def handle_data(context, data):
context.i += 1
lookback_days = 7 # 7 days
# current date & time in each iteration formatted into a string
now = data.current_dt
date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ')
lookback_date = now - timedelta(days=lookback_days)
# keep only the date as a string, discard the time
lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0]
one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute'
# update universe everyday at midnight
if not context.i % one_day_in_minutes:
context.universe = universe(context, lookback_date, date)
# get data every 30 minutes
minutes = 30
# get lookback_days of history data: that is 'lookback' number of bins
lookback = int(one_day_in_minutes / minutes * lookback_days)
if not context.i % minutes and context.universe:
# we iterate for every pair in the current universe
for coin in context.coins:
pair = str(coin.symbol)
# Get 30 minute interval OHLCV data. This is the standard data
# required for candlestick or indicators/signals. Return Pandas
# DataFrames. 30T means 30-minute re-sampling of one minute data.
# Adjust it to your desired time interval as needed.
opened = fill(data.history(coin,
'open',
bar_count=lookback,
frequency='30T')).values
high = fill(data.history(coin,
'high',
bar_count=lookback,
frequency='30T')).values
low = fill(data.history(coin,
'low',
bar_count=lookback,
frequency='30T')).values
close = fill(data.history(coin,
'price',
bar_count=lookback,
frequency='30T')).values
volume = fill(data.history(coin,
'volume',
bar_count=lookback,
frequency='30T')).values
# close[-1] is the last value in the set, which is the equivalent
# to current price (as in the most recent value)
# displays the minute price for each pair every 30 minutes
print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},'
'\tV:{v}'.format(
now=now,
pair=pair,
o=opened[-1],
h=high[-1],
l=low[-1],
c=close[-1],
v=volume[-1],
))
# -------------------------------------------------------------
# --------------- Insert Your Strategy Here -------------------
# -------------------------------------------------------------
def analyze(context=None, results=None):
pass
# Get the universe for a given exchange and a given base_currency market
# Example: Poloniex BTC Market
def universe(context, lookback_date, current_date):
# get all the pairs for the given exchange
json_symbols = get_exchange_symbols(context.exchange)
# convert into a DataFrame for easier processing
df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str)
df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],
axis=1)
df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],
axis=1)
# Filter all the pairs to get only the ones for a given base_currency
df = df[df['base_currency'] == context.base_currency]
# Filter all pairs to ensure that pair existed in the current date range
df = df[df.start_date < lookback_date]
df = df[df.end_daily >= current_date]
context.coins = symbols(*df.symbol) # convert all the pairs to symbols
return df.symbol.tolist()
# Replace all NA, NAN or infinite values with its nearest value
def fill(series):
if isinstance(series, pd.Series):
return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
elif isinstance(series, np.ndarray):
return pd.Series(series).replace(
[np.inf, -np.inf], np.nan
).ffill().bfill().values
else:
return series
if __name__ == '__main__':
start_date = pd.to_datetime('2017-11-10', utc=True)
end_date = pd.to_datetime('2017-11-13', utc=True)
performance = run_algorithm(start=start_date, end=end_date,
capital_base=100.0, # amount of base_currency
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
data_frequency='minute',
base_currency='btc',
live=False,
live_graph=False,
algo_namespace='simple_universe')
+366
View File
@@ -0,0 +1,366 @@
# Run Command
# catalyst run --start 2017-1-1 --end 2017-11-1 -o talib_simple.pickle \
# -f talib_simple.py -x poloniex
#
# Description
# Simple TALib Example showing how to use various indicators
# in you strategy. Based loosly on
# https://github.com/mellertson/talib-macd-example/blob/master/talib-macd-matplotlib-example.py
import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import talib as ta
from logbook import Logger
from matplotlib.dates import date2num
from matplotlib.finance import candlestick_ohlc
from catalyst import run_algorithm
from catalyst.api import (
order,
order_target_percent,
symbol,
)
from catalyst.exchange.utils.stats_utils import get_pretty_stats
algo_namespace = 'talib_sample'
log = Logger(algo_namespace)
def initialize(context):
log.info('Starting TALib Simple Example')
context.ASSET_NAME = 'BTC_USDT'
context.asset = symbol(context.ASSET_NAME)
context.ORDER_SIZE = 10
context.SLIPPAGE_ALLOWED = 0.05
context.swallow_errors = True
context.errors = []
# Bars to look at per iteration should be bigger than SMA_SLOW
context.BARS = 365
context.COUNT = 0
# Technical Analysis Settings
context.SMA_FAST = 50
context.SMA_SLOW = 100
context.RSI_PERIOD = 14
context.RSI_OVER_BOUGHT = 80
context.RSI_OVER_SOLD = 20
context.RSI_AVG_PERIOD = 15
context.MACD_FAST = 12
context.MACD_SLOW = 26
context.MACD_SIGNAL = 9
context.STOCH_K = 14
context.STOCH_D = 3
context.STOCH_OVER_BOUGHT = 80
context.STOCH_OVER_SOLD = 20
pass
def _handle_data(context, data):
# Get price, open, high, low, close
prices = data.history(
context.asset,
bar_count=context.BARS,
fields=['price', 'open', 'high', 'low', 'close'],
frequency='1d')
# Create a analysis data frame
analysis = pd.DataFrame(index=prices.index)
# SMA FAST
analysis['sma_f'] = ta.SMA(prices.close.as_matrix(), context.SMA_FAST)
# SMA SLOW
analysis['sma_s'] = ta.SMA(prices.close.as_matrix(), context.SMA_SLOW)
# Relative Strength Index
analysis['rsi'] = ta.RSI(prices.close.as_matrix(), context.RSI_PERIOD)
# RSI SMA
analysis['sma_r'] = ta.SMA(analysis.rsi.as_matrix(),
context.RSI_AVG_PERIOD)
# MACD, MACD Signal, MACD Histogram
analysis['macd'], analysis['macdSignal'], analysis['macdHist'] = ta.MACD(
prices.close.as_matrix(), fastperiod=context.MACD_FAST,
slowperiod=context.MACD_SLOW, signalperiod=context.MACD_SIGNAL)
# Stochastics %K %D
# %K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
# %D = 3-day SMA of %K
analysis['stoch_k'], analysis['stoch_d'] = ta.STOCH(
prices.high.as_matrix(), prices.low.as_matrix(),
prices.close.as_matrix(), slowk_period=context.STOCH_K,
slowd_period=context.STOCH_D)
# SMA FAST over SLOW Crossover
analysis['sma_test'] = np.where(analysis.sma_f > analysis.sma_s, 1, 0)
# MACD over Signal Crossover
analysis['macd_test'] = np.where((analysis.macd > analysis.macdSignal), 1,
0)
# Stochastics OVER BOUGHT & Decreasing
analysis['stoch_over_bought'] = np.where(
(analysis.stoch_k > context.STOCH_OVER_BOUGHT) & (
analysis.stoch_k > analysis.stoch_k.shift(1)), 1, 0)
# Stochastics OVER SOLD & Increasing
analysis['stoch_over_sold'] = np.where(
(analysis.stoch_k < context.STOCH_OVER_SOLD) & (
analysis.stoch_k > analysis.stoch_k.shift(1)), 1, 0)
# RSI OVER BOUGHT & Decreasing
analysis['rsi_over_bought'] = np.where(
(analysis.rsi > context.RSI_OVER_BOUGHT) & (
analysis.rsi < analysis.rsi.shift(1)), 1, 0)
# RSI OVER SOLD & Increasing
analysis['rsi_over_sold'] = np.where(
(analysis.rsi < context.RSI_OVER_SOLD) & (
analysis.rsi > analysis.rsi.shift(1)), 1, 0)
# Save the prices and analysis to send to analyze
context.prices = prices
context.analysis = analysis
context.price = data.current(context.asset, 'price')
makeOrders(context, analysis)
# Log the values of this bar
logAnalysis(analysis)
def handle_data(context, data):
log.info('handling bar {}'.format(data.current_dt))
try:
_handle_data(context, data)
except Exception as e:
log.warn('aborting the bar on error {}'.format(e))
context.errors.append(e)
log.info('completed bar {}, total execution errors {}'.format(
data.current_dt,
len(context.errors)
))
if len(context.errors) > 0:
log.info('the errors:\n{}'.format(context.errors))
def analyze(context, results):
# Save results in CSV file
filename = os.path.splitext(os.path.basename('talib_simple'))[0]
results.to_csv(filename + '.csv')
log.info('the daily stats:\n{}'.format(get_pretty_stats(results)))
chart(context, context.prices, context.analysis, results)
pass
def makeOrders(context, analysis):
if context.asset in context.portfolio.positions:
# Current position
position = context.portfolio.positions[context.asset]
if (position == 0):
log.info('Position Zero')
return
# Cost Basis
cost_basis = position.cost_basis
log.info(
'Holdings: {amount} @ {cost_basis}'.format(
amount=position.amount,
cost_basis=cost_basis
)
)
# Sell when holding and got sell singnal
if isSell(context, analysis):
profit = (context.price * position.amount) - (
cost_basis * position.amount)
order_target_percent(
asset=context.asset,
target=0,
limit_price=context.price * (1 - context.SLIPPAGE_ALLOWED),
)
log.info(
'Sold {amount} @ {price} Profit: {profit}'.format(
amount=position.amount,
price=context.price,
profit=profit
)
)
else:
log.info('no buy or sell opportunity found')
else:
# Buy when not holding and got buy signal
if isBuy(context, analysis):
order(
asset=context.asset,
amount=context.ORDER_SIZE,
limit_price=context.price * (1 + context.SLIPPAGE_ALLOWED)
)
log.info(
'Bought {amount} @ {price}'.format(
amount=context.ORDER_SIZE,
price=context.price
)
)
def isBuy(context, analysis):
# Bullish SMA Crossover
if (getLast(analysis, 'sma_test') == 1):
# Bullish MACD
if (getLast(analysis, 'macd_test') == 1):
return True
# # Bullish Stochastics
# if(getLast(analysis, 'stoch_over_sold') == 1):
# return True
# # Bullish RSI
# if(getLast(analysis, 'rsi_over_sold') == 1):
# return True
return False
def isSell(context, analysis):
# Bearish SMA Crossover
if (getLast(analysis, 'sma_test') == 0):
# Bearish MACD
if (getLast(analysis, 'macd_test') == 0):
return True
# # Bearish Stochastics
# if(getLast(analysis, 'stoch_over_bought') == 0):
# return True
# # Bearish RSI
# if(getLast(analysis, 'rsi_over_bought') == 0):
# return True
return False
def chart(context, prices, analysis, results):
results.portfolio_value.plot()
# Data for matplotlib finance plot
dates = date2num(prices.index.to_pydatetime())
# Create the Open High Low Close Tuple
prices_ohlc = [tuple([dates[i],
prices.open[i],
prices.high[i],
prices.low[i],
prices.close[i]]) for i in range(len(dates))]
fig = plt.figure(figsize=(14, 18))
# Draw the candle sticks
ax1 = fig.add_subplot(411)
ax1.set_ylabel(context.ASSET_NAME, size=20)
candlestick_ohlc(ax1, prices_ohlc, width=0.4, colorup='g', colordown='r')
# Draw Moving Averages
analysis.sma_f.plot(ax=ax1, c='r')
analysis.sma_s.plot(ax=ax1, c='g')
# RSI
ax2 = fig.add_subplot(412)
ax2.set_ylabel('RSI', size=12)
analysis.rsi.plot(ax=ax2, c='g',
label='Period: ' + str(context.RSI_PERIOD))
analysis.sma_r.plot(ax=ax2, c='r',
label='MA: ' + str(context.RSI_AVG_PERIOD))
ax2.axhline(y=30, c='b')
ax2.axhline(y=50, c='black')
ax2.axhline(y=70, c='b')
ax2.set_ylim([0, 100])
handles, labels = ax2.get_legend_handles_labels()
ax2.legend(handles, labels)
# Draw MACD computed with Talib
ax3 = fig.add_subplot(413)
ax3.set_ylabel('MACD: ' + str(context.MACD_FAST) + ', ' + str(
context.MACD_SLOW) + ', ' + str(context.MACD_SIGNAL), size=12)
analysis.macd.plot(ax=ax3, color='b', label='Macd')
analysis.macdSignal.plot(ax=ax3, color='g', label='Signal')
analysis.macdHist.plot(ax=ax3, color='r', label='Hist')
ax3.axhline(0, lw=2, color='0')
handles, labels = ax3.get_legend_handles_labels()
ax3.legend(handles, labels)
# Stochastic plot
ax4 = fig.add_subplot(414)
ax4.set_ylabel('Stoch (k,d)', size=12)
analysis.stoch_k.plot(ax=ax4, label='stoch_k:' + str(context.STOCH_K),
color='r')
analysis.stoch_d.plot(ax=ax4, label='stoch_d:' + str(context.STOCH_D),
color='g')
handles, labels = ax4.get_legend_handles_labels()
ax4.legend(handles, labels)
ax4.axhline(y=20, c='b')
ax4.axhline(y=50, c='black')
ax4.axhline(y=80, c='b')
plt.show()
def logAnalysis(analysis):
# Log only the last value in the array
log.info('- sma_f: {:.2f}'.format(getLast(analysis, 'sma_f')))
log.info('- sma_s: {:.2f}'.format(getLast(analysis, 'sma_s')))
log.info('- rsi: {:.2f}'.format(getLast(analysis, 'rsi')))
log.info('- sma_r: {:.2f}'.format(getLast(analysis, 'sma_r')))
log.info('- macd: {:.2f}'.format(getLast(analysis, 'macd')))
log.info(
'- macdSignal: {:.2f}'.format(getLast(analysis, 'macdSignal')))
log.info('- macdHist: {:.2f}'.format(getLast(analysis, 'macdHist')))
log.info('- stoch_k: {:.2f}'.format(getLast(analysis, 'stoch_k')))
log.info('- stoch_d: {:.2f}'.format(getLast(analysis, 'stoch_d')))
log.info('- sma_test: {}'.format(getLast(analysis, 'sma_test')))
log.info('- macd_test: {}'.format(getLast(analysis, 'macd_test')))
log.info('- stoch_over_bought: {}'.format(
getLast(analysis, 'stoch_over_bought')))
log.info(
'- stoch_over_sold: {}'.format(getLast(analysis, 'stoch_over_sold')))
log.info('- rsi_over_bought: {}'.format(
getLast(analysis, 'rsi_over_bought')))
log.info(
'- rsi_over_sold: {}'.format(getLast(analysis, 'rsi_over_sold')))
def getLast(arr, name):
return arr[name][arr[name].index[-1]]
if __name__ == '__main__':
run_algorithm(
capital_base=10000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
base_currency='usdt',
start=pd.to_datetime('2016-11-1', utc=True),
end=pd.to_datetime('2017-11-10', utc=True),
)
-437
View File
@@ -1,437 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import signal
import sys
import pickle
from datetime import timedelta
from time import sleep
from os import listdir
from os.path import isfile, join
import logbook
import pandas as pd
import catalyst.protocol as zp
from catalyst.algorithm import TradingAlgorithm
from catalyst.data.minute_bars import BcolzMinuteBarWriter, \
BcolzMinuteBarReader
from catalyst.errors import OrderInBeforeTradingStart
from catalyst.exchange.exchange_clock import ExchangeClock
from catalyst.exchange.exchange_errors import (
ExchangeRequestError,
ExchangePortfolioDataError,
ExchangeTransactionError
)
from catalyst.exchange.exchange_utils import get_exchange_minute_writer_root, \
save_algo_object, get_algo_object, get_algo_folder
from catalyst.finance.performance.period import calc_period_stats
from catalyst.gens.tradesimulation import AlgorithmSimulator
from catalyst.utils.api_support import (
api_method,
disallowed_in_before_trading_start)
from catalyst.utils.input_validation import error_keywords
log = logbook.Logger("ExchangeTradingAlgorithm")
class ExchangeAlgorithmExecutor(AlgorithmSimulator):
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
class ExchangeTradingAlgorithm(TradingAlgorithm):
def __init__(self, *args, **kwargs):
self.exchange = kwargs.pop('exchange', None)
self.algo_namespace = kwargs.pop('algo_namespace', None)
self.orders = {}
self.is_running = True
self.retry_check_open_orders = 5
self.retry_update_portfolio = 5
self.retry_get_open_orders = 5
self.retry_order = 2
self.retry_delay = 5
super(self.__class__, self).__init__(*args, **kwargs)
self._create_minute_writer()
signal.signal(signal.SIGINT, self.signal_handler)
log.info('exchange trading algorithm successfully initialized')
def _create_minute_writer(self):
root = get_exchange_minute_writer_root(self.exchange.name)
filename = os.path.join(root, 'metadata.json')
if os.path.isfile(filename):
writer = BcolzMinuteBarWriter.open(
root, self.sim_params.end_session)
else:
writer = BcolzMinuteBarWriter(
rootdir=root,
calendar=self.trading_calendar,
minutes_per_day=1440,
start_session=self.sim_params.start_session,
end_session=self.sim_params.end_session,
write_metadata=True
)
self.exchange.minute_writer = writer
self.exchange.minute_reader = BcolzMinuteBarReader(root)
def signal_handler(self, signal, frame):
self.is_running = False
log.info('You pressed Ctrl+C!')
stats = None
try:
algo_folder = get_algo_folder(self.algo_namespace)
folder = join(algo_folder, 'daily_perf')
files = [f for f in listdir(folder) if isfile(join(folder, f))]
daily_perf_list = []
for item in files:
filename = join(folder, item)
with open(filename, 'rb') as handle:
daily_perf_list.append(pickle.load(handle))
stats = pd.DataFrame(daily_perf_list)
stats.set_index('period_close', drop=True, inplace=True)
except Exception as e:
log.warn('Unable to compute daily stats: {}'.format(e))
self.analyze(stats)
sys.exit(0)
def _create_clock(self):
# The calendar's execution times are the minutes over which we actually
# want to run the clock. Typically the execution times simply adhere to
# the market open and close times. In the case of the futures calendar,
# for example, we only want to simulate over a subset of the full 24
# hour calendar, so the execution times dictate a market open time of
# 6:31am US/Eastern and a close of 5:00pm US/Eastern.
# In our case, we are trading around the clock, so the market close
# corresponds to the last minute of the day.
# This method is taken from TradingAlgorithm.
# The clock has been replaced to use RealtimeClock
# TODO: should we apply a time skew? not sure to understand the utility.
return ExchangeClock(
self.sim_params.sessions,
time_skew=self.exchange.time_skew
)
def _create_generator(self, sim_params):
if self.perf_tracker is None:
self.perf_tracker = get_algo_object(
algo_name=self.algo_namespace,
key='perf_tracker'
)
# Call the simulation trading algorithm for side-effects:
# it creates the perf tracker
TradingAlgorithm._create_generator(self, sim_params)
self.trading_client = ExchangeAlgorithmExecutor(
self,
sim_params,
self.data_portal,
self._create_clock(),
self._create_benchmark_source(),
self.restrictions,
universe_func=self._calculate_universe
)
return self.trading_client.transform()
def updated_portfolio(self):
"""
We skip the entire performance tracker business and update the
portfolio directly.
:return:
"""
return self.exchange.portfolio
def updated_account(self):
return self.exchange.account
def _update_portfolio(self, attempt_index=0):
try:
self.exchange.update_portfolio()
# Applying the updated last_sales_price to the positions
# in the performance tracker. This seems a bit redundant
# but it will make sense when we have multiple exchange portfolios
# feeding into the same performance tracker.
tracker = self.perf_tracker.todays_performance.position_tracker
for asset in self.exchange.portfolio.positions:
position = self.exchange.portfolio.positions[asset]
tracker.update_position(
asset=asset,
last_sale_date=position.last_sale_date,
last_sale_price=position.last_sale_price
)
except ExchangeRequestError as e:
log.warn(
'update portfolio attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_update_portfolio:
sleep(self.retry_delay)
self._update_portfolio(attempt_index + 1)
else:
raise ExchangePortfolioDataError(
data_type='update-portfolio',
attempts=attempt_index,
error=e
)
def _check_open_orders(self, attempt_index=0):
try:
return self.exchange.check_open_orders()
except ExchangeRequestError as e:
log.warn(
'check open orders attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_check_open_orders:
sleep(self.retry_delay)
return self._check_open_orders(attempt_index + 1)
else:
raise ExchangePortfolioDataError(
data_type='order-status',
attempts=attempt_index,
error=e
)
def prepare_period_stats(self, start_dt, end_dt):
"""
Creates a dictionary representing the state of the tracker.
I rewrote this in an attempt to better control the stats.
I don't want things to happen magically through complex logic
pertaining to backtesting.
"""
tracker = self.perf_tracker
period = tracker.todays_performance
pos_stats = period.position_tracker.stats()
period_stats = calc_period_stats(pos_stats, period.ending_cash)
stats = dict(
period_start=tracker.period_start,
period_end=tracker.period_end,
capital_base=tracker.capital_base,
progress=tracker.progress,
ending_value=period.ending_value,
ending_exposure=period.ending_exposure,
capital_used=period.cash_flow,
starting_value=period.starting_value,
starting_exposure=period.starting_exposure,
starting_cash=period.starting_cash,
ending_cash=period.ending_cash,
portfolio_value=period.ending_cash + period.ending_value,
pnl=period.pnl,
returns=period.returns,
period_open=period.period_open,
period_close=period.period_close,
gross_leverage=period_stats.gross_leverage,
net_leverage=period_stats.net_leverage,
short_exposure=pos_stats.short_exposure,
long_exposure=pos_stats.long_exposure,
short_value=pos_stats.short_value,
long_value=pos_stats.long_value,
longs_count=pos_stats.longs_count,
shorts_count=pos_stats.shorts_count,
)
# Merging cumulative risk
stats.update(tracker.cumulative_risk_metrics.to_dict())
# Merging latest recorded variables
stats.update(self.recorded_vars)
stats['positions'] = period.position_tracker.get_positions_list()
# we want the key to be absent, not just empty
# Only include transactions for given dt
stats['transactions'] = dict()
for date in period.processed_transactions:
if start_dt <= date < end_dt:
stats['transactions'][date] = \
period.processed_transactions[date]
stats['orders'] = dict()
for date in period.orders_by_modified:
if start_dt <= date < end_dt:
stats['orders'][date] = \
period.orders_by_modified[date]
return stats
def handle_data(self, data):
if not self.is_running:
return
self._update_portfolio()
transactions = self._check_open_orders()
for transaction in transactions:
self.perf_tracker.process_transaction(transaction)
if self._handle_data:
self._handle_data(self, data)
# Unlike trading controls which remain constant unless placing an
# order, account controls can change each bar. Thus, must check
# every bar no matter if the algorithm places an order or not.
self.validate_account_controls()
try:
# Since the clock runs 24/7, I trying to disable the daily
# Performance tracker and keep only minute and cumulative
self.perf_tracker.update_performance()
# TODO: save for future use?
minute_stats = self.prepare_period_stats(
data.current_dt, data.current_dt + timedelta(minutes=1))
log.debug('the minute performance:\n{}'.format(minute_stats))
today = pd.to_datetime('today', utc=True)
daily_stats = self.prepare_period_stats(
start_dt=today,
end_dt=pd.Timestamp.utcnow()
)
save_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
obj=daily_stats,
rel_path='daily_perf'
)
except Exception as e:
log.warn('unable to calculate performance: {}'.format(e))
try:
save_algo_object(
algo_name=self.algo_namespace,
key='perf_tracker',
obj=self.perf_tracker
)
except Exception as e:
log.warn('unable to save minute perfs to disk: {}'.format(e))
try:
save_algo_object(
algo_name=self.algo_namespace,
key='portfolio_{}'.format(self.exchange.name),
obj=self.exchange.portfolio
)
except Exception as e:
log.warn('unable to save portfolio to disk: {}'.format(e))
def _order(self,
asset,
amount,
limit_price=None,
stop_price=None,
style=None,
attempt_index=0):
try:
return self.exchange.order(asset, amount, limit_price,
stop_price,
style)
except ExchangeRequestError as e:
log.warn(
'order attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_order:
sleep(self.retry_delay)
return self._order(
asset, amount, limit_price, stop_price, style,
attempt_index + 1)
else:
raise ExchangeTransactionError(
transaction_type='order',
attempts=attempt_index,
error=e
)
@api_method
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
def order(self,
asset,
amount,
limit_price=None,
stop_price=None,
style=None):
amount, style = self._calculate_order(asset, amount,
limit_price, stop_price,
style)
order_id = self._order(asset, amount, limit_price, stop_price, style)
order = self.portfolio.open_orders[order_id]
self.perf_tracker.process_order(order)
return order
def round_order(self, amount):
"""
We need fractions with cryptocurrencies
:param amount:
:return:
"""
return amount
@api_method
def batch_market_order(self, share_counts):
raise NotImplementedError()
def _get_open_orders(self, asset=None, attempt_index=0):
try:
return self.exchange.get_open_orders(asset)
except ExchangeRequestError as e:
log.warn(
'open orders attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_get_open_orders:
sleep(self.retry_delay)
return self._get_open_orders(asset, attempt_index + 1)
else:
raise ExchangePortfolioDataError(
data_type='open-orders',
attempts=attempt_index,
error=e
)
@error_keywords(sid='Keyword argument `sid` is no longer supported for '
'get_open_orders. Use `asset` instead.')
@api_method
def get_open_orders(self, asset=None):
return self._get_open_orders(asset)
@api_method
def get_order(self, order_id):
return self.exchange.get_order(order_id)
@api_method
def cancel_order(self, order_param):
order_id = order_param
if isinstance(order_param, zp.Order):
order_id = order_param.id
self.exchange.cancel_order(order_id)
@@ -1,91 +0,0 @@
from logbook import Logger
log = Logger('AssetFinderExchange')
class AssetFinderExchange(object):
def __init__(self, exchange):
self.exchange = exchange
self._asset_cache = {}
@property
def sids(self):
"""
This seems to be used to pre-fetch assets.
I don't think that we need this for live-trading.
Leaving the list empty.
"""
return list()
def retrieve_all(self, sids, default_none=False):
"""
Retrieve all assets in `sids`.
Parameters
----------
sids : iterable of int
Assets to retrieve.
default_none : bool
If True, return None for failed lookups.
If False, raise `SidsNotFound`.
Returns
-------
assets : list[Asset or None]
A list of the same length as `sids` containing Assets (or Nones)
corresponding to the requested sids.
Raises
------
SidsNotFound
When a requested sid is not found and default_none=False.
"""
for sid in sids:
if sid in self._asset_cache:
log.info('got asset from cache: {}'.format(sid))
else:
log.info('fetching asset: {}'.format(sid))
return list()
def lookup_symbol(self, symbol, as_of_date, fuzzy=False):
"""Lookup an asset by symbol.
Parameters
----------
symbol : str
The ticker symbol to resolve.
as_of_date : datetime or None
Look up the last owner of this symbol as of this datetime.
If ``as_of_date`` is None, then this can only resolve the equity
if exactly one equity has ever owned the ticker.
fuzzy : bool, optional
Should fuzzy symbol matching be used? Fuzzy symbol matching
attempts to resolve differences in representations for
shareclasses. For example, some people may represent the ``A``
shareclass of ``BRK`` as ``BRK.A``, where others could write
``BRK_A``.
Returns
-------
equity : Asset
The equity that held ``symbol`` on the given ``as_of_date``, or the
only equity to hold ``symbol`` if ``as_of_date`` is None.
Raises
------
SymbolNotFound
Raised when no equity has ever held the given symbol.
MultipleSymbolsFound
Raised when no ``as_of_date`` is given and more than one equity
has held ``symbol``. This is also raised when ``fuzzy=True`` and
there are multiple candidates for the given ``symbol`` on the
``as_of_date``.
"""
log.info('looking up symbol: {}'.format(symbol))
if symbol in self._asset_cache:
return self._asset_cache[symbol]
else:
asset = self.exchange.get_asset(symbol)
self._asset_cache[symbol] = asset
return asset
-647
View File
@@ -1,647 +0,0 @@
import base64
import numpy as np
import hashlib
import hmac
import json
import re
import time
import pandas as pd
import pytz
import requests
import six
from catalyst.assets._assets import Asset
from logbook import Logger
# from websocket import create_connection
from catalyst.exchange.exchange import Exchange
from catalyst.exchange.exchange_errors import (
ExchangeRequestError,
InvalidHistoryFrequencyError
)
from catalyst.finance.execution import (MarketOrder,
LimitOrder,
StopOrder,
StopLimitOrder)
from catalyst.finance.order import Order, ORDER_STATUS
from catalyst.protocol import Account
# Trying to account for REST api instability
# https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request
requests.adapters.DEFAULT_RETRIES = 20
BITFINEX_URL = 'https://api.bitfinex.com'
log = Logger('Bitfinex')
warning_logger = Logger('AlgoWarning')
class Bitfinex(Exchange):
def __init__(self, key, secret, base_currency, portfolio=None):
self.url = BITFINEX_URL
self.key = key
self.secret = secret
self.id = 'b'
self.name = 'bitfinex'
self.assets = {}
self.load_assets()
self.base_currency = base_currency
self._portfolio = portfolio
self.minute_writer = None
self.minute_reader = None
def _request(self, operation, data, version='v1'):
payload_object = {
'request': '/{}/{}'.format(version, operation),
'nonce': '{0:f}'.format(time.time() * 1000000),
# convert to string
'options': {}
}
if data is None:
payload_dict = payload_object
else:
payload_dict = payload_object.copy()
payload_dict.update(data)
payload_json = json.dumps(payload_dict)
if six.PY3:
payload = base64.b64encode(bytes(payload_json, 'utf-8'))
else:
payload = base64.b64encode(payload_json)
m = hmac.new(self.secret, payload, hashlib.sha384)
m = m.hexdigest()
# headers
headers = {
'X-BFX-APIKEY': self.key,
'X-BFX-PAYLOAD': payload,
'X-BFX-SIGNATURE': m
}
if data is None:
request = requests.get(
'{url}/{version}/{operation}'.format(
url=self.url,
version=version,
operation=operation
), data={},
headers=headers)
else:
request = requests.post(
'{url}/{version}/{operation}'.format(
url=self.url,
version=version,
operation=operation
),
headers=headers)
return request
def _get_v2_symbol(self, asset):
pair = asset.symbol.split('_')
symbol = 't' + pair[0].upper() + pair[1].upper()
return symbol
def _get_v2_symbols(self, assets):
"""
Workaround to support Bitfinex v2
TODO: Might require a separate asset dictionary
:param assets:
:return:
"""
v2_symbols = []
for asset in assets:
v2_symbols.append(self._get_v2_symbol(asset))
return v2_symbols
def _create_order(self, order_status):
"""
Create a Catalyst order object from a Bitfinex order dictionary
:param order_status:
:return: Order
"""
if order_status['is_cancelled']:
status = ORDER_STATUS.CANCELLED
elif not order_status['is_live']:
log.info('found executed order {}'.format(order_status))
status = ORDER_STATUS.FILLED
else:
status = ORDER_STATUS.OPEN
amount = float(order_status['original_amount'])
filled = float(order_status['executed_amount'])
is_buy = (amount > 0)
price = float(order_status['price'])
order_type = order_status['type']
stop_price = None
limit_price = None
# TODO: is this comprehensive enough?
if order_type.endswith('limit'):
limit_price = price
elif order_type.endswith('stop'):
stop_price = price
executed_price = float(order_status['avg_execution_price'])
# TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it.
commission = None
# TODO: zipline likes rounded dates to match statistics, is this ok?
date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
date = pytz.utc.localize(date)
order = Order(
dt=date,
asset=self.assets[order_status['symbol']],
amount=amount,
stop=stop_price,
limit=limit_price,
filled=filled,
id=order_status['id'],
commission=commission
)
order.status = status
return order, executed_price
def update_portfolio(self):
"""
Update the portfolio cash and position balances based on the
latest ticker prices.
:return:
"""
try:
response = self._request('balances', None)
balances = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in balances:
raise ExchangeRequestError(
error='unable to fetch balance {}'.format(balances['message'])
)
base_position = None
for position in balances:
if not base_position and position['type'] == 'exchange' \
and position['currency'] == self.base_currency:
base_position = position
if position is None:
raise ValueError(
error='Base currency %s not found in portfolio' % self.base_currency
)
portfolio = self._portfolio
portfolio.cash = float(base_position['available'])
if portfolio.starting_cash is None:
portfolio.starting_cash = portfolio.cash
if portfolio.positions:
assets = portfolio.positions.keys()
tickers = self.tickers(assets)
portfolio.positions_value = 0.0
for ticker in tickers:
# TODO: convert if the position is not in the base currency
position = portfolio.positions[ticker['asset']]
position.last_sale_price = ticker['last_price']
position.last_sale_date = ticker['timestamp']
portfolio.positions_value += \
position.amount * position.last_sale_price
portfolio.portfolio_value = \
portfolio.positions_value + portfolio.cash
@property
def portfolio(self):
"""
Return the Portfolio
:return:
"""
# if self._portfolio is None:
# portfolio = ExchangePortfolio(
# start_date=pd.Timestamp.utcnow()
# )
# self.store.portfolio = portfolio
# self.update_portfolio()
#
# portfolio.starting_cash = portfolio.cash
# else:
# portfolio = self.store.portfolio
return self._portfolio
@property
def account(self):
account = Account()
account.settled_cash = None
account.accrued_interest = None
account.buying_power = None
account.equity_with_loan = None
account.total_positions_value = None
account.total_positions_exposure = None
account.regt_equity = None
account.regt_margin = None
account.initial_margin_requirement = None
account.maintenance_margin_requirement = None
account.available_funds = None
account.excess_liquidity = None
account.cushion = None
account.day_trades_remaining = None
account.leverage = None
account.net_leverage = None
account.net_liquidation = None
return account
@property
def positions(self):
return self.portfolio.positions
@property
def time_skew(self):
# TODO: research the time skew conditions
return pd.Timedelta('0s')
def subscribe_to_market_data(self, symbol):
pass
def get_candles(self, data_frequency, assets, bar_count=None):
"""
Retrieve OHLVC candles from Bitfinex
:param data_frequency:
:param assets:
:param bar_count:
:return:
Available Frequencies
---------------------
'1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D',
'1M'
"""
# TODO: use BcolzMinuteBarReader to read from cache
freq_match = re.match(r'([0-9].*)(m|h|d)', data_frequency, re.M | re.I)
if freq_match:
number = int(freq_match.group(1))
unit = freq_match.group(2)
if unit == 'd':
converted_unit = 'D'
else:
converted_unit = unit
frequency = '{}{}'.format(number, converted_unit)
allowed_frequencies = ['1m', '5m', '15m', '30m', '1h', '3h', '6h',
'12h', '1D', '7D', '14D', '1M']
if frequency not in allowed_frequencies:
raise InvalidHistoryFrequencyError(
frequency=data_frequency
)
elif data_frequency == 'minute':
frequency = '1m'
elif data_frequency == 'daily':
frequency = '1D'
else:
raise InvalidHistoryFrequencyError(
frequency=data_frequency
)
# Making sure that assets are iterable
asset_list = [assets] if isinstance(assets, Asset) else assets
ohlc_list = dict()
for asset in asset_list:
symbol = self._get_v2_symbol(asset)
url = '{url}/v2/candles/trade:{frequency}:{symbol}'.format(
url=self.url,
frequency=frequency,
symbol=symbol
)
if bar_count:
is_list = True
url += '/hist?limit={}'.format(int(bar_count))
else:
is_list = False
url += '/last'
try:
response = requests.get(url)
except Exception as e:
raise ExchangeRequestError(error=e)
if 'error' in response.content:
raise ExchangeRequestError(
error='Unable to retrieve candles: {}'.format(
response.content)
)
candles = response.json()
def ohlc_from_candle(candle):
ohlc = dict(
open=np.float64(candle[1]),
high=np.float64(candle[3]),
low=np.float64(candle[4]),
close=np.float64(candle[2]),
volume=np.float64(candle[5]),
price=np.float64(candle[2]),
last_traded=pd.Timestamp.utcfromtimestamp(
candle[0] / 1000.0),
minute_dt=pd.Timestamp.utcnow().floor('1 min')
)
return ohlc
if is_list:
ohlc_bars = []
# We can to list candles from old to new
for candle in reversed(candles):
ohlc = ohlc_from_candle(candle)
ohlc_bars.append(ohlc)
ohlc_list[asset] = ohlc_bars
else:
ohlc = ohlc_from_candle(candles)
ohlc_list[asset] = ohlc
return ohlc_list[assets] \
if isinstance(assets, Asset) else ohlc_list
def order(self, asset, amount, limit_price, stop_price, style):
"""Place an order.
Parameters
----------
asset : Asset
The asset that this order is for.
amount : int
The amount of shares to order. If ``amount`` is positive, this is
the number of shares to buy or cover. If ``amount`` is negative,
this is the number of shares to sell or short.
limit_price : float, optional
The limit price for the order.
stop_price : float, optional
The stop price for the order.
style : ExecutionStyle, optional
The execution style for the order.
Returns
-------
order_id : str or None
The unique identifier for this order, or None if no order was
placed.
Notes
-----
The ``limit_price`` and ``stop_price`` arguments provide shorthands for
passing common execution styles. Passing ``limit_price=N`` is
equivalent to ``style=LimitOrder(N)``. Similarly, passing
``stop_price=M`` is equivalent to ``style=StopOrder(M)``, and passing
``limit_price=N`` and ``stop_price=M`` is equivalent to
``style=StopLimitOrder(N, M)``. It is an error to pass both a ``style``
and ``limit_price`` or ``stop_price``.
Bitfinex Order Types
--------------------
LIMIT, MARKET, STOP, TRAILING STOP,
EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP,
EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK.
See Also
--------
:class:`catalyst.finance.execution.ExecutionStyle`
:func:`catalyst.api.order_value`
:func:`catalyst.api.order_percent`
"""
if amount == 0:
log.warn('skipping order amount of 0')
return None
base_currency = asset.symbol.split('_')[1]
if base_currency.lower() != self.base_currency.lower():
raise NotImplementedError(
'Currency pairs must share their base with the exchange.'
)
is_buy = (amount > 0)
if isinstance(style, MarketOrder):
order_type = 'market'
elif isinstance(style, LimitOrder):
order_type = 'limit'
price = limit_price
elif isinstance(style, StopOrder):
order_type = 'stop'
price = stop_price
elif isinstance(style, StopLimitOrder):
log.warn('using limit order instead of stop/limit')
# TODO: Not sure how to do this with the api. Investigate.
order_type = 'limit'
price = limit_price
else:
raise NotImplementedError('%s orders not available' % style)
log.debug(
'ordering {amount} {symbol} for {price}'.format(
amount=amount,
symbol=asset.symbol,
price=price
)
)
exchange_symbol = self.get_symbol(asset)
req = dict(
symbol=exchange_symbol,
amount=str(float(abs(amount))),
price=str(float(price)),
side='buy' if is_buy else 'sell',
type='exchange ' + order_type, # TODO: support margin trades
exchange=self.name,
is_hidden=False,
is_postonly=False,
use_all_available=0,
ocoorder=False,
buy_price_oco=0,
sell_price_oco=0
)
date = pd.Timestamp.utcnow()
try:
response = self._request('order/new', req)
exchange_order = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in exchange_order:
raise ExchangeRequestError(
error='unable to create Bitfinex order {}'.format(
exchange_order['message'])
)
order_id = exchange_order['id']
order = Order(
dt=date,
asset=asset,
amount=amount,
stop=style.get_stop_price(is_buy),
limit=style.get_limit_price(is_buy),
id=order_id
)
# TODO: is this required?
order.broker_order_id = order_id
self.portfolio.create_order(order)
return order_id
def get_open_orders(self, asset=None):
"""Retrieve all of the current open orders.
Parameters
----------
asset : Asset
If passed and not None, return only the open orders for the given
asset instead of all open orders.
Returns
-------
open_orders : dict[list[Order]] or list[Order]
If no asset is passed this will return a dict mapping Assets
to a list containing all the open orders for the asset.
If an asset is passed then this will return a list of the open
orders for this asset.
"""
try:
response = self._request('orders', None)
order_statuses = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in order_statuses:
raise ExchangeRequestError(
error='Unable to retrieve open orders: {}'.format(
order_statuses['message'])
)
orders = list()
for order_status in order_statuses:
order, = self._create_order(order_status)
if asset is None or asset == order.sid:
orders.append(order)
return orders
def get_order(self, order_id):
"""Lookup an order based on the order id returned from one of the
order functions.
Parameters
----------
order_id : str
The unique identifier for the order.
Returns
-------
order : Order
The order object.
"""
try:
response = self._request(
'order/status', {'order_id': int(order_id)})
order_status = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in order_status:
raise ExchangeRequestError(
error='Unable to retrieve order status: {}'.format(
order_status['message'])
)
return self._create_order(order_status)
def cancel_order(self, order_param):
"""Cancel an open order.
Parameters
----------
order_param : str or Order
The order_id or order object to cancel.
"""
order_id = order_param.id \
if isinstance(order_param, Order) else order_param
try:
response = self._request('order/cancel', {'order_id': order_id})
status = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in status:
raise ExchangeRequestError(
error='Unable to cancel order: {} {}'.format(
order_id, status['message'])
)
def tickers(self, assets):
"""
Fetch ticket data for assets
https://docs.bitfinex.com/v2/reference#rest-public-tickers
:param assets:
:return:
"""
symbols = self._get_v2_symbols(assets)
log.debug('fetching tickers {}'.format(symbols))
try:
response = requests.get(
'{url}/v2/tickers?symbols={symbols}'.format(
url=self.url,
symbols=','.join(symbols),
)
)
except Exception as e:
raise ExchangeRequestError(error=e)
if 'error' in response.content:
raise ExchangeRequestError(
error='Unable to retrieve tickers: {}'.format(
response.content)
)
tickers = response.json()
formatted_tickers = []
for index, ticker in enumerate(tickers):
if not len(ticker) == 11:
raise ExchangeRequestError(
error='Invalid ticker in response: {}'.format(ticker)
)
tick = dict(
asset=assets[index],
timestamp=pd.Timestamp.utcnow(),
bid=ticker[1],
ask=ticker[3],
last_price=ticker[7],
low=ticker[10],
high=ticker[9],
volume=ticker[8],
)
formatted_tickers.append(tick)
log.debug('got tickers {}'.format(formatted_tickers))
return formatted_tickers
File diff suppressed because it is too large Load Diff
-121
View File
@@ -1,121 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from time import sleep
from logbook import Logger
from catalyst.data.data_portal import DataPortal
from catalyst.exchange.exchange_errors import (
ExchangeRequestError,
ExchangeBarDataError
)
log = Logger('DataPortalExchange')
class DataPortalExchange(DataPortal):
def __init__(self, exchange, *args, **kwargs):
self.exchange = exchange
# TODO: put somewhere accessible by each algo
self.retry_get_history_window = 5
self.retry_get_spot_value = 5
self.retry_delay = 5
super(DataPortalExchange, self).__init__(*args, **kwargs)
def _get_history_window(self,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True,
attempt_index=0):
try:
return self.exchange.get_history_window(
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill)
except ExchangeRequestError as e:
log.warn(
'get history attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_get_history_window:
sleep(self.retry_delay)
return self._get_history_window(assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill,
attempt_index + 1)
else:
raise ExchangeBarDataError(
data_type='history',
attempts=attempt_index,
error=e
)
def get_history_window(self,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True):
return self._get_history_window(assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill)
def _get_spot_value(self, assets, field, dt, data_frequency,
attempt_index=0):
try:
return self.exchange.get_spot_value(assets, field, dt,
data_frequency)
except ExchangeRequestError as e:
log.warn(
'get spot value attempt {}: {}'.format(attempt_index, e)
)
if attempt_index < self.retry_get_spot_value:
sleep(self.retry_delay)
return self._get_spot_value(assets, field, dt, data_frequency,
attempt_index + 1)
else:
raise ExchangeBarDataError(
data_type='spot',
attempts=attempt_index,
error=e
)
def get_spot_value(self, assets, field, dt, data_frequency):
return self._get_spot_value(assets, field, dt, data_frequency)
def get_adjusted_value(self, asset, field, dt,
perspective_dt,
data_frequency,
spot_value=None):
# TODO: does this pertain to cryptocurrencies?
raise NotImplementedError("get_adjusted_value is not implemented yet!")
File diff suppressed because it is too large Load Diff
+953
View File
@@ -0,0 +1,953 @@
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import pickle
import signal
import sys
from datetime import timedelta
from os import listdir
from os.path import isfile, join
import catalyst.protocol as zp
import logbook
import pandas as pd
from catalyst.algorithm import TradingAlgorithm
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.exchange_blotter import ExchangeBlotter
from catalyst.exchange.exchange_errors import (
ExchangeRequestError,
OrderTypeNotSupported)
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
from catalyst.exchange.live_graph_clock import LiveGraphClock
from catalyst.exchange.simple_clock import SimpleClock
from catalyst.exchange.utils.exchange_utils import (
save_algo_object,
get_algo_object,
get_algo_folder,
get_algo_df,
save_algo_df,
group_assets_by_exchange, )
from catalyst.exchange.utils.stats_utils import get_pretty_stats, stats_to_s3, \
stats_to_algo_folder
from catalyst.finance.execution import MarketOrder
from catalyst.finance.performance import PerformanceTracker
from catalyst.finance.performance.period import calc_period_stats
from catalyst.gens.tradesimulation import AlgorithmSimulator
from catalyst.utils.api_support import api_method
from catalyst.utils.input_validation import error_keywords, ensure_upper_case
from catalyst.utils.math_utils import round_nearest
from catalyst.utils.preprocess import preprocess
from redo import retry
log = logbook.Logger('exchange_algorithm', level=LOG_LEVEL)
class ExchangeAlgorithmExecutor(AlgorithmSimulator):
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
class ExchangeTradingAlgorithmBase(TradingAlgorithm):
def __init__(self, *args, **kwargs):
self.exchanges = kwargs.pop('exchanges', None)
self.simulate_orders = kwargs.pop('simulate_orders', None)
super(ExchangeTradingAlgorithmBase, self).__init__(*args, **kwargs)
self.current_day = None
if self.simulate_orders is None \
and self.sim_params.arena == 'backtest':
self.simulate_orders = True
# Operations with retry features
self.attempts = dict(
get_transactions_attempts=5,
order_attempts=5,
synchronize_portfolio_attempts=5,
get_order_attempts=5,
get_open_orders_attempts=5,
cancel_order_attempts=5,
get_spot_value_attempts=5,
get_history_window_attempts=5,
retry_sleeptime=5,
)
self.blotter = ExchangeBlotter(
data_frequency=self.data_frequency,
# Default to NeverCancel in catalyst
cancel_policy=self.cancel_policy,
simulate_orders=self.simulate_orders,
exchanges=self.exchanges,
attempts=self.attempts,
)
@staticmethod
def __convert_order_params_for_blotter(limit_price, stop_price, style):
"""
Helper method for converting deprecated limit_price and stop_price
arguments into ExecutionStyle instances.
This function assumes that either style == None or (limit_price,
stop_price) == (None, None).
"""
if stop_price:
raise OrderTypeNotSupported(order_type='stop')
if style:
if limit_price is not None:
raise ValueError(
'An order style and a limit price was included in the '
'order. Please pick one to avoid any possible conflict.'
)
# Currently limiting order types or limit and market to
# be in-line with CXXT and many exchanges. We'll consider
# adding more order types in the future.
if not isinstance(style, ExchangeLimitOrder) or \
not isinstance(style, MarketOrder):
raise OrderTypeNotSupported(
order_type=style.__class__.__name__
)
return style
if limit_price:
return ExchangeLimitOrder(limit_price)
else:
return MarketOrder()
@api_method
def set_commission(self, maker=None, taker=None):
key = list(self.blotter.commission_models.keys())[0]
if maker is not None:
self.blotter.commission_models[key].maker = maker
if taker is not None:
self.blotter.commission_models[key].taker = taker
@api_method
def set_slippage(self, spread=None):
key = list(self.blotter.slippage_models.keys())[0]
if spread is not None:
self.blotter.slippage_models[key].spread = spread
def _calculate_order(self, asset, amount,
limit_price=None, stop_price=None, style=None):
# Raises a ZiplineError if invalid parameters are detected.
self.validate_order_params(asset,
amount,
limit_price,
stop_price,
style)
# Convert deprecated limit_price and stop_price parameters to use
# ExecutionStyle objects.
style = self.__convert_order_params_for_blotter(limit_price,
stop_price,
style)
return amount, style
def round_order(self, amount, asset):
"""
We need fractions with cryptocurrencies
:param amount:
:return:
"""
return round_nearest(amount, asset.min_trade_size)
@api_method
@preprocess(symbol_str=ensure_upper_case)
def symbol(self, symbol_str, exchange_name=None):
"""Lookup an Equity by its ticker symbol.
Parameters
----------
symbol_str : str
The ticker symbol for the equity to lookup.
exchange_name: str
The name of the exchange containing the symbol
Returns
-------
equity : Equity
The equity that held the ticker symbol on the current
symbol lookup date.
Raises
------
SymbolNotFound
Raised when the symbols was not held on the current lookup date.
See Also
--------
:func:`catalyst.api.set_symbol_lookup_date`
"""
# If the user has not set the symbol lookup date,
# use the end_session as the date for sybmol->sid resolution.
_lookup_date = self._symbol_lookup_date \
if self._symbol_lookup_date is not None \
else self.sim_params.end_session
if exchange_name is None:
exchange = list(self.exchanges.values())[0]
else:
exchange = self.exchanges[exchange_name]
data_frequency = self.data_frequency \
if self.sim_params.arena == 'backtest' else None
return self.asset_finder.lookup_symbol(
symbol=symbol_str,
exchange=exchange,
data_frequency=data_frequency,
as_of_date=_lookup_date
)
def prepare_period_stats(self, start_dt, end_dt):
"""
Creates a dictionary representing the state of the tracker.
Parameters
----------
start_dt: datetime
end_dt: datetime
Notes
-----
I rewrote this in an attempt to better control the stats.
I don't want things to happen magically through complex logic
pertaining to backtesting.
"""
tracker = self.perf_tracker
cum = tracker.cumulative_performance
pos_stats = cum.position_tracker.stats()
period_stats = calc_period_stats(pos_stats, cum.ending_cash)
stats = dict(
period_start=tracker.period_start,
period_end=tracker.period_end,
capital_base=tracker.capital_base,
progress=tracker.progress,
ending_value=cum.ending_value,
ending_exposure=cum.ending_exposure,
capital_used=cum.cash_flow,
starting_value=cum.starting_value,
starting_exposure=cum.starting_exposure,
starting_cash=cum.starting_cash,
ending_cash=cum.ending_cash,
portfolio_value=cum.ending_cash + cum.ending_value,
pnl=cum.pnl,
returns=cum.returns,
period_open=start_dt,
period_close=end_dt,
gross_leverage=period_stats.gross_leverage,
net_leverage=period_stats.net_leverage,
short_exposure=pos_stats.short_exposure,
long_exposure=pos_stats.long_exposure,
short_value=pos_stats.short_value,
long_value=pos_stats.long_value,
longs_count=pos_stats.longs_count,
shorts_count=pos_stats.shorts_count,
)
# Merging cumulative risk
stats.update(tracker.cumulative_risk_metrics.to_dict())
# Merging latest recorded variables
stats.update(self.recorded_vars)
period = tracker.todays_performance
stats['positions'] = period.position_tracker.get_positions_list()
# we want the key to be absent, not just empty
# Only include transactions for given dt
stats['transactions'] = []
for date in period.processed_transactions:
if start_dt <= date < end_dt:
transactions = period.processed_transactions[date]
for t in transactions:
stats['transactions'].append(t.to_dict())
stats['orders'] = []
for date in period.orders_by_modified:
if start_dt <= date < end_dt:
orders = period.orders_by_modified[date]
for order in orders:
stats['orders'].append(orders[order].to_dict())
return stats
def run(self, data=None, overwrite_sim_params=True):
data.attempts = self.attempts
return super(ExchangeTradingAlgorithmBase, self).run(
data, overwrite_sim_params
)
class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase):
def __init__(self, *args, **kwargs):
super(ExchangeTradingAlgorithmBacktest, self).__init__(*args, **kwargs)
self.frame_stats = list()
self.state = {}
log.info('initialized trading algorithm in backtest mode')
def is_last_frame_of_day(self, data):
# TODO: adjust here to support more intervals
next_frame_dt = data.current_dt + timedelta(minutes=1)
if next_frame_dt.date() > data.current_dt.date():
return True
else:
return False
def handle_data(self, data):
super(ExchangeTradingAlgorithmBacktest, self).handle_data(data)
if self.data_frequency == 'minute':
frame_stats = self.prepare_period_stats(
data.current_dt, data.current_dt + timedelta(minutes=1)
)
self.frame_stats.append(frame_stats)
self.current_day = data.current_dt.floor('1D')
def _create_stats_df(self):
stats = pd.DataFrame(self.frame_stats)
stats.set_index('period_close', inplace=True, drop=False)
return stats
def analyze(self, perf):
stats = self._create_stats_df() if self.data_frequency == 'minute' \
else perf
super(ExchangeTradingAlgorithmBacktest, self).analyze(stats)
def run(self, data=None, overwrite_sim_params=True):
perf = super(ExchangeTradingAlgorithmBacktest, self).run(
data, overwrite_sim_params
)
# Rebuilding the stats to support minute data
stats = self._create_stats_df() if self.data_frequency == 'minute' \
else perf
return stats
class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
def __init__(self, *args, **kwargs):
self.algo_namespace = kwargs.pop('algo_namespace', None)
self.live_graph = kwargs.pop('live_graph', None)
self.stats_output = kwargs.pop('stats_output', None)
self._analyze_live = kwargs.pop('analyze_live', None)
self.end = kwargs.pop('end', None)
self._clock = None
self.frame_stats = list()
self.pnl_stats = get_algo_df(self.algo_namespace, 'pnl_stats')
self.custom_signals_stats = \
get_algo_df(self.algo_namespace, 'custom_signals_stats')
self.exposure_stats = \
get_algo_df(self.algo_namespace, 'exposure_stats')
self.is_running = True
self.stats_minutes = 1
self._last_orders = []
self.trading_client = None
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
try:
signal.signal(signal.SIGINT, self.signal_handler)
except ValueError:
log.warn("Can't initialize signal handler inside another thread."
"Exit should be handled by the user.")
log.info('initialized trading algorithm in live mode')
def interrupt_algorithm(self):
self.is_running = False
if self._analyze is None:
log.info('Exiting the algorithm.')
else:
log.info('Exiting the algorithm. Calling `analyze()` '
'before exiting the algorithm.')
algo_folder = get_algo_folder(self.algo_namespace)
folder = join(algo_folder, 'daily_performance')
files = [f for f in listdir(folder) if isfile(join(folder, f))]
daily_perf_list = []
for item in files:
filename = join(folder, item)
with open(filename, 'rb') as handle:
perf_period = pickle.load(handle)
perf_period_dict = perf_period.to_dict()
daily_perf_list.append(perf_period_dict)
stats = pd.DataFrame(daily_perf_list)
stats.set_index('period_close', drop=False, inplace=True)
self.analyze(stats)
sys.exit(0)
def signal_handler(self, signal, frame):
"""
Handles the keyboard interruption signal.
Parameters
----------
signal
frame
Returns
-------
"""
log.info('Interruption signal detected {}, exiting the '
'algorithm'.format(signal))
self.interrupt_algorithm()
@property
def clock(self):
if self._clock is None:
return self._create_clock()
else:
return self._clock
def _create_clock(self):
# The calendar's execution times are the minutes over which we actually
# want to run the clock. Typically the execution times simply adhere to
# the market open and close times. In the case of the futures calendar,
# for example, we only want to simulate over a subset of the full 24
# hour calendar, so the execution times dictate a market open time of
# 6:31am US/Eastern and a close of 5:00pm US/Eastern.
# In our case, we are trading around the clock, so the market close
# corresponds to the last minute of the day.
# This method is taken from TradingAlgorithm.
# The clock has been replaced to use RealtimeClock
# TODO: should we apply time skew? not sure to understand the utility.
log.debug('creating clock')
if self.live_graph or self._analyze_live is not None:
self._clock = LiveGraphClock(
self.sim_params.sessions,
context=self,
callback=self._analyze_live,
)
else:
self._clock = SimpleClock(
self.sim_params.sessions,
)
return self._clock
def _init_trading_client(self):
"""
This replaces Ziplines `_create_generator` method. The main difference
is that we are restoring performance tracker objects if available.
This allows us to stop/start algos without loosing their state.
"""
self.state = get_algo_object(
algo_name=self.algo_namespace,
key='context.state',
)
if self.state is None:
self.state = {}
if self.perf_tracker is None:
# Note from the Zipline dev:
# HACK: When running with the `run` method, we set perf_tracker to
# None so that it will be overwritten here.
tracker = self.perf_tracker = PerformanceTracker(
sim_params=self.sim_params,
trading_calendar=self.trading_calendar,
env=self.trading_environment,
)
# Set the dt initially to the period start by forcing it to change.
self.on_dt_changed(self.sim_params.start_session)
new_position_tracker = tracker.position_tracker
tracker.position_tracker = None
# Unpacking the perf_tracker and positions if available
cum_perf = get_algo_object(
algo_name=self.algo_namespace,
key='cumulative_performance',
)
if cum_perf is not None:
tracker.cumulative_performance = cum_perf
# Ensure single common position tracker
tracker.position_tracker = cum_perf.position_tracker
today = pd.Timestamp.utcnow().floor('1D')
todays_perf = get_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
rel_path='daily_performance',
)
if todays_perf is not None:
# Ensure single common position tracker
if tracker.position_tracker is not None:
todays_perf.position_tracker = tracker.position_tracker
else:
tracker.position_tracker = todays_perf.position_tracker
tracker.todays_performance = todays_perf
if tracker.position_tracker is None:
# Use a new position_tracker if not is found in the state
tracker.position_tracker = new_position_tracker
if not self.initialized:
# Calls the initialize function of the algorithm
self.initialize(*self.initialize_args, **self.initialize_kwargs)
self.initialized = True
self.trading_client = ExchangeAlgorithmExecutor(
algo=self,
sim_params=self.sim_params,
data_portal=self.data_portal,
clock=self.clock,
benchmark_source=self._create_benchmark_source(),
restrictions=self.restrictions,
universe_func=self._calculate_universe,
)
def get_generator(self):
if self.trading_client is None:
self._init_trading_client()
return self.trading_client.transform()
def updated_portfolio(self):
return self.perf_tracker.get_portfolio(False)
def updated_account(self):
return self.perf_tracker.get_account(False)
def synchronize_portfolio(self):
"""
Synchronizes the portfolio tracked by the algorithm to refresh
its current value.
This includes updating the last_sale_price of all tracked
positions, returning the available cash, and raising error
if the data goes out of sync.
Returns
-------
float
The amount of base currency available for trading.
float
The total value of all tracked positions.
"""
check_balances = (not self.simulate_orders)
base_currency = None
tracker = self.perf_tracker.position_tracker
total_cash = 0.0
total_positions_value = 0.0
# Position keys correspond to assets
positions = self.portfolio.positions
assets = list(positions)
exchange_assets = group_assets_by_exchange(assets)
for exchange_name in self.exchanges:
assets = exchange_assets[exchange_name] \
if exchange_name in exchange_assets else []
exchange_positions = copy.deepcopy(
[positions[asset] for asset in assets if asset in positions]
)
exchange = self.exchanges[exchange_name] # Type: Exchange
if base_currency is None:
base_currency = exchange.base_currency
# Don't check the cash if there are open orders. This could
# results in false positives.
orders = []
for asset in self.blotter.open_orders:
asset_orders = self.blotter.open_orders[asset]
if asset_orders:
orders += asset_orders
required_cash = self.portfolio.cash if not orders else None
cash, positions_value = exchange.sync_positions(
positions=exchange_positions,
check_balances=check_balances,
cash=required_cash,
)
total_cash += cash
total_positions_value += positions_value
# Applying modifications to the original positions
for position in exchange_positions:
tracker.update_position(
asset=position.asset,
amount=position.amount,
last_sale_date=position.last_sale_date,
last_sale_price=position.last_sale_price,
)
if not check_balances:
total_cash = self.portfolio.cash
return total_cash, total_positions_value
def add_pnl_stats(self, period_stats):
"""
Save p&l stats.
Parameters
----------
period_stats
Returns
-------
"""
starting = period_stats['starting_cash']
current = period_stats['portfolio_value']
appreciation = (current / starting) - 1
perc = (appreciation * 100) if current != 0 else 0
log.debug('adding pnl stats: {:6f}%'.format(perc))
df = pd.DataFrame(
data=[dict(performance=perc)],
index=[period_stats['period_close']]
)
self.pnl_stats = pd.concat([self.pnl_stats, df])
save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats)
def add_custom_signals_stats(self, period_stats):
"""
Save custom signals stats.
Parameters
----------
period_stats
Returns
-------
"""
log.debug('adding custom signals stats: {}'.format(self.recorded_vars))
df = pd.DataFrame(
data=[self.recorded_vars],
index=[period_stats['period_close']],
)
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
save_algo_df(self.algo_namespace, 'custom_signals_stats',
self.custom_signals_stats)
def add_exposure_stats(self, period_stats):
"""
Save exposure stats.
Parameters
----------
period_stats
Returns
-------
"""
data = dict(
long_exposure=period_stats['long_exposure'],
base_currency=period_stats['ending_cash']
)
log.debug('adding exposure stats: {}'.format(data))
df = pd.DataFrame(
data=[data],
index=[period_stats['period_close']],
)
self.exposure_stats = pd.concat([self.exposure_stats, df])
save_algo_df(
self.algo_namespace, 'exposure_stats', self.exposure_stats
)
def handle_data(self, data):
"""
Wrapper around the handle_data method of each algo.
Parameters
----------
data
"""
if not self.is_running:
return
if self.end is not None and self.end < data.current_dt:
log.info('Algorithm has reached specified end time. Finishing...')
self.interrupt_algorithm()
# Resetting the frame stats every day to minimize memory footprint
today = data.current_dt.floor('1D')
if self.current_day is not None and today > self.current_day:
self.frame_stats = list()
self.performance_needs_update = False
orders = list(self.perf_tracker.todays_performance.orders_by_id.keys())
if orders != self._last_orders:
self.performance_needs_update = True
# Saving current orders to detect changes in the next frame
self._last_orders = copy.deepcopy(orders)
if self.performance_needs_update:
self.perf_tracker.update_performance()
self.performance_needs_update = False
if self.portfolio_needs_update:
cash, positions_value = retry(
action=self.synchronize_portfolio,
attempts=self.attempts['synchronize_portfolio_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('Ordering again.')
)
self.portfolio_needs_update = False
log.info(
'portfolio balances, cash: {}, positions: {}'.format(
cash, positions_value
)
)
if self._handle_data:
self._handle_data(self, data)
# Unlike trading controls which remain constant unless placing an
# order, account controls can change each bar. Thus, must check
# every bar no matter if the algorithm places an order or not.
self.validate_account_controls()
self._save_algo_state(data)
self.current_day = data.current_dt.floor('1D')
def _save_algo_state(self, data):
today = data.current_dt.floor('1D')
try:
self._save_stats_csv(self._process_stats(data))
except Exception as e:
log.warn('unable to calculate performance: {}'.format(e))
log.debug('saving cumulative performance object')
save_algo_object(
algo_name=self.algo_namespace,
key='cumulative_performance',
obj=self.perf_tracker.cumulative_performance,
)
log.debug('saving todays performance object')
save_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
obj=self.perf_tracker.todays_performance,
rel_path='daily_performance'
)
log.debug('saving context.state object')
save_algo_object(
algo_name=self.algo_namespace,
key='context.state',
obj=self.state)
def _process_stats(self, data):
today = data.current_dt.floor('1D')
# Since the clock runs 24/7, I trying to disable the daily
# Performance tracker and keep only minute and cumulative
self.perf_tracker.update_performance()
frame_stats = self.prepare_period_stats(
data.current_dt, data.current_dt + timedelta(minutes=1)
)
# Saving the last hour in memory
self.frame_stats.append(frame_stats)
self.add_pnl_stats(frame_stats)
if self.recorded_vars:
self.add_custom_signals_stats(frame_stats)
recorded_cols = list(self.recorded_vars.keys())
else:
recorded_cols = None
self.add_exposure_stats(frame_stats)
log.info(
'statistics for the last {stats_minutes} minutes:\n'
'{stats}'.format(
stats_minutes=self.stats_minutes,
stats=get_pretty_stats(
stats=self.frame_stats,
recorded_cols=recorded_cols,
num_rows=self.stats_minutes,
)
))
# Saving the daily stats in a format usable for performance
# analysis.
daily_stats = self.prepare_period_stats(
start_dt=today,
end_dt=data.current_dt
)
return recorded_cols
def _save_stats_csv(self, recorded_cols):
# Writing the stats output
csv_bytes = None
try:
csv_bytes = stats_to_algo_folder(
stats=self.frame_stats,
algo_namespace=self.algo_namespace,
recorded_cols=recorded_cols,
)
except Exception as e:
log.warn('unable save stats locally: {}'.format(e))
try:
if self.stats_output is not None:
if 's3://' in self.stats_output:
stats_to_s3(
uri=self.stats_output,
stats=self.frame_stats,
algo_namespace=self.algo_namespace,
recorded_cols=recorded_cols,
bytes_to_write=csv_bytes
)
else:
raise ValueError(
'Only S3 stats output is supported for now.'
)
except Exception as e:
log.warn('unable save stats externally: {}'.format(e))
@api_method
def batch_market_order(self, share_counts):
raise NotImplementedError()
def _get_open_orders(self, asset=None):
if asset:
exchange = self.exchanges[asset.exchange]
return exchange.get_open_orders(asset)
else:
open_orders = []
for exchange_name in self.exchanges:
exchange = self.exchanges[exchange_name]
exchange_orders = exchange.get_open_orders()
open_orders.append(exchange_orders)
return open_orders
@error_keywords(sid='Keyword argument `sid` is no longer supported for '
'get_open_orders. Use `asset` instead.')
@api_method
def get_open_orders(self, asset=None):
"""Retrieve all of the current open orders.
Parameters
----------
asset : Asset
If passed and not None, return only the open orders for the given
asset instead of all open orders.
Returns
-------
open_orders : dict[list[Order]] or list[Order]
If no asset is passed this will return a dict mapping Assets
to a list containing all the open orders for the asset.
If an asset is passed then this will return a list of the open
orders for this asset.
"""
return retry(
action=self._get_open_orders,
attempts=self.attempts['get_open_orders_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('Fetching open orders again.'),
args=(asset,))
@api_method
def get_order(self, order_id, exchange_name):
"""Lookup an order based on the order id returned from one of the
order functions.
Parameters
----------
order_id : str
The unique identifier for the order.
Returns
-------
order : Order
The order object.
execution_price: float
The execution price per share of the order
"""
exchange = self.exchanges[exchange_name]
return retry(
action=exchange.get_order,
attempts=self.attempts['get_order_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('Fetching orders again.'),
args=(order_id,))
@api_method
def cancel_order(self, order_param, exchange_name):
"""Cancel an open order.
Parameters
----------
order_param : str or Order
The order_id or order object to cancel.
"""
exchange = self.exchanges[exchange_name]
order_id = order_param
if isinstance(order_param, zp.Order):
order_id = order_param.id
retry(
action=exchange.cancel_order,
attempts=self.attempts['cancel_order_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('cancelling order again.'),
args=(order_id,))
+179
View File
@@ -0,0 +1,179 @@
import pandas as pd
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.utils.factory import find_exchanges
from logbook import Logger
log = Logger('ExchangeAssetFinder', level=LOG_LEVEL)
class ExchangeAssetFinder(object):
def __init__(self, exchanges):
self.exchanges = exchanges
@property
def sids(self):
"""
This seems to be used to pre-fetch assets.
I don't think that we need this for live-trading.
Leaving the list empty.
"""
all_sids = []
for exchange_name in self.exchanges:
# This is what initializes each exchanges at the beginning
# of an algo
exchange = self.exchanges[exchange_name]
exchange.init()
all_sids += [asset.sid for asset in exchange.assets]
sids = list(set(all_sids))
return sids
def retrieve_asset(self, sid, default_none=False):
"""
Retrieve the first Asset found for a given sid.
"""
asset = None
for exchange_name in self.exchanges:
if asset is not None:
break
exchange = self.exchanges[exchange_name]
assets = [asset for asset in exchange.assets if asset.sid == sid]
if assets:
asset = assets[0]
return asset
def retrieve_all(self, sids, default_none=False):
"""
Retrieve all assets in `sids`.
Parameters
----------
sids : iterable of int
Assets to retrieve.
default_none : bool
If True, return None for failed lookups.
If False, raise `SidsNotFound`.
Returns
-------
assets : list[Asset or None]
A list of the same length as `sids` containing Assets (or Nones)
corresponding to the requested sids.
Raises
------
SidsNotFound
When a requested sid is not found and default_none=False.
"""
assets = []
for exchange_name in self.exchanges:
exchange = self.exchanges[exchange_name]
xas = [asset for asset in exchange.assets if asset.sid in sids]
assets += xas
return assets
def lookup_symbol(self, symbol, exchange, data_frequency=None,
as_of_date=None, fuzzy=False):
"""Lookup an asset by symbol.
Parameters
----------
symbol : str
The ticker symbol to resolve.
as_of_date : datetime or None
Look up the last owner of this symbol as of this datetime.
If ``as_of_date`` is None, then this can only resolve the equity
if exactly one equity has ever owned the ticker.
fuzzy : bool, optional
Should fuzzy symbol matching be used? Fuzzy symbol matching
attempts to resolve differences in representations for
shareclasses. For example, some people may represent the ``A``
shareclass of ``BRK`` as ``BRK.A``, where others could write
``BRK_A``.
Returns
-------
equity : Asset
The equity that held ``symbol`` on the given ``as_of_date``, or the
only equity to hold ``symbol`` if ``as_of_date`` is None.
Raises
------
SymbolNotFound
Raised when no equity has ever held the given symbol.
MultipleSymbolsFound
Raised when no ``as_of_date`` is given and more than one equity
has held ``symbol``. This is also raised when ``fuzzy=True`` and
there are multiple candidates for the given ``symbol`` on the
``as_of_date``.
"""
log.debug('looking up symbol: {} {}'.format(symbol, exchange.name))
return exchange.get_asset(symbol, data_frequency)
def lifetimes(self, dates, include_start_date):
"""
Compute a DataFrame representing asset lifetimes for the specified date
range.
Parameters
----------
dates : pd.DatetimeIndex
The dates for which to compute lifetimes.
include_start_date : bool
Whether or not to count the asset as alive on its start_date.
This is useful in a backtesting context where `lifetimes` is being
used to signify "do I have data for this asset as of the morning of
this date?" For many financial metrics, (e.g. daily close), data
isn't available for an asset until the end of the asset's first
day.
Returns
-------
lifetimes : pd.DataFrame
A frame of dtype bool with `dates` as index and an Int64Index of
assets as columns. The value at `lifetimes.loc[date, asset]` will
be True iff `asset` existed on `date`. If `include_start_date` is
False, then lifetimes.loc[date, asset] will be false when date ==
asset.start_date.
See Also
--------
numpy.putmask
catalyst.pipeline.engine.SimplePipelineEngine._compute_root_mask
"""
exchanges = find_exchanges(features=['minuteBundle'])
if not exchanges:
raise ValueError('exchange with minute bundles not found')
# TODO: find a way to support multiple exchanges
exchange = exchanges[0]
# Using a single exchange for now because are not unique for the
# same asset in different exchanges. I'd like to avoid binding
# pipeline to a single exchange.
exchange.init()
data = []
for dt in dates:
exists = []
for asset in exchange.assets:
if include_start_date:
condition = (asset.start_date <= dt < asset.end_minute)
else:
condition = (asset.start_date < dt < asset.end_minute)
exists.append(condition)
data.append(exists)
sids = [asset.sid for asset in exchange.assets]
df = pd.DataFrame(data, index=dates, columns=exchange.assets)
return df
+98
View File
@@ -0,0 +1,98 @@
import numpy as np
from catalyst import get_calendar
from catalyst.data.minute_bars import BcolzMinuteBarReader, \
BcolzMinuteBarWriter
class BcolzExchangeBarWriter(BcolzMinuteBarWriter):
def __init__(self, *args, **kwargs):
self._data_frequency = kwargs.pop('data_frequency', None)
kwargs.pop('minutes_per_day', None)
kwargs.pop('calendar', None)
end_session = kwargs.pop('end_session', None)
if end_session is not None:
end_session = end_session.floor('1d')
minutes_per_day = 1440 if self._data_frequency == 'minute' else 1
default_ohlc_ratio = kwargs.pop('default_ohlc_ratio', 100000000)
calendar = get_calendar('OPEN')
super(BcolzExchangeBarWriter, self) \
.__init__(*args, **dict(kwargs,
minutes_per_day=minutes_per_day,
default_ohlc_ratio=default_ohlc_ratio,
calendar=calendar,
end_session=end_session
))
class BcolzExchangeBarReader(BcolzMinuteBarReader):
def __init__(self, *args, **kwargs):
self._data_frequency = kwargs.pop('data_frequency', None)
super(BcolzExchangeBarReader, self).__init__(*args, **kwargs)
@property
def data_frequency(self):
return self._data_frequency
def load_raw_arrays(self, fields, start_dt, end_dt, sids):
"""
Parameters
----------
fields : list of str
'open', 'high', 'low', 'close', or 'volume'
start_dt: Timestamp
Beginning of the window range.
end_dt: Timestamp
End of the window range.
sids : list of int
The asset identifiers in the window.
Returns
-------
list of np.ndarray
A list with an entry per field of ndarrays with shape
(minutes in range, sids) with a dtype of float64, containing the
values for the respective field over start and end dt range.
"""
start_idx = self._find_position_of_minute(start_dt)
end_idx = self._find_position_of_minute(end_dt)
periods = self.calendar.minutes_in_range(start_dt, end_dt) \
if self.data_frequency == 'minute' \
else self.calendar.sessions_in_range(start_dt, end_dt)
num_days = len(periods)
shape = num_days, len(sids)
all_fields = fields[:]
if len(all_fields) == 1 and all_fields[0] == 'volume':
all_fields.insert(0, 'close')
mask = None
data = []
for field in all_fields:
if field != 'volume':
out = np.full(shape, np.nan)
else:
out = np.zeros(shape, dtype=np.float64)
for i, sid in enumerate(sids):
carray = self._open_minute_file(field, sid)
a = carray[start_idx:end_idx + 1]
if mask is None:
mask = a != 0
inverse_ratio = self._ohlc_ratio_inverse_for_sid(sid)
out[:len(mask), i][mask] = (
a[mask] * inverse_ratio
)
if field in fields:
data.append(out)
return data
+274
View File
@@ -0,0 +1,274 @@
import numpy as np
import pandas as pd
from logbook import Logger
from redo import retry
from catalyst.assets._assets import TradingPair
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.exchange_errors import ExchangeRequestError
from catalyst.finance.blotter import Blotter
from catalyst.finance.commission import CommissionModel
from catalyst.finance.order import ORDER_STATUS
from catalyst.finance.slippage import SlippageModel
from catalyst.finance.transaction import create_transaction, Transaction
from catalyst.utils.input_validation import expect_types
log = Logger('exchange_blotter', level=LOG_LEVEL)
class TradingPairFeeSchedule(CommissionModel):
"""
Calculates a commission for a transaction based on a per percentage fee.
Parameters
----------
maker : float, optional
The percentage maker fee.
taker: float, optional
The percentage taker fee.
"""
def __init__(self, maker=None, taker=None):
self.maker = maker
self.taker = taker
def __repr__(self):
return (
'{class_name}(maker={maker}, '
'taker={taker})'.format(
class_name=self.__class__.__name__,
maker=self.maker,
taker=self.taker,
)
)
def get_maker_taker(self, asset):
maker = self.maker if self.maker is not None else asset.maker
taker = self.taker if self.taker is not None else asset.taker
return maker, taker
def calculate(self, order, transaction):
"""
Calculate the final fee based on the order parameters.
:param order: Order
:param transaction: Transaction
:return float:
The total commission.
"""
cost = abs(transaction.amount) * transaction.price
asset = order.asset
maker, taker = self.get_maker_taker(asset)
multiplier = taker
if order.limit is not None:
multiplier = maker \
if ((order.amount > 0 and order.limit < transaction.price)
or (order.amount < 0 and order.limit > transaction.price)) \
and order.limit_reached else taker
fee = cost * multiplier
return fee
class TradingPairFixedSlippage(SlippageModel):
"""
Model slippage as a fixed spread.
Parameters
----------
spread : float, optional
spread / 2 will be added to buys and subtracted from sells.
"""
def __init__(self, spread=0.0001):
super(TradingPairFixedSlippage, self).__init__()
self.spread = spread
def __repr__(self):
return '{class_name}(spread={spread})'.format(
class_name=self.__class__.__name__, spread=self.spread,
)
def simulate(self, data, asset, orders_for_asset):
self._volume_for_bar = 0
price = data.current(asset, 'close')
dt = data.current_dt
for order in orders_for_asset:
if order.open_amount == 0:
continue
order.check_triggers(price, dt)
if not order.triggered:
log.info(
'order has not reached the trigger at current '
'price {}'.format(price)
)
continue
execution_price, execution_volume = self.process_order(data, order)
if execution_price is not None:
transaction = create_transaction(
order, dt, execution_price, execution_volume
)
self._volume_for_bar += abs(transaction.amount)
yield order, transaction
def process_order(self, data, order):
price = data.current(order.asset, 'close')
if order.amount > 0:
# Buy order
adj_price = price * (1 + self.spread)
else:
# Sell order
adj_price = price * (1 - self.spread)
log.debug('added slippage to price: {} => {}'.format(price, adj_price))
return adj_price, order.amount
class ExchangeBlotter(Blotter):
def __init__(self, *args, **kwargs):
self.simulate_orders = kwargs.pop('simulate_orders', False)
self.attempts = kwargs.pop('attempts', False)
self.exchanges = kwargs.pop('exchanges', None)
if not self.exchanges:
raise ValueError(
'ExchangeBlotter must have an `exchanges` attribute.'
)
super(ExchangeBlotter, self).__init__(*args, **kwargs)
# Using the equity models for now
# We may be able to define more sophisticated models based on the fee
# structure of each exchange.
self.slippage_models = {
TradingPair: TradingPairFixedSlippage()
}
self.commission_models = {
TradingPair: TradingPairFeeSchedule()
}
def exchange_order(self, asset, amount, style=None):
exchange = self.exchanges[asset.exchange]
return exchange.order(
asset, amount, style
)
@expect_types(asset=TradingPair)
def order(self, asset, amount, style, order_id=None):
log.debug('ordering {} {}'.format(amount, asset.symbol))
if amount == 0:
log.warn('skipping 0 amount orders')
return None
if self.simulate_orders:
return super(ExchangeBlotter, self).order(
asset, amount, style, order_id
)
else:
order = retry(
action=self.exchange_order,
attempts=self.attempts['order_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('Ordering again.'),
args=(asset, amount, style),
)
self.open_orders[order.asset].append(order)
self.orders[order.id] = order
self.new_orders.append(order)
return order.id
def check_open_orders(self):
"""
Loop through the list of open orders in the Portfolio object.
For each executed order found, create a transaction and apply to the
Portfolio.
Returns
-------
list[Transaction]
"""
for asset in self.open_orders:
exchange = self.exchanges[asset.exchange]
for order in self.open_orders[asset]:
log.debug('found open order: {}'.format(order.id))
transactions = exchange.process_order(order)
# This is a temporary measure, we should really update all
# trades, not just when the order gets filled. I just think
# that this is safer until we have a robust way to track
# the trades already processed by the algo. We can't loose
# them if the algo shuts down.
if transactions and order.open_amount == 0:
avg_price = np.average(
a=[t.price for t in transactions],
weights=[t.amount for t in transactions],
)
ostatus = 'filled' if order.open_amount == 0 else 'partial'
log.info(
'{} order {} / {}: {}, avg price: {}'.format(
ostatus,
order.id,
asset.symbol,
order.filled,
avg_price,
)
)
for transaction in transactions:
yield order, transaction
elif order.status == ORDER_STATUS.CANCELLED:
yield order, None
else:
delta = pd.Timestamp.utcnow() - order.dt
log.info(
'order {order_id} still open after {delta}'.format(
order_id=order.id,
delta=delta
)
)
def get_exchange_transactions(self):
closed_orders = []
transactions = []
commissions = []
for order, txn in self.check_open_orders():
order.dt = txn.dt
transactions.append(txn)
if not order.open:
closed_orders.append(order)
return transactions, commissions, closed_orders
def get_transactions(self, bar_data):
if self.simulate_orders:
return super(ExchangeBlotter, self).get_transactions(bar_data)
else:
return retry(
action=self.get_exchange_transactions,
attempts=self.attempts['get_transactions_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn(
'Fetching exchange transactions again.'
)
)
File diff suppressed because it is too large Load Diff
+373
View File
@@ -0,0 +1,373 @@
import abc
import numpy as np
import pandas as pd
from catalyst.assets._assets import TradingPair
from catalyst.constants import LOG_LEVEL, AUTO_INGEST
from catalyst.data.data_portal import DataPortal
from catalyst.exchange.exchange_bundle import ExchangeBundle
from catalyst.exchange.exchange_errors import (
ExchangeRequestError,
PricingDataNotLoadedError)
from catalyst.exchange.utils.exchange_utils import resample_history_df, group_assets_by_exchange
from catalyst.exchange.utils.datetime_utils import get_frequency
from logbook import Logger
from redo import retry
log = Logger('DataPortalExchange', level=LOG_LEVEL)
class DataPortalExchangeBase(DataPortal):
def __init__(self, *args, **kwargs):
self.attempts = dict(
get_spot_value_attempts=5,
get_history_window_attempts=5,
retry_sleeptime=5,
)
super(DataPortalExchangeBase, self).__init__(*args, **kwargs)
def _get_history_window(self,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True):
exchange_assets = group_assets_by_exchange(assets)
if len(exchange_assets) > 1:
df_list = []
for exchange_name in exchange_assets:
assets = exchange_assets[exchange_name]
df_exchange = self.get_exchange_history_window(
exchange_name,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill)
df_list.append(df_exchange)
# Merging the values values of each exchange
return pd.concat(df_list)
else:
exchange_name = list(exchange_assets.keys())[0]
return self.get_exchange_history_window(
exchange_name,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill)
def get_history_window(self,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency=None,
ffill=True):
if field == 'price':
field = 'close'
return retry(
action=self._get_history_window,
attempts=self.attempts['get_history_window_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('fetching history again.'),
args=(assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill))
@abc.abstractmethod
def get_exchange_history_window(self,
exchange_name,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True):
pass
def _get_spot_value(self, assets, field, dt, data_frequency):
if isinstance(assets, TradingPair):
spot_values = self.get_exchange_spot_value(
assets.exchange, [assets], field, dt, data_frequency)
if not spot_values:
return np.nan
return spot_values[0]
else:
exchange_assets = dict()
for asset in assets:
if asset.exchange not in exchange_assets:
exchange_assets[asset.exchange] = list()
exchange_assets[asset.exchange].append(asset)
if len(list(exchange_assets.keys())) == 1:
exchange_name = list(exchange_assets.keys())[0]
return self.get_exchange_spot_value(
exchange_name, assets, field, dt, data_frequency)
else:
spot_values = []
for exchange_name in exchange_assets:
assets = exchange_assets[exchange_name]
exchange_spot_values = self.get_exchange_spot_value(
exchange_name,
assets,
field,
dt,
data_frequency
)
if len(assets) == 1:
spot_values.append(exchange_spot_values)
else:
spot_values += exchange_spot_values
return spot_values
def get_spot_value(self, assets, field, dt, data_frequency):
if field == 'price':
field = 'close'
return retry(
action=self._get_spot_value,
attempts=self.attempts['get_spot_value_attempts'],
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('fetching spot value again.'),
args=(assets, field, dt, data_frequency))
@abc.abstractmethod
def get_exchange_spot_value(self, exchange_name, assets, field, dt,
data_frequency):
return
def get_adjusted_value(self, asset, field, dt,
perspective_dt,
data_frequency,
spot_value=None):
# TODO: does this pertain to cryptocurrencies?
log.warn('get_adjusted_value is not implemented yet!')
return spot_value
class DataPortalExchangeLive(DataPortalExchangeBase):
def __init__(self, *args, **kwargs):
self.exchanges = kwargs.pop('exchanges', None)
super(DataPortalExchangeLive, self).__init__(*args, **kwargs)
def get_exchange_history_window(self,
exchange_name,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True):
"""
Fetching price history window from the exchange.
Parameters
----------
exchange_name: Exchange
assets: list[TradingPair]
end_dt: datetime
bar_count: int
frequency: str
field: str
data_frequency: str
ffill: bool
Returns
-------
DataFrame
"""
exchange = self.exchanges[exchange_name]
df = exchange.get_history_window(
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
False)
return df
def get_exchange_spot_value(self, exchange_name, assets, field, dt,
data_frequency):
"""
A spot value for the exchange.
Parameters
----------
exchange_name: str
assets: list[TradingPair]
field: str
dt: datetime
data_frequency: str
Returns
-------
float
"""
exchange = self.exchanges[exchange_name]
exchange_spot_values = exchange.get_spot_value(
assets, field, dt, data_frequency)
return exchange_spot_values
class DataPortalExchangeBacktest(DataPortalExchangeBase):
def __init__(self, *args, **kwargs):
self.exchange_names = kwargs.pop('exchange_names', None)
super(DataPortalExchangeBacktest, self).__init__(*args, **kwargs)
self.exchange_bundles = dict()
self.history_loaders = dict()
self.minute_history_loaders = dict()
for name in self.exchange_names:
self.exchange_bundles[name] = ExchangeBundle(name)
def _get_first_trading_day(self, assets):
first_date = None
for asset in assets:
if first_date is None or asset.start_date > first_date:
first_date = asset.start_date
return first_date
def get_exchange_history_window(self,
exchange_name,
assets,
end_dt,
bar_count,
frequency,
field,
data_frequency,
ffill=True):
"""
Fetching price history window from the exchange bundle.
Parameters
----------
exchange: Exchange
assets: list[TradingPair]
end_dt: datetime
bar_count: int
frequency: str
field: str
data_frequency: str
ffill: bool
Returns
-------
DataFrame
"""
# TODO: verify that the exchange supports the timeframe
bundle = self.exchange_bundles[exchange_name] # type: ExchangeBundle
freq, candle_size, unit, adj_data_frequency = get_frequency(
frequency, data_frequency
)
adj_bar_count = candle_size * bar_count
trailing_bar_count = candle_size - 1
if data_frequency == 'minute' and adj_data_frequency == 'daily':
end_dt = end_dt.floor('1D')
series = bundle.get_history_window_series_and_load(
assets=assets,
end_dt=end_dt,
bar_count=adj_bar_count,
field=field,
data_frequency=adj_data_frequency,
algo_end_dt=self._last_available_session,
trailing_bar_count=trailing_bar_count,
)
df = resample_history_df(pd.DataFrame(series), freq, field)
return df
def get_exchange_spot_value(self,
exchange_name,
assets,
field,
dt,
data_frequency
):
"""
A spot value for the exchange bundle. Try to ingest data if not in
the bundle.
Parameters
----------
exchange_name: str
assets: list[TradingPair]
field: str
dt: datetime
data_frequency: str
Returns
-------
float
"""
bundle = self.exchange_bundles[exchange_name]
if data_frequency == 'daily':
dt = dt.floor('1D')
else:
dt = dt.floor('1 min')
if AUTO_INGEST:
try:
return bundle.get_spot_values(
assets, field, dt, data_frequency
)
except PricingDataNotLoadedError:
log.info(
'pricing data for {symbol} not found on {dt}'
', updating the bundles.'.format(
symbol=[asset.symbol for asset in assets],
dt=dt
)
)
bundle.ingest_assets(
assets=assets,
start_dt=self._first_trading_day,
end_dt=self._last_available_session,
data_frequency=data_frequency,
show_progress=True
)
return bundle.get_spot_values(
assets, field, dt, data_frequency, True
)
else:
return bundle.get_spot_values(assets, field, dt, data_frequency)
+265 -1
View File
@@ -1,6 +1,24 @@
import sys
import traceback
from catalyst.errors import ZiplineError
def silent_except_hook(exctype, excvalue, exctraceback):
if exctype in [PricingDataBeforeTradingError, PricingDataNotLoadedError,
SymbolNotFoundOnExchange, NoDataAvailableOnExchange,
ExchangeAuthEmpty]:
fn = traceback.extract_tb(exctraceback)[-1][0]
ln = traceback.extract_tb(exctraceback)[-1][1]
print("Error traceback: {1} (line {2})\n"
"{0.__name__}: {3}".format(exctype, fn, ln, excvalue))
else:
sys.__excepthook__(exctype, excvalue, exctraceback)
sys.excepthook = silent_except_hook
class ExchangeRequestError(ZiplineError):
msg = (
'Request failed: {error}'
@@ -34,6 +52,13 @@ class ExchangeTransactionError(ZiplineError):
).strip()
class ExchangeNotFoundError(ZiplineError):
msg = (
'Exchange {exchange_name} not found. Please specify exchanges '
'supported by Catalyst and verify spelling for accuracy.'
).strip()
class ExchangeAuthNotFound(ZiplineError):
msg = (
'Please create an auth.json file containing the api token and key for '
@@ -41,6 +66,13 @@ class ExchangeAuthNotFound(ZiplineError):
).strip()
class ExchangeAuthEmpty(ZiplineError):
msg = (
'Please enter your API token key and secret for exchange {exchange} '
'in the following file: {filename}'
).strip()
class ExchangeSymbolsNotFound(ZiplineError):
msg = (
'Unable to download or find a local copy of symbols.json for exchange '
@@ -54,7 +86,239 @@ class AlgoPickleNotFound(ZiplineError):
).strip()
class InvalidHistoryFrequencyAlias(ZiplineError):
msg = (
'Invalid frequency alias {freq}. Valid suffixes are M (minute) '
'and D (day). For example, these aliases would be valid '
'1M, 5M, 1D.'
).strip()
class InvalidHistoryFrequencyError(ZiplineError):
msg = (
'History frequency {frequency} not supported by the exchange.'
'Frequency {frequency} not supported by the exchange.'
).strip()
class UnsupportedHistoryFrequencyError(ZiplineError):
msg = (
'{exchange} does not support candle frequency {freq}, please choose '
'from: {freqs}.'
).strip()
class InvalidHistoryTimeframeError(ZiplineError):
msg = (
'CCXT timeframe {timeframe} not supported by the exchange.'
).strip()
class MismatchingFrequencyError(ZiplineError):
msg = (
'Bar aggregate frequency {frequency} not compatible with '
'data frequency {data_frequency}.'
).strip()
class InvalidSymbolError(ZiplineError):
msg = (
'Invalid trading pair symbol: {symbol}. '
'Catalyst symbols must follow this convention: '
'[Market Currency]_[Base Currency]. For example: eth_usd, btc_usd, '
'neo_eth, ubq_btc. Error details: {error}'
).strip()
class InvalidOrderStyle(ZiplineError):
msg = (
'Order style {style} not supported by exchange {exchange}.'
).strip()
class CreateOrderError(ZiplineError):
msg = (
'Unable to create order on exchange {exchange} {error}.'
).strip()
class OrderNotFound(ZiplineError):
msg = (
'Order {order_id} not found on exchange {exchange}.'
).strip()
class OrphanOrderError(ZiplineError):
msg = (
'Order {order_id} found in exchange {exchange} but not tracked by '
'the algorithm.'
).strip()
class OrphanOrderReverseError(ZiplineError):
msg = (
'Order {order_id} tracked by algorithm, but not found in exchange '
'{exchange}.'
).strip()
class OrderCancelError(ZiplineError):
msg = (
'Unable to cancel order {order_id} on exchange {exchange} {error}.'
).strip()
class SidHashError(ZiplineError):
msg = (
'Unable to hash sid from symbol {symbol}.'
).strip()
class BaseCurrencyNotFoundError(ZiplineError):
msg = (
'Algorithm base currency {base_currency} not found in account '
'balances on {exchange}: {balances}'
).strip()
class MismatchingBaseCurrencies(ZiplineError):
msg = (
'Unable to trade with base currency {base_currency} when the '
'algorithm uses {algo_currency}.'
).strip()
class MismatchingBaseCurrenciesExchanges(ZiplineError):
msg = (
'Unable to trade with base currency {base_currency} when the '
'exchange {exchange_name} users {exchange_currency}.'
).strip()
class SymbolNotFoundOnExchange(ZiplineError):
"""
Raised when a symbol() call contains a non-existent symbol.
"""
msg = ('Symbol {symbol} not found on exchange {exchange}. '
'Choose from: {supported_symbols}').strip()
class BundleNotFoundError(ZiplineError):
msg = ('Unable to find bundle data for exchange {exchange} and '
'data frequency {data_frequency}.'
'Please ingest some price data.'
'See `catalyst ingest-exchange --help` for details.').strip()
class TempBundleNotFoundError(ZiplineError):
msg = ('Temporary bundle not found in: {path}.').strip()
class EmptyValuesInBundleError(ZiplineError):
msg = ('{name} with end minute {end_minute} has empty rows '
'in ranges: {dates}').strip()
class PricingDataBeforeTradingError(ZiplineError):
msg = ('Pricing data for trading pairs {symbols} on exchange {exchange} '
'starts on {first_trading_day}, but you are either trying to trade '
'or retrieve pricing data on {dt}. Adjust your dates accordingly.'
).strip()
class PricingDataNotLoadedError(ZiplineError):
msg = ('Missing data for {exchange} {symbols} in date range '
'[{start_dt} - {end_dt}]'
'\nPlease run: `catalyst ingest-exchange -x {exchange} -f '
'{data_frequency} -i {symbol_list}`. See catalyst documentation '
'for details.').strip()
class PricingDataValueError(ZiplineError):
msg = ('Unable to retrieve pricing data for {exchange} {symbol} '
'[{start_dt} - {end_dt}]: {error}').strip()
class DataCorruptionError(ZiplineError):
msg = (
'Unable to validate data for {exchange} {symbols} in date range '
'[{start_dt} - {end_dt}]. The data is either corrupted or '
'unavailable. Please try deleting this bundle:'
'\n`catalyst clean-exchange -x {exchange}\n'
'Then, ingest the data again. Please contact the Catalyst team if '
'the issue persists.'
).strip()
class ApiCandlesError(ZiplineError):
msg = (
'Unable to fetch candles from the remote API: {error}.'
).strip()
class NoDataAvailableOnExchange(ZiplineError):
msg = (
'Requested data for trading pair {symbol} is not available on '
'exchange {exchange} '
'in `{data_frequency}` frequency at this time. '
'Check `http://enigma.co/catalyst/status` for market coverage.'
).strip()
class NoValueForField(ZiplineError):
msg = (
'Value not found for field: {field}.'
).strip()
class OrderTypeNotSupported(ZiplineError):
msg = (
'Order type `{order_type}` not currency supported by Catalyst. '
'Please use `limit` or `market` orders only.'
).strip()
class NotEnoughCapitalError(ZiplineError):
msg = (
'Not enough capital on exchange {exchange} for trading. Each '
'exchange should contain at least as much {base_currency} '
'as the specified `capital_base`. The current balance {balance} is '
'lower than the `capital_base`: {capital_base}'
).strip()
class NotEnoughCashError(ZiplineError):
msg = (
'Total {currency} amount on {exchange} is lower than the cash '
'reserved for this algo: {free} < {cash}. While trades can be made on '
'the exchange accounts outside of the algo, exchange must have enough '
'free {currency} to cover the algo cash.'
).strip()
class LastCandleTooEarlyError(ZiplineError):
msg = (
'The trade date of the last candle {last_traded} is before the '
'specified end date minus one candle {end_dt}. Please verify how '
'{exchange} calculates the start date of OHLCV candles.'
).strip()
class TickerNotFoundError(ZiplineError):
msg = (
'Unable to fetch ticker for {symbol} on {exchange}.'
).strip()
class BalanceNotFoundError(ZiplineError):
msg = (
'{currency} not found in account balance on {exchange}: {balances}.'
).strip()
class BalanceTooLowError(ZiplineError):
msg = (
'Balance for {currency} on {exchange} too low: {free} < {amount}. '
'Positions have likely been sold outside of this algorithm. Please '
'add positions to hold a free amount greater than {amount}, or clean '
'the state of this algo and restart.'
).strip()
+67
View File
@@ -0,0 +1,67 @@
from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder
class ExchangeLimitOrder(LimitOrder):
def get_limit_price(self, is_buy):
"""
We may be trading Satoshis with 8 decimals, we cannot round numbers.
Parameters
----------
is_buy: bool
Returns
-------
float
"""
return self.limit_price
class ExchangeStopOrder(StopOrder):
def get_stop_price(self, is_buy):
"""
We may be trading Satoshis with 8 decimals, we cannot round numbers.
Parameters
----------
is_buy: bool
Returns
-------
float
"""
return self.stop_price
class ExchangeStopLimitOrder(StopLimitOrder):
def get_limit_price(self, is_buy):
"""
We may be trading Satoshis with 8 decimals, we cannot round numbers.
Parameters
----------
is_buy: bool
Returns
-------
float
"""
return self.limit_price
def get_stop_price(self, is_buy):
"""
We may be trading Satoshis with 8 decimals, we cannot round numbers.
Parameters
----------
is_buy: bool
Returns
-------
float
"""
return self.stop_price
+55 -11
View File
@@ -1,15 +1,16 @@
import numpy as np
from catalyst.constants import LOG_LEVEL
from catalyst.protocol import Portfolio, Positions, Position
from logbook import Logger
from catalyst.protocol import Portfolio, Positions, Position
log = Logger('ExchangePortfolio')
log = Logger('ExchangePortfolio', level=LOG_LEVEL)
class ExchangePortfolio(Portfolio):
"""
Since the goal is to support multiple exchanges, it makes sense to
include additional stats in the portfolio object.
include additional stats in the portfolio object. This fills the role
of Blotter and Portfolio in live mode.
Instead of relying on the performance tracker, each exchange portfolio
tracks its own holding. This offers a separation between tracking an
@@ -28,12 +29,23 @@ class ExchangePortfolio(Portfolio):
self.positions_value = 0.0
self.open_orders = dict()
def calculate_pnl(self):
log.debug('calculating pnl')
def create_order(self, order):
"""
Create an open order and store in memory.
Parameters
----------
order: Order
"""
log.debug('creating order {}'.format(order.id))
self.open_orders[order.id] = order
open_orders = self.open_orders[order.asset] \
if order.asset is self.open_orders else []
open_orders.append(order)
self.open_orders[order.asset] = open_orders
order_position = self.positions[order.asset] \
if order.asset in self.positions else None
@@ -45,16 +57,40 @@ class ExchangePortfolio(Portfolio):
order_position.amount += order.amount
log.debug('open order added to portfolio')
def _remove_open_order(self, order):
try:
open_orders = self.open_orders[order.asset]
if order in open_orders:
open_orders.remove(order)
except Exception:
raise ValueError(
'unable to clear order not found in open order list.'
)
def execute_order(self, order, transaction):
"""
Update the open orders and positions to apply an executed order.
Unlike with backtesting, we do not need to add slippage and fees.
The executed price includes transaction fees.
Parameters
----------
order: Order
transaction: Transaction
"""
log.debug('executing order {}'.format(order.id))
del self.open_orders[order.id]
self._remove_open_order(order)
order_position = self.positions[order.asset] \
if order.asset in self.positions else None
if order_position is None:
raise ValueError(
'Trying to execute order for a position not held: %s' % order.id
'Trying to execute order for a position not held:'
' {}'.format(order.id)
)
self.capital_used += order.amount * transaction.price
@@ -71,8 +107,16 @@ class ExchangePortfolio(Portfolio):
log.debug('updated portfolio with executed order')
def remove_order(self, order):
"""
Removing an open order.
Parameters
----------
order: Order
"""
log.info('removing cancelled order {}'.format(order.id))
del self.open_orders[order.id]
self._remove_open_order(order)
order_position = self.positions[order.asset] \
if order.asset in self.positions else None
@@ -0,0 +1,177 @@
# Copyright 2015 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from catalyst.constants import LOG_LEVEL
from catalyst.data.us_equity_pricing import BcolzDailyBarReader
from catalyst.errors import NoFurtherDataError
from catalyst.exchange.utils.factory import get_exchange
from catalyst.lib.adjusted_array import AdjustedArray
from catalyst.pipeline.data import DataSet, Column
from catalyst.pipeline.loaders.base import PipelineLoader
from catalyst.utils.calendars import get_calendar
from catalyst.utils.numpy_utils import float64_dtype
from logbook import Logger
from numpy import (
iinfo,
uint32,
)
UINT32_MAX = iinfo(uint32).max
log = Logger('ExchangePriceLoader', level=LOG_LEVEL)
class TradingPairPricing(DataSet):
"""
Dataset representing daily trading prices and volumes.
"""
open = Column(float64_dtype)
high = Column(float64_dtype)
low = Column(float64_dtype)
close = Column(float64_dtype)
volume = Column(float64_dtype)
class ExchangePricingLoader(PipelineLoader):
"""
PipelineLoader for Crypto Pricing data
Delegates loading of baselines and adjustments.
"""
def __init__(self, data_frequency):
cal = get_calendar('OPEN')
if data_frequency == 'daily':
reader = None
all_sessions = cal.all_sessions
elif data_frequency == 'minute':
reader = None
all_sessions = cal.all_minutes
else:
raise ValueError(
'Invalid data frequency: {}'.format(data_frequency)
)
self.data_frequency = data_frequency
self.raw_price_loader = reader
self._columns = TradingPairPricing.columns
self._all_sessions = all_sessions
@classmethod
def from_files(cls, pricing_path):
"""
Create a loader from a bcolz equity pricing dir and a SQLite
adjustments path.
Parameters
----------
pricing_path : str
Path to a bcolz directory written by a BcolzDailyBarWriter.
"""
return cls(
BcolzDailyBarReader(pricing_path),
)
def load_adjusted_array(self, columns, dates, assets, mask):
# load_adjusted_array is called with dates on which the user's algo
# will be shown data, which means we need to return the data that would
# be known at the start of each date. We assume that the latest data
# known on day N is the data from day (N - 1), so we shift all query
# dates back by a day.
start_date, end_date = _shift_dates(
self._all_sessions, dates[0], dates[-1], shift=1,
)
colnames = [c.name for c in columns]
if len(assets) == 0:
raise ValueError(
'Pipeline cannot load data with eligible assets.'
)
exchange_names = []
for asset in assets:
if asset.exchange not in exchange_names:
exchange_names.append(asset.exchange)
exchange = get_exchange(exchange_names[0])
reader = exchange.bundle.get_reader(self.data_frequency)
raw_arrays = reader.load_raw_arrays(
colnames,
start_date,
end_date,
assets,
)
out = {}
for c, c_raw in zip(columns, raw_arrays):
out[c] = AdjustedArray(
c_raw.astype(c.dtype),
mask,
{},
c.missing_value,
)
return out
@property
def columns(self):
return self._columns
def _shift_dates(dates, start_date, end_date, shift):
try:
start = dates.get_loc(start_date)
except KeyError:
if start_date < dates[0]:
raise NoFurtherDataError(
msg=(
"Pipeline Query requested data starting on {query_start}, "
"but first known date is {calendar_start}"
).format(
query_start=str(start_date),
calendar_start=str(dates[0]),
)
)
else:
raise ValueError("Query start %s not in calendar" % start_date)
# Make sure that shifting doesn't push us out of the calendar.
if start < shift:
raise NoFurtherDataError(
msg=(
"Pipeline Query requested data from {shift}"
" days before {query_start}, but first known date is only "
"{start} days earlier."
).format(shift=shift, query_start=start_date, start=start),
)
try:
end = dates.get_loc(end_date)
except KeyError:
if end_date > dates[-1]:
raise NoFurtherDataError(
msg=(
"Pipeline Query requesting data up to {query_end}, "
"but last known date is {calendar_end}"
).format(
query_end=end_date,
calendar_end=dates[-1],
)
)
else:
raise ValueError("Query end %s not in calendar" % end_date)
return dates[start - shift], dates[end - shift]
-133
View File
@@ -1,133 +0,0 @@
import json
import os
import pickle
import urllib
from datetime import date, datetime
from catalyst.exchange.exchange_errors import ExchangeAuthNotFound, \
ExchangeSymbolsNotFound
from catalyst.utils.paths import data_root, ensure_directory
SYMBOLS_URL = 'https://raw.githubusercontent.com/enigmampc/catalyst/' \
'live-trading/catalyst/exchange/symbols/{exchange}.json'
def get_exchange_folder(exchange_name, environ=None):
if not environ:
environ = os.environ
root = data_root(environ)
exchange_folder = os.path.join(root, 'exchanges', exchange_name)
ensure_directory(exchange_folder)
return exchange_folder
def download_exchange_symbols(exchange_name, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
filename = os.path.join(exchange_folder, 'symbols.json')
url = SYMBOLS_URL.format(exchange=exchange_name)
response = urllib.urlretrieve(url=url, filename=filename)
return response
def get_exchange_symbols(exchange_name, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
filename = os.path.join(exchange_folder, 'symbols.json')
if not os.path.isfile(filename):
download_exchange_symbols(exchange_name, environ)
if os.path.isfile(filename):
with open(filename) as data_file:
data = json.load(data_file)
return data
else:
raise ExchangeSymbolsNotFound(
exchange=exchange_name,
filename=filename
)
def get_exchange_auth(exchange_name, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
filename = os.path.join(exchange_folder, 'auth.json')
if os.path.isfile(filename):
with open(filename) as data_file:
data = json.load(data_file)
return data
else:
raise ExchangeAuthNotFound(
exchange=exchange_name,
filename=filename
)
def get_algo_folder(algo_name, environ=None):
if not environ:
environ = os.environ
root = data_root(environ)
algo_folder = os.path.join(root, 'live_algos', algo_name)
ensure_directory(algo_folder)
return algo_folder
def get_algo_object(algo_name, key, environ=None, rel_path=None):
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
filename = os.path.join(folder, key + '.p')
if os.path.isfile(filename):
try:
with open(filename, 'rb') as handle:
return pickle.load(handle)
except Exception as e:
return None
else:
return None
def save_algo_object(algo_name, key, obj, environ=None, rel_path=None):
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
ensure_directory(folder)
filename = os.path.join(folder, key + '.p')
with open(filename, 'wb') as handle:
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
def append_algo_object(algo_name, key, obj, environ=None):
algo_folder = get_algo_folder(algo_name, environ)
filename = os.path.join(algo_folder, key + '.p')
mode = 'a+b' if os.path.isfile(filename) else 'wb'
with open(filename, mode) as handle:
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
def get_exchange_minute_writer_root(exchange_name, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
minute_data_folder = os.path.join(exchange_folder, 'minute_data')
ensure_directory(minute_data_folder)
return minute_data_folder
def perf_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj))
+74
View File
@@ -0,0 +1,74 @@
import pandas as pd
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.utils.stats_utils import prepare_stats
from catalyst.gens.sim_engine import (
BAR,
SESSION_START
)
from logbook import Logger
log = Logger('LiveGraphClock', level=LOG_LEVEL)
class LiveGraphClock(object):
"""Realtime clock for live trading.
This class is a drop-in replacement for
:class:`zipline.gens.sim_engine.MinuteSimulationClock`.
This mixes the clock with a live graph.
Notes
-----
This seemingly awkward approach allows us to run the program using a single
thread. This is important because Matplotlib does not play nice with
multi-threaded environments. Zipline probably does not either.
Matplotlib has a pause() method which is a wrapper around time.sleep()
used in the SimpleClock. The key difference is that users
can still interact with the chart during the pause cycles. This is
what enables us to keep a single thread. This is also why we are not using
the 'animate' callback of Matplotlib. We need to direct access to the
__iter__ method in order to yield events to Zipline.
The :param:`time_skew` parameter represents the time difference between
the exchange and the live trading machine's clock. It's not used currently.
"""
def __init__(self, sessions, context, callback=None,
time_skew=pd.Timedelta('0s')):
self.sessions = sessions
self.time_skew = time_skew
self._last_emit = None
self._before_trading_start_bar_yielded = True
self.context = context
self.callback = callback
def __iter__(self):
from matplotlib import pyplot as plt
yield pd.Timestamp.utcnow(), SESSION_START
while True:
current_time = pd.Timestamp.utcnow()
current_minute = current_time.floor('1T')
if self._last_emit is None or current_minute > self._last_emit:
log.debug('emitting minutely bar: {}'.format(current_minute))
self._last_emit = current_minute
yield current_minute, BAR
recorded_cols = list(self.context.recorded_vars.keys())
df, _ = prepare_stats(
self.context.frame_stats, recorded_cols=recorded_cols
)
self.callback(self.context, df)
else:
# I can't use the "animate" reactive approach here because
# I need to yield from the main loop.
# Workaround: https://stackoverflow.com/a/33050617/814633
plt.pause(1)
@@ -14,24 +14,24 @@
from time import sleep
import pandas as pd
from catalyst.constants import LOG_LEVEL
from catalyst.gens.sim_engine import (
BAR,
SESSION_START,
MINUTE_END,
SESSION_END
SESSION_START
)
from logbook import Logger
log = Logger('ExchangeClock')
log = Logger('ExchangeClock', level=LOG_LEVEL)
class ExchangeClock(object):
class SimpleClock(object):
"""Realtime clock for live trading.
This class is a drop-in replacement for
:class:`zipline.gens.sim_engine.MinuteSimulationClock`.
This is a stripped down version because crypto exchanges run around the clock.
This is a stripped down version because crypto exchanges run
around the clock.
The :param:`time_skew` parameter represents the time difference between
the Broker and the live trading machine's clock.
-110
View File
@@ -1,110 +0,0 @@
{
"btcusd": {
"symbol": "btc_usd",
"start_date": "2010-01-01"
},
"ltcusd": {
"symbol": "ltc_usd",
"start_date": "2010-01-01"
},
"ltcbtc": {
"symbol": "ltc_btc",
"start_date": "2010-01-01"
},
"ethusd": {
"symbol": "eth_usd",
"start_date": "2010-01-01"
},
"ethbtc": {
"symbol": "eth_btc",
"start_date": "2010-01-01"
},
"etcbtc": {
"symbol": "etc_btc",
"start_date": "2010-01-01"
},
"etcusd": {
"symbol": "etc_usd",
"start_date": "2010-01-01"
},
"rrtusd": {
"symbol": "rrt_usd",
"start_date": "2010-01-01"
},
"rrtbtc": {
"symbol": "rrt_btc",
"start_date": "2010-01-01"
},
"zecusd": {
"symbol": "zec_usd",
"start_date": "2010-01-01"
},
"zecbtc": {
"symbol": "zec_btc",
"start_date": "2010-01-01"
},
"xmrusd": {
"symbol": "xmr_usd",
"start_date": "2010-01-01"
},
"xmrbtc": {
"symbol": "xmr_btc",
"start_date": "2010-01-01"
},
"dshusd": {
"symbol": "dsh_usd",
"start_date": "2010-01-01"
},
"dshbtc": {
"symbol": "dsh_btc",
"start_date": "2010-01-01"
},
"bccbtc": {
"symbol": "bcc_btc",
"start_date": "2010-01-01"
},
"bcubtc": {
"symbol": "bcu_btc",
"start_date": "2010-01-01"
},
"bccusd": {
"symbol": "bcc_usd",
"start_date": "2010-01-01"
},
"bcuusd": {
"symbol": "bcu_usd",
"start_date": "2010-01-01"
},
"xrpusd": {
"symbol": "xrp_usd",
"start_date": "2010-01-01"
},
"xrpbtc": {
"symbol": "xrp_btc",
"start_date": "2010-01-01"
},
"iotusd": {
"symbol": "iot_usd",
"start_date": "2010-01-01"
},
"iotbtc": {
"symbol": "iot_btc",
"start_date": "2010-01-01"
},
"ioteth": {
"symbol": "iot_eth",
"start_date": "2010-01-01"
},
"eosusd": {
"symbol": "eos_usd",
"start_date": "2010-01-01"
},
"eosbtc": {
"symbol": "eos_btc",
"start_date": "2010-01-01"
},
"eoseth": {
"symbol": "eos_eth",
"start_date": "2010-01-01"
}
}
+158
View File
@@ -0,0 +1,158 @@
import os
import tarfile
from datetime import datetime
import numpy as np
import pandas as pd
from catalyst.data.bundles.core import download_without_progress
from catalyst.exchange.utils.exchange_utils import get_exchange_bundles_folder
import os
import tarfile
from datetime import datetime
import numpy as np
import pandas as pd
from catalyst.data.bundles.core import download_without_progress
from catalyst.exchange.utils.exchange_utils import get_exchange_bundles_folder
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
API_URL = 'http://data.enigma.co/api/v1'
def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
"""
Download and extract a bcolz bundle.
Parameters
----------
exchange_name: str
symbol: str
data_frequency: str
period: str
Returns
-------
str
Filename: bitfinex-daily-neo_eth-2017-10.tar.gz
"""
root = get_exchange_bundles_folder(exchange_name)
name = '{exchange}-{frequency}-{symbol}-{period}'.format(
exchange=exchange_name,
frequency=data_frequency,
symbol=symbol,
period=period
)
path = os.path.join(root, name)
if not os.path.isdir(path):
url = 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' \
'exchange-{exchange}/{name}.tar.gz'.format(
exchange=exchange_name,
name=name)
bytes = download_without_progress(url)
with tarfile.open('r', fileobj=bytes) as tar:
tar.extractall(path)
return path
def get_df_from_arrays(arrays, periods):
"""
A DataFrame from the specified OHCLV arrays.
Parameters
----------
arrays: Object
periods: DateTimeIndex
Returns
-------
DataFrame
"""
ohlcv = dict()
for index, field in enumerate(
['open', 'high', 'low', 'close', 'volume']):
ohlcv[field] = arrays[index].flatten()
df = pd.DataFrame(
data=ohlcv,
index=periods
)
return df
def range_in_bundle(asset, start_dt, end_dt, reader):
"""
Evaluate whether price data of an asset is included has been ingested in
the exchange bundle for the given date range.
Parameters
----------
asset: TradingPair
start_dt: datetime
end_dt: datetime
reader: BcolzBarMinuteReader
Returns
-------
bool
"""
has_data = True
dates = [start_dt, end_dt]
while dates and has_data:
try:
dt = dates.pop(0)
close = reader.get_value(asset.sid, dt, 'close')
if np.isnan(close):
has_data = False
except Exception:
has_data = False
return has_data
def get_assets(exchange, include_symbols, exclude_symbols):
"""
Get assets from an exchange, including or excluding the specified
symbols.
Parameters
----------
exchange: Exchange
include_symbols: str
exclude_symbols: str
Returns
-------
list[TradingPair]
"""
if include_symbols is not None:
include_symbols_list = include_symbols.split(',')
return exchange.get_assets(include_symbols_list)
else:
all_assets = exchange.get_assets()
if exclude_symbols is not None:
exclude_symbols_list = exclude_symbols.split(',')
assets = []
for asset in all_assets:
if asset.symbol not in exclude_symbols_list:
assets.append(asset)
return assets
else:
return all_assets
+327
View File
@@ -0,0 +1,327 @@
import calendar
import re
from datetime import datetime, timedelta, date
import pandas as pd
import pytz
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
InvalidHistoryFrequencyAlias
def get_date_from_ms(ms):
"""
The date from the number of miliseconds from the epoch.
Parameters
----------
ms: int
Returns
-------
datetime
"""
return datetime.fromtimestamp(ms / 1000.0)
def get_seconds_from_date(date):
"""
The number of seconds from the epoch.
Parameters
----------
date: datetime
Returns
-------
int
"""
epoch = datetime.utcfromtimestamp(0)
epoch = epoch.replace(tzinfo=pytz.UTC)
return int((date - epoch).total_seconds())
def get_delta(periods, data_frequency):
"""
Get a time delta based on the specified data frequency.
Parameters
----------
periods: int
data_frequency: str
Returns
-------
timedelta
"""
return timedelta(minutes=periods) \
if data_frequency == 'minute' else timedelta(days=periods)
def get_periods_range(freq, start_dt=None, end_dt=None, periods=None):
"""
Get a date range for the specified parameters.
Parameters
----------
start_dt: datetime
end_dt: datetime
freq: str
Returns
-------
DateTimeIndex
"""
if freq == 'minute':
freq = 'T'
elif freq == 'daily':
freq = 'D'
if start_dt is not None and end_dt is not None and periods is None:
return pd.date_range(start_dt, end_dt, freq=freq)
elif periods is not None and (start_dt is not None or end_dt is not None):
_, unit_periods, unit, _ = get_frequency(freq)
adj_periods = periods * unit_periods
# TODO: standardize time aliases to avoid any mapping
unit = 'd' if unit == 'D' else 'm'
delta = pd.Timedelta(adj_periods, unit)
if start_dt is not None:
return pd.date_range(
start=start_dt,
end=start_dt + delta,
freq=freq,
closed='left',
)
else:
return pd.date_range(
start=end_dt - delta,
end=end_dt,
freq=freq,
)
else:
raise ValueError(
'Choose only two parameters between start_dt, end_dt '
'and periods.'
)
def get_periods(start_dt, end_dt, freq):
"""
The number of periods in the specified range.
Parameters
----------
start_dt: datetime
end_dt: datetime
freq: str
Returns
-------
int
"""
return len(get_periods_range(start_dt=start_dt, end_dt=end_dt, freq=freq))
def get_start_dt(end_dt, bar_count, data_frequency, include_first=True):
"""
The start date based on specified end date and data frequency.
Parameters
----------
end_dt: datetime
bar_count: int
data_frequency: str
include_first
Returns
-------
datetime
"""
periods = bar_count
if periods > 1:
delta = get_delta(periods, data_frequency)
start_dt = end_dt - delta
if not include_first:
start_dt += get_delta(1, data_frequency)
else:
start_dt = end_dt
return start_dt
def get_period_label(dt, data_frequency):
"""
The period label for the specified date and frequency.
Parameters
----------
dt: datetime
data_frequency: str
Returns
-------
str
"""
if data_frequency == 'minute':
return '{}-{:02d}'.format(dt.year, dt.month)
else:
return '{}'.format(dt.year)
def get_month_start_end(dt, first_day=None, last_day=None):
"""
The first and last day of the month for the specified date.
Parameters
----------
dt: datetime
first_day: datetime
last_day: datetime
Returns
-------
datetime, datetime
"""
month_range = calendar.monthrange(dt.year, dt.month)
if first_day:
month_start = first_day
else:
month_start = pd.to_datetime(datetime(
dt.year, dt.month, 1, 0, 0, 0, 0
), utc=True)
if last_day:
month_end = last_day
else:
month_end = pd.to_datetime(datetime(
dt.year, dt.month, month_range[1], 23, 59, 0, 0
), utc=True)
if month_end > pd.Timestamp.utcnow():
month_end = pd.Timestamp.utcnow().floor('1D')
return month_start, month_end
def get_year_start_end(dt, first_day=None, last_day=None):
"""
The first and last day of the year for the specified date.
Parameters
----------
dt: datetime
first_day: datetime
last_day: datetime
Returns
-------
datetime, datetime
"""
year_start = first_day if first_day \
else pd.to_datetime(date(dt.year, 1, 1), utc=True)
year_end = last_day if last_day \
else pd.to_datetime(date(dt.year, 12, 31), utc=True)
if year_end > pd.Timestamp.utcnow():
year_end = pd.Timestamp.utcnow().floor('1D')
return year_start, year_end
def get_frequency(freq, data_frequency=None):
"""
Get the frequency parameters.
Notes
-----
We're trying to use Pandas convention for frequency aliases.
Parameters
----------
freq: str
data_frequency: str
Returns
-------
str, int, str, str
"""
if data_frequency is None:
data_frequency = 'daily' if freq.upper().endswith('D') else 'minute'
if freq == 'minute':
unit = 'T'
candle_size = 1
elif freq == 'daily':
unit = 'D'
candle_size = 1
else:
freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I)
if freq_match:
candle_size = int(freq_match.group(1)) if freq_match.group(1) \
else 1
unit = freq_match.group(2)
else:
raise InvalidHistoryFrequencyError(frequency=freq)
# TODO: some exchanges support H and W frequencies but not bundles
# Find a way to pass-through these parameters to exchanges
# but resample from minute or daily in backtest mode
# see catalyst/exchange/ccxt/ccxt_exchange.py:242 for mapping between
# Pandas offet aliases (used by Catalyst) and the CCXT timeframes
if unit.lower() == 'd':
unit = 'D'
alias = '{}D'.format(candle_size)
if data_frequency == 'minute':
data_frequency = 'daily'
elif unit.lower() == 'm' or unit == 'T':
unit = 'T'
alias = '{}T'.format(candle_size)
if data_frequency == 'daily':
data_frequency = 'minute'
# elif unit.lower() == 'h':
# candle_size = candle_size * 60
#
# alias = '{}T'.format(candle_size)
# if data_frequency == 'daily':
# data_frequency = 'minute'
else:
raise InvalidHistoryFrequencyAlias(freq=freq)
return alias, candle_size, unit, data_frequency
def from_ms_timestamp(ms):
return pd.to_datetime(ms, unit='ms', utc=True)
def get_epoch():
return pd.to_datetime('1970-1-1', utc=True)
+663
View File
@@ -0,0 +1,663 @@
import hashlib
import json
import os
import pickle
import shutil
from datetime import date, datetime
import pandas as pd
from catalyst.assets._assets import TradingPair
from six import string_types
from six.moves.urllib import request
from catalyst.constants import DATE_FORMAT, SYMBOLS_URL
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound
from catalyst.exchange.utils.serialization_utils import ExchangeJSONEncoder, \
ExchangeJSONDecoder
from catalyst.utils.paths import data_root, ensure_directory, \
last_modified_time
def get_sid(symbol):
"""
Create a sid by hashing the symbol of a currency pair.
Parameters
----------
symbol: str
Returns
-------
int
The resulting sid.
"""
sid = int(
hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16
) % 10 ** 6
return sid
def get_exchange_folder(exchange_name, environ=None):
"""
The root path of an exchange folder.
Parameters
----------
exchange_name: str
environ:
Returns
-------
str
"""
if not environ:
environ = os.environ
root = data_root(environ)
exchange_folder = os.path.join(root, 'exchanges', exchange_name)
ensure_directory(exchange_folder)
return exchange_folder
def is_blacklist(exchange_name, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
filename = os.path.join(exchange_folder, 'blacklist.txt')
return os.path.exists(filename)
def get_exchange_symbols_filename(exchange_name, is_local=False, environ=None):
"""
The absolute path of the exchange's symbol.json file.
Parameters
----------
exchange_name:
environ:
Returns
-------
str
"""
name = 'symbols.json' if not is_local else 'symbols_local.json'
exchange_folder = get_exchange_folder(exchange_name, environ)
return os.path.join(exchange_folder, name)
def download_exchange_symbols(exchange_name, environ=None):
"""
Downloads the exchange's symbols.json from the repository.
Parameters
----------
exchange_name: str
environ:
Returns
-------
str
"""
filename = get_exchange_symbols_filename(exchange_name)
url = SYMBOLS_URL.format(exchange=exchange_name)
response = request.urlretrieve(url=url, filename=filename)
return response
def get_exchange_symbols(exchange_name, is_local=False, environ=None):
"""
The de-serialized content of the exchange's symbols.json.
Parameters
----------
exchange_name: str
is_local: bool
environ:
Returns
-------
Object
"""
filename = get_exchange_symbols_filename(exchange_name, is_local)
if not is_local and (not os.path.isfile(filename) or pd.Timedelta(
pd.Timestamp('now', tz='UTC') - last_modified_time(
filename)).days > 1):
try:
download_exchange_symbols(exchange_name, environ)
except Exception as e:
pass
if os.path.isfile(filename):
with open(filename) as data_file:
try:
data = json.load(data_file, cls=ExchangeJSONDecoder)
return data
except ValueError:
return dict()
else:
raise ExchangeSymbolsNotFound(
exchange=exchange_name,
filename=filename
)
def save_exchange_symbols(exchange_name, assets, is_local=False, environ=None):
"""
Save assets into an exchange_symbols file.
Parameters
----------
exchange_name: str
assets: list[dict[str, object]]
is_local: bool
environ
Returns
-------
"""
asset_dicts = dict()
for symbol in assets:
asset_dicts[symbol] = assets[symbol].to_dict()
filename = get_exchange_symbols_filename(
exchange_name, is_local, environ
)
with open(filename, 'wt') as handle:
json.dump(asset_dicts, handle, indent=4, default=symbols_serial)
def get_symbols_string(assets):
"""
A concatenated string of symbols from a list of assets.
Parameters
----------
assets: list[TradingPair]
Returns
-------
str
"""
array = [assets] if isinstance(assets, TradingPair) else assets
return ', '.join([asset.symbol for asset in array])
def get_exchange_auth(exchange_name, alias=None, environ=None):
"""
The de-serialized contend of the exchange's auth.json file.
Parameters
----------
exchange_name: str
environ:
Returns
-------
Object
"""
exchange_folder = get_exchange_folder(exchange_name, environ)
name = 'auth' if alias is None else alias
filename = os.path.join(exchange_folder, '{}.json'.format(name))
if os.path.isfile(filename):
with open(filename) as data_file:
data = json.load(data_file)
return data
else:
data = dict(name=exchange_name, key='', secret='')
with open(filename, 'w') as f:
json.dump(data, f, sort_keys=False, indent=2,
separators=(',', ':'))
return data
def delete_algo_folder(algo_name, environ=None):
"""
Delete the folder containing the algo state.
Parameters
----------
algo_name: str
environ:
Returns
-------
str
"""
folder = get_algo_folder(algo_name, environ)
shutil.rmtree(folder)
def get_algo_folder(algo_name, environ=None):
"""
The algorithm root folder of the algorithm.
Parameters
----------
algo_name: str
environ:
Returns
-------
str
"""
if not environ:
environ = os.environ
root = data_root(environ)
algo_folder = os.path.join(root, 'live_algos', algo_name)
ensure_directory(algo_folder)
return algo_folder
def get_algo_object(algo_name, key, environ=None, rel_path=None, how='pickle'):
"""
The de-serialized object of the algo name and key.
Parameters
----------
algo_name: str
key: str
environ:
rel_path: str
Returns
-------
Object
"""
if algo_name is None:
return None
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
name = '{}.p'.format(key) if how == 'pickle' else '{}.json'.format(key)
filename = os.path.join(folder, name)
if os.path.isfile(filename):
if how == 'pickle':
with open(filename, 'rb') as handle:
return pickle.load(handle)
else:
with open(filename) as data_file:
data = json.load(data_file, cls=ExchangeJSONDecoder)
return data
else:
return None
def save_algo_object(algo_name, key, obj, environ=None, rel_path=None,
how='pickle'):
"""
Serialize and save an object by algo name and key.
Parameters
----------
algo_name: str
key: str
obj: Object
environ:
rel_path: str
"""
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
ensure_directory(folder)
if how == 'json':
filename = os.path.join(folder, '{}.json'.format(key))
with open(filename, 'wt') as handle:
json.dump(obj, handle, indent=4, cls=ExchangeJSONEncoder)
else:
filename = os.path.join(folder, '{}.p'.format(key))
with open(filename, 'wb') as handle:
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
def get_algo_df(algo_name, key, environ=None, rel_path=None):
"""
The de-serialized DataFrame of an algo name and key.
Parameters
----------
algo_name: str
key: str
environ:
rel_path: str
Returns
-------
DataFrame
"""
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
filename = os.path.join(folder, key + '.csv')
if os.path.isfile(filename):
try:
with open(filename, 'rb') as handle:
return pd.read_csv(handle, index_col=0, parse_dates=True)
except IOError:
return pd.DataFrame()
else:
return pd.DataFrame()
def save_algo_df(algo_name, key, df, environ=None, rel_path=None):
"""
Serialize to csv and save a DataFrame by algo name and key.
Parameters
----------
algo_name: str
key: str
df: pd.DataFrame
environ:
rel_path: str
"""
folder = get_algo_folder(algo_name, environ)
if rel_path is not None:
folder = os.path.join(folder, rel_path)
ensure_directory(folder)
filename = os.path.join(folder, key + '.csv')
with open(filename, 'wt') as handle:
df.to_csv(handle, encoding='UTF_8')
def get_exchange_minute_writer_root(exchange_name, environ=None):
"""
The minute writer folder for the exchange.
Parameters
----------
exchange_name: str
environ:
Returns
-------
BcolzExchangeBarWriter
"""
exchange_folder = get_exchange_folder(exchange_name, environ)
minute_data_folder = os.path.join(exchange_folder, 'minute_data')
ensure_directory(minute_data_folder)
return minute_data_folder
def get_exchange_bundles_folder(exchange_name, environ=None):
"""
The temp folder for bundle downloads by algo name.
Parameters
----------
exchange_name: str
environ:
Returns
-------
str
"""
exchange_folder = get_exchange_folder(exchange_name, environ)
temp_bundles = os.path.join(exchange_folder, 'temp_bundles')
ensure_directory(temp_bundles)
return temp_bundles
def has_bundle(exchange_name, data_frequency, environ=None):
exchange_folder = get_exchange_folder(exchange_name, environ)
folder_name = '{}_bundle'.format(data_frequency.lower())
folder = os.path.join(exchange_folder, folder_name)
return os.path.isdir(folder)
def symbols_serial(obj):
"""
JSON serializer for objects not serializable by default json code
Parameters
----------
obj: Object
Returns
-------
str
"""
if isinstance(obj, (datetime, date)):
return obj.floor('1D').strftime(DATE_FORMAT)
raise TypeError("Type %s not serializable" % type(obj))
def perf_serial(obj):
"""
JSON serializer for objects not serializable by default json code
Parameters
----------
obj: Object
Returns
-------
str
"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj))
def get_common_assets(exchanges):
"""
The assets available in all specified exchanges.
Parameters
----------
exchanges: list[Exchange]
Returns
-------
list[TradingPair]
"""
symbols = []
for exchange_name in exchanges:
s = [asset.symbol for asset in exchanges[exchange_name].get_assets()]
symbols.append(s)
inter_symbols = set.intersection(*map(set, symbols))
assets = []
for symbol in inter_symbols:
for exchange_name in exchanges:
asset = exchanges[exchange_name].get_asset(symbol)
assets.append(asset)
return assets
def resample_history_df(df, freq, field):
"""
Resample the OHCLV DataFrame using the specified frequency.
Parameters
----------
df: DataFrame
freq: str
field: str
Returns
-------
DataFrame
"""
if field == 'open':
agg = 'first'
elif field == 'high':
agg = 'max'
elif field == 'low':
agg = 'min'
elif field == 'close':
agg = 'last'
elif field == 'volume':
agg = 'sum'
else:
raise ValueError('Invalid field.')
resampled_df = df.resample(freq).agg(agg)
return resampled_df
def mixin_market_params(exchange_name, params, market):
"""
Applies a CCXT market dict to parameters of TradingPair init.
Parameters
----------
params: dict[Object]
market: dict[Object]
Returns
-------
"""
# TODO: make this more externalized / configurable
if 'lot' in market:
params['min_trade_size'] = market['lot']
params['lot'] = market['lot']
if exchange_name == 'bitfinex':
params['maker'] = 0.001
params['taker'] = 0.002
elif 'maker' in market and 'taker' in market \
and market['maker'] is not None and market['taker'] is not None:
params['maker'] = market['maker']
params['taker'] = market['taker']
else:
# TODO: default commission, make configurable
params['maker'] = 0.0015
params['taker'] = 0.0025
info = market['info'] if 'info' in market else None
if info:
if 'minimum_order_size' in info:
params['min_trade_size'] = float(info['minimum_order_size'])
if 'lot' not in params:
params['lot'] = params['min_trade_size']
def group_assets_by_exchange(assets):
exchange_assets = dict()
for asset in assets:
if asset.exchange not in exchange_assets:
exchange_assets[asset.exchange] = list()
exchange_assets[asset.exchange].append(asset)
return exchange_assets
def get_catalyst_symbol(market_or_symbol):
"""
The Catalyst symbol.
Parameters
----------
market_or_symbol
Returns
-------
"""
if isinstance(market_or_symbol, string_types):
parts = market_or_symbol.split('/')
return '{}_{}'.format(parts[0].lower(), parts[1].lower())
else:
return '{}_{}'.format(
market_or_symbol['base'].lower(),
market_or_symbol['quote'].lower(),
)
def save_asset_data(folder, df, decimals=8):
symbols = df.index.get_level_values('symbol')
for symbol in symbols:
symbol_df = df.loc[(symbols == symbol)] # Type: pd.DataFrame
filename = os.path.join(folder, '{}.csv'.format(symbol))
if os.path.exists(filename):
print_headers = False
else:
print_headers = True
with open(filename, 'a') as f:
symbol_df.to_csv(
path_or_buf=f,
header=print_headers,
float_format='%.{}f'.format(decimals),
)
def get_candles_df(candles, field, freq, bar_count, end_dt,
previous_value=None):
all_series = dict()
for asset in candles:
periods = pd.date_range(end=end_dt, periods=bar_count, freq=freq)
dates = [candle['last_traded'] for candle in candles[asset]]
values = [candle[field] for candle in candles[asset]]
series = pd.Series(values, index=dates)
series = series.reindex(
periods,
method='ffill',
fill_value=previous_value,
)
series.sort_index(inplace=True)
all_series[asset] = series
df = pd.DataFrame(all_series)
df.dropna(inplace=True)
return df
+97
View File
@@ -0,0 +1,97 @@
import os
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
from catalyst.exchange.exchange import Exchange
from catalyst.exchange.exchange_errors import ExchangeAuthEmpty
from catalyst.exchange.utils.exchange_utils import get_exchange_auth, \
get_exchange_folder, is_blacklist
from logbook import Logger
log = Logger('factory', level=LOG_LEVEL)
exchange_cache = dict()
def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
skip_init=False, auth_alias=None):
key = (exchange_name, base_currency)
if key in exchange_cache:
return exchange_cache[key]
exchange_auth = get_exchange_auth(exchange_name, alias=auth_alias)
has_auth = (exchange_auth['key'] != '' and exchange_auth['secret'] != '')
if must_authenticate and not has_auth:
raise ExchangeAuthEmpty(
exchange=exchange_name.title(),
filename=os.path.join(
get_exchange_folder(exchange_name), 'auth.json'
)
)
exchange = CCXT(
exchange_name=exchange_name,
key=exchange_auth['key'],
secret=exchange_auth['secret'],
base_currency=base_currency,
)
exchange_cache[key] = exchange
if not skip_init:
exchange.init()
return exchange
def get_exchanges(exchange_names):
exchanges = dict()
for exchange_name in exchange_names:
exchanges[exchange_name] = get_exchange(exchange_name)
return exchanges
def find_exchanges(features=None, skip_blacklist=True, is_authenticated=False,
base_currency=None):
"""
Find exchanges filtered by a list of feature.
Parameters
----------
features: str
The list of features.
skip_blacklist: bool
is_authenticated: bool
base_currency: bool
Returns
-------
list[Exchange]
"""
exchange_names = CCXT.find_exchanges(features, is_authenticated)
exchanges = []
for exchange_name in exchange_names:
if skip_blacklist and is_blacklist(exchange_name):
continue
exchange = get_exchange(
exchange_name=exchange_name,
skip_init=True,
base_currency=base_currency,
)
if features is not None:
if 'dailyBundle' in features \
and not exchange.has_bundle('daily'):
continue
elif 'minuteBundle' in features \
and not exchange.has_bundle('minute'):
continue
exchanges.append(exchange)
return exchanges
+131
View File
@@ -0,0 +1,131 @@
import matplotlib.dates as mdates
import pandas as pd
from catalyst.exchange.exchange_errors import \
MismatchingBaseCurrenciesExchanges
fmt = mdates.DateFormatter('%Y-%m-%d %H:%M')
def format_ax(ax):
"""
Trying to assign reasonable parameters to the time axis.
Parameters
----------
ax:
"""
# TODO: room for improvement
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(fmt)
locator = mdates.HourLocator(interval=4)
locator.MAXTICKS = 5000
ax.xaxis.set_minor_locator(locator)
datemin = pd.Timestamp.utcnow()
ax.set_xlim(datemin)
ax.grid(True)
def set_legend(ax):
"""
Set legend on the chart.
Parameters
----------
ax
"""
ax.legend(loc='upper left', ncol=1, fontsize=10, numpoints=1)
def draw_pnl(ax, df):
"""
Draw p&l line on the chart.
"""
ax.clear()
ax.set_title('Performance')
index = df.index.unique()
dt = index.get_level_values(level=0)
pnl = index.get_level_values(level=4)
ax.plot(
dt, pnl, '-',
color='green',
linewidth=1.0,
label='Performance'
)
def perc(val):
return '{:2f}'.format(val)
ax.format_ydata = perc
set_legend(ax)
format_ax(ax)
def draw_custom_signals(ax, df):
"""
Draw custom signals on the chart.
"""
colors = ['blue', 'green', 'red', 'black', 'orange', 'yellow', 'pink']
ax.clear()
ax.set_title('Custom Signals')
for index, column in enumerate(df.columns.values.tolist()):
ax.plot(df.index, df[column], '-',
color=colors[index],
linewidth=1.0,
label=column
)
set_legend(ax)
format_ax(ax)
def draw_exposure(ax, df, context):
"""
Draw exposure line on the chart.
"""
# TODO: list exchanges in graph
base_currency = None
positions = []
for exchange_name in context.exchanges:
exchange = context.exchanges[exchange_name]
if not base_currency:
base_currency = exchange.base_currency
elif base_currency != exchange.base_currency:
raise MismatchingBaseCurrenciesExchanges(
base_currency=base_currency,
exchange_name=exchange.name,
exchange_currency=exchange.base_currency
)
positions += exchange.portfolio.positions
ax.clear()
ax.set_title('Exposure')
ax.plot(df.index, df['base_currency'], '-',
color='green',
linewidth=1.0,
label='Base Currency: {}'.format(base_currency.upper())
)
symbols = []
for position in positions:
symbols.append(position.symbol)
ax.plot(df.index, df['long_exposure'], '-',
color='blue',
linewidth=1.0,
label='Long Exposure: {}'.format(', '.join(symbols).upper()))
set_legend(ax)
format_ax(ax)
@@ -0,0 +1,69 @@
import json
import re
from json import JSONEncoder
import pandas as pd
from catalyst.constants import DATE_TIME_FORMAT
from six import string_types
class ExchangeJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, pd.Timestamp):
return obj.strftime(DATE_TIME_FORMAT)
# Let the base class default method raise the TypeError
return JSONEncoder.default(self, obj)
class ExchangeJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(
self, object_hook=self.object_hook, *args, **kwargs
)
def recursive_iter(self, obj):
if isinstance(obj, dict):
for key, value in obj.items():
match = isinstance(value, string_types) and re.search(
r'(\d{4}-\d{2}-\d{2}).*', value
)
if match:
try:
obj[key] = pd.to_datetime(value, utc=True)
except ValueError:
pass
elif any(isinstance(obj, t) for t in (list, tuple)):
for item in obj:
self.recursive_iter(item)
def object_hook(self, obj):
self.recursive_iter(obj)
return obj
def portfolio_to_dict(portfolio):
positions = []
for asset in portfolio.positions:
p = portfolio.positions[asset] # Type: Position
position = dict(
symbol=asset.symbol,
exchange=asset.exchange,
amount=p.amount,
cost_basis=p.cost_basis,
last_sale_price=p.last_sale_price,
last_sale_date=p.last_sale_date,
)
positions.append(position)
portfolio_dict = vars(portfolio)
portfolio_dict['positions'] = positions
return portfolio_dict
def portfolio_from_dict(self, portfolio_data):
from catalyst.protocol import Portfolio
return Portfolio()
+486
View File
@@ -0,0 +1,486 @@
import copy
import csv
import json
import numbers
import os
import time
import numpy as np
import pandas as pd
from catalyst.assets._assets import TradingPair
from catalyst.exchange.utils.exchange_utils import get_algo_folder
from catalyst.utils.paths import data_root, ensure_directory
from operator import itemgetter
s3_conn = []
mailgun = []
def trend_direction(series):
if series[-1] is np.nan or series[-1] is np.nan:
return None
if series[-1] > series[-2]:
return 'up'
else:
return 'down'
def crossover(source, target):
"""
The `x`-series is defined as having crossed over `y`-series if the value
of `x` is greater than the value of `y` and the value of `x` was less than
the value of `y` on the bar immediately preceding the current bar.
Parameters
----------
source: Series
target: Series
Returns
-------
bool
"""
if isinstance(target, numbers.Number):
if source[-1] is np.nan or source[-2] is np.nan \
or target is np.nan:
return False
if source[-1] >= target > source[-2]:
return True
else:
return False
else:
if source[-1] is np.nan or source[-2] is np.nan \
or target[-1] is np.nan or target[-2] is np.nan:
return False
if source[-1] > target[-1] and source[-2] < target[-2]:
return True
else:
return False
def crossunder(source, target):
"""
The `x`-series is defined as having crossed under `y`-series if the value
of `x` is less than the value of `y` and the value of `x` was greater than
the value of `y` on the bar immediately preceding the current bar.
Parameters
----------
source: Series
target: Series
Returns
-------
bool
"""
if isinstance(target, numbers.Number):
if source[-1] is np.nan or source[-2] is np.nan \
or target is np.nan:
return False
if source[-1] < target <= source[-2]:
return True
else:
return False
else:
if source[-1] is np.nan or source[-2] is np.nan \
or target[-1] is np.nan or target[-2] is np.nan:
return False
if source[-1] < target[-1] and source[-2] >= target[-2]:
return True
else:
return False
def vwap(df):
"""
Volume-weighted average price (VWAP) is a ratio generally used by
institutional investors and mutual funds to make buys and sells so as not
to disturb the market prices with large orders. It is the average share
price of a stock weighted against its trading volume within a particular
time frame, generally one day.
Read more: Volume Weighted Average Price - VWAP
https://www.investopedia.com/terms/v/vwap.asp#ixzz4xt922daE
Parameters
----------
df: pd.DataFrame
Returns
-------
"""
if 'close' not in df.columns or 'volume' not in df.columns:
raise ValueError('price data must include `volume` and `close`')
vol_sum = np.nansum(df['volume'].values)
try:
ret = np.nansum(df['close'].values * df['volume'].values) / vol_sum
except ZeroDivisionError:
ret = np.nan
return ret
def set_position_row(row, asset, asset_values=list()):
"""
Apply the position data as individual columns.
Parameters
----------
row: dict[str, Object]
asset: TradingPair
asset_values: list[str]
If a recorded_col contains a tuple which first value is an asset
matching a position, its value will be displayed with the
position and not in the index.
Returns
-------
"""
asset_cols = ['symbol']
row['symbol'] = asset.symbol
position = next((p for p in row['positions'] if p['sid'] == asset), None)
columns = ['amount', 'cost_basis', 'last_sale_price']
for column in columns:
if position is not None:
row[column] = position[column]
else:
row[column] = 0
asset_cols.append(column)
values = asset_values[asset] if asset in asset_values else list()
for column in values:
row[column] = values[column]
asset_cols.append(column)
return asset_cols
def prepare_stats(stats, recorded_cols=list()):
"""
Prepare the stats DataFrame for user-friendly output.
Parameters
----------
stats: list[Object]
recorded_cols: list[str]
Returns
-------
"""
asset_cols = list()
stats = copy.deepcopy(stats)
# Using a copy since we are adding rows inside the loop.
for row_index, row_data in enumerate(list(stats)):
assets = [p['sid'] for p in row_data['positions']]
asset_values = dict()
if recorded_cols is not None:
for column in recorded_cols[:]:
value = row_data[column]
if isinstance(value, pd.Series):
value = value.to_dict()
if type(value) is dict:
for asset in value:
if not isinstance(asset, TradingPair):
break
if asset not in assets:
assets.append(asset)
if asset not in asset_values:
asset_values[asset] = dict()
asset_values[asset][column] = value[asset]
if len(assets) == 1:
row = stats[row_index]
asset_cols = set_position_row(row, assets[0], asset_values)
elif len(assets) > 1:
for asset_index, asset in enumerate(assets):
if asset_index > 0:
row = copy.deepcopy(row_data)
stats.append(row)
else:
row = stats[row_index]
asset_cols = set_position_row(row, assets[asset_index],
asset_values)
df = pd.DataFrame(stats)
index_cols = [
'period_close', 'starting_cash', 'ending_cash', 'portfolio_value',
'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions',
]
# Removing the asset specific entries
if recorded_cols is not None:
recorded_cols = [x for x in recorded_cols if x not in asset_cols]
for column in recorded_cols:
index_cols.append(column)
df['orders'] = df['orders'].apply(lambda orders: len(orders))
df['transactions'] = df['transactions'].apply(
lambda transactions: len(transactions)
)
if asset_cols:
columns = asset_cols
df.set_index(index_cols, drop=True, inplace=True)
else:
columns = index_cols
columns.remove('period_close')
df.set_index('period_close', drop=False, inplace=True)
df.dropna(axis=1, how='all', inplace=True)
df.sort_index(axis=0, level=0, inplace=True)
return df, columns
def set_print_settings():
pd.set_option('display.expand_frame_repr', False)
pd.set_option('precision', 8)
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 1000)
def get_pretty_stats(stats, recorded_cols=None, num_rows=10, show_tail=True):
"""
Format and print the last few rows of a statistics DataFrame.
See the pyfolio project for the data structure.
Parameters
----------
stats: list[Object]
An array of statistics for the period.
num_rows: int
The number of rows to display on the screen.
Returns
-------
str
"""
if isinstance(stats, pd.DataFrame):
stats = list(stats.T.to_dict().values())
stats.sort(key=itemgetter('period_close'))
if len(stats) > num_rows:
display_stats = stats[-num_rows:] if show_tail else stats[0:num_rows]
else:
display_stats = stats
df, columns = prepare_stats(
display_stats, recorded_cols=recorded_cols
)
set_print_settings()
return df.to_string(columns=columns)
def get_csv_stats(stats, recorded_cols=None):
"""
Create a CSV buffer from the stats DataFrame.
Parameters
----------
path: str
stats: list[Object]
recorded_cols: list[str]
Returns
-------
"""
df, columns = prepare_stats(stats, recorded_cols=recorded_cols)
return df.to_csv(
None,
columns=columns,
# encoding='utf-8',
quoting=csv.QUOTE_NONNUMERIC
).encode()
def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None,
folder='catalyst/stats', bytes_to_write=None):
"""
Uploads the performance stats to a S3 bucket.
Parameters
----------
uri: str
stats: list[Object]
algo_namespace: str
recorded_cols: list[str]
folder: str
bytes_to_write: str
Option to reuse bytes instead of re-computing the csv
Returns
-------
"""
if not s3_conn:
import boto3
s3_conn.append(boto3.resource('s3'))
s3 = s3_conn[0]
if bytes_to_write is None:
bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols)
now = pd.Timestamp.utcnow()
timestr = now.strftime('%Y%m%d')
pid = os.getpid()
parts = uri.split('//')
path = '{folder}/{algo}/{time}-{algo}-{pid}.csv'.format(
folder=folder,
algo=algo_namespace,
time=timestr,
pid=pid,
)
obj = s3.Object(parts[1], path)
obj.put(Body=bytes_to_write)
def email_error(algo_name, dt, e, environ=None):
import requests
import traceback
if not mailgun:
root = data_root(environ)
filename = os.path.join(root, 'mailgun.json')
if not os.path.exists(filename):
raise ValueError(
'mailgun.json not found in the catalyst data folder'
)
with open(filename) as data_file:
mailgun.append(json.load(data_file))
mg = mailgun[0]
return requests.post(
mg['url'],
auth=("api", mg['api']),
data={
"from": mg['from'],
"to": mg['to'],
"subject": 'Error: {}'.format(algo_name),
"text": '{}\n\n{}\n{}'.format(
dt, e, traceback.format_exc()
)})
def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
"""
Saves the performance stats to the algo local folder.
Parameters
----------
stats: list[Object]
algo_namespace: str
recorded_cols: list[str]
Returns
-------
str
"""
bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols)
timestr = time.strftime('%Y%m%d')
folder = get_algo_folder(algo_namespace)
stats_folder = os.path.join(folder, 'stats')
ensure_directory(stats_folder)
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
with open(filename, 'wb') as handle:
handle.write(bytes_to_write)
return bytes_to_write
def df_to_string(df):
"""
Create a formatted str representation of the DataFrame.
Parameters
----------
df: DataFrame
Returns
-------
str
"""
pd.set_option('display.expand_frame_repr', False)
pd.set_option('precision', 8)
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 1000)
return df.to_string()
def extract_orders(perf):
order_list = perf.orders.values
all_orders = [t for sublist in order_list for t in sublist]
all_orders.sort(key=lambda o: o['dt'])
orders = pd.DataFrame(all_orders)
if not orders.empty:
orders.set_index('dt', inplace=True, drop=True)
return orders
def extract_transactions(perf):
"""
Compute indexes for buy and sell transactions
Parameters
----------
perf: DataFrame
The algo performance DataFrame.
Returns
-------
DataFrame
A DataFrame of transactions.
"""
trans_list = perf.transactions.values
all_trans = [t for sublist in trans_list for t in sublist]
all_trans.sort(key=lambda t: t['dt'])
transactions = pd.DataFrame(all_trans)
if not transactions.empty:
transactions.set_index('dt', inplace=True, drop=True)
return transactions
+82
View File
@@ -0,0 +1,82 @@
import os
import random
import tempfile
from catalyst.assets._assets import TradingPair
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
from catalyst.exchange.utils.factory import find_exchanges
from catalyst.utils.paths import ensure_directory
def handle_exchange_error(exchange, e):
try:
message = '{}: {}'.format(
e.__class__, e.message.decode('ascii', 'ignore')
)
except Exception:
message = 'unexpected error'
folder = get_exchange_folder(exchange.name)
filename = os.path.join(folder, 'blacklist.txt')
with open(filename, 'wt') as handle:
handle.write(message)
def select_random_exchanges(population=3, features=None,
is_authenticated=False, base_currency=None):
all_exchanges = find_exchanges(
features=features,
is_authenticated=is_authenticated,
base_currency=base_currency,
)
if population is not None:
if len(all_exchanges) < population:
population = len(all_exchanges)
exchanges = random.sample(all_exchanges, population)
else:
exchanges = all_exchanges
return exchanges
def select_random_assets(all_assets, population=3):
assets = random.sample(all_assets, population)
return assets
def output_df(df, assets, name=None):
"""
Outputs a price DataFrame to a temp folder.
Parameters
----------
df: pd.DataFrame
assets
name
Returns
-------
"""
if isinstance(assets, TradingPair):
asset_folder = '{}_{}'.format(assets.exchange, assets.symbol)
else:
asset_folder = ','.join(
['{}_{}'.format(a.exchange, a.symbol) for a in assets]
)
folder = os.path.join(
tempfile.gettempdir(), 'catalyst', asset_folder
)
ensure_directory(folder)
if name is None:
name = 'output'
path = os.path.join(folder, '{}.csv'.format(name))
df.to_csv(path)
return path, folder
+3 -1
View File
@@ -34,7 +34,9 @@ from catalyst.finance.commission import (
from catalyst.finance.cancel_policy import NeverCancel
from catalyst.utils.input_validation import expect_types
log = Logger('Blotter')
from catalyst.constants import LOG_LEVEL
log = Logger('Blotter', level=LOG_LEVEL)
warning_logger = Logger('AlgoWarning')
+3 -1
View File
@@ -24,7 +24,9 @@ from catalyst.errors import (
TradingControlViolation,
)
log = logbook.Logger('TradingControl')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('TradingControl', level=LOG_LEVEL)
class TradingControl(with_metaclass(abc.ABCMeta)):
+15 -28
View File
@@ -15,13 +15,8 @@
import abc
from sys import float_info
from six import with_metaclass
import catalyst.utils.math_utils as zp_math
from numpy import isfinite
from six import with_metaclass
from catalyst.errors import BadOrderParameters
@@ -77,6 +72,7 @@ class LimitOrder(ExecutionStyle):
Execution style representing an order to be executed at a price equal to or
better than a specified limit price.
"""
def __init__(self, limit_price, exchange=None):
"""
Store the given price.
@@ -99,6 +95,7 @@ class StopOrder(ExecutionStyle):
Execution style representing an order to be placed once the market price
reaches a specified stop price.
"""
def __init__(self, stop_price, exchange=None):
"""
Store the given price.
@@ -121,6 +118,7 @@ class StopLimitOrder(ExecutionStyle):
Execution style representing a limit order to be placed with a specified
limit price once the market reaches a specified stop price.
"""
def __init__(self, limit_price, stop_price, exchange=None):
"""
Store the given prices
@@ -144,31 +142,20 @@ class StopLimitOrder(ExecutionStyle):
def asymmetric_round_price_to_penny(price, prefer_round_down,
diff=(0.0095 - .005)):
"""
Asymmetric rounding function for adjusting prices to two places in a way
that "improves" the price. For limit prices, this means preferring to
round down on buys and preferring to round up on sells. For stop prices,
it means the reverse.
Modified the original function because we do not want to round
prices on crypto exchange.
If prefer_round_down == True:
When .05 below to .95 above a penny, use that penny.
If prefer_round_down == False:
When .95 below to .05 above a penny, use that penny.
Parameters
----------
price: float
Returns
-------
float
In math-speak:
If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
"""
# Subtracting an epsilon from diff to enforce the open-ness of the upper
# bound on buys and the lower bound on sells. Using the actual system
# epsilon doesn't quite get there, so use a slightly less epsilon-ey value.
epsilon = float_info.epsilon * 10
diff = diff - epsilon
# relies on rounding half away from zero, unlike numpy's bankers' rounding
rounded = round(price - (diff if prefer_round_down else -diff), 2)
if zp_math.tolerant_equals(rounded, 0.0):
return 0.0
return rounded
# TODO: consider overriding outside of the original function
return price
def check_stoplimit_prices(price, label):
+4 -1
View File
@@ -88,7 +88,10 @@ from six import itervalues, iteritems
import catalyst.protocol as zp
log = logbook.Logger('Performance')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Performance', level=LOG_LEVEL)
TRADE_TYPE = zp.DATASOURCE_TYPE.TRADE
+3 -1
View File
@@ -40,7 +40,9 @@ import logbook
from catalyst.assets import Future, Asset
from catalyst.utils.input_validation import expect_types
log = logbook.Logger('Performance')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Performance', level=LOG_LEVEL)
class Position(object):
@@ -32,7 +32,9 @@ from catalyst.assets import (
)
from . position import positiondict
log = logbook.Logger('Performance')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Performance', level=LOG_LEVEL)
PositionStats = namedtuple('PositionStats',
+3 -17
View File
@@ -70,7 +70,9 @@ import catalyst.finance.risk as risk
from . position_tracker import PositionTracker
log = logbook.Logger('Performance')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Performance', level=LOG_LEVEL)
class PerformanceTracker(object):
@@ -111,27 +113,11 @@ class PerformanceTracker(object):
self.treasury_curves,
self.trading_calendar
)
elif self.emission_rate == '5-minute':
self.all_benchmark_returns = pd.Series(
index=pd.date_range(
self.sim_params.first_open,
self.sim_params.last_close,
freq='5min'
),
)
self.cumulative_risk_metrics = \
risk.RiskMetricsCumulative(
self.sim_params,
self.treasury_curves,
self.trading_calendar,
create_first_day_stats=True,
)
elif self.emission_rate == 'minute':
self.all_benchmark_returns = pd.Series(index=pd.date_range(
self.sim_params.first_open, self.sim_params.last_close,
freq='Min')
)
self.cumulative_risk_metrics = \
risk.RiskMetricsCumulative(
self.sim_params,
+63 -25
View File
@@ -22,24 +22,25 @@ from pandas.tseries.tools import normalize_date
from six import iteritems
from . risk import (
from .risk import (
check_entry,
choose_treasury
)
from empyrical import (
from catalyst.patches.stats import (
alpha_beta_aligned,
annual_volatility,
cum_returns,
downside_risk,
information_ratio,
max_drawdown,
sharpe_ratio,
sortino_ratio,
cum_returns,
)
import warnings
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Risk Cumulative')
log = logbook.Logger('Risk Cumulative', level=LOG_LEVEL)
choose_treasury = functools.partial(choose_treasury, lambda *args: '10year',
compound=False)
@@ -143,6 +144,8 @@ class RiskMetricsCumulative(object):
self.num_trading_days = 0
def update(self, dt, algorithm_returns, benchmark_returns, leverage):
warnings.filterwarnings('error')
# Keep track of latest dt for use in to_dict and other methods
# that report current state.
self.latest_dt = dt
@@ -158,9 +161,13 @@ class RiskMetricsCumulative(object):
if len(self.algorithm_returns) == 1:
self.algorithm_returns = np.append(0.0, self.algorithm_returns)
self.algorithm_cumulative_returns[dt_loc] = cum_returns(
self.algorithm_returns
)[-1]
try:
self.algorithm_cumulative_returns[dt_loc] = cum_returns(
self.algorithm_returns
)[-1]
except Exception as e:
log.debug('unable to calculate cum returns: {}'.format(e))
self.algorithm_cumulative_returns[dt_loc] = np.nan
algo_cumulative_returns_to_date = \
self.algorithm_cumulative_returns[:dt_loc + 1]
@@ -189,9 +196,15 @@ class RiskMetricsCumulative(object):
if len(self.benchmark_returns) == 1:
self.benchmark_returns = np.append(0.0, self.benchmark_returns)
self.benchmark_cumulative_returns[dt_loc] = cum_returns(
self.benchmark_returns
)[-1]
try:
self.benchmark_cumulative_returns[dt_loc] = cum_returns(
self.benchmark_returns
)[-1]
except Exception as e:
log.debug(
'unable to calculate benchmark cum returns: {}'.format(e)
)
self.benchmark_cumulative_returns[dt_loc] = np.nan
benchmark_cumulative_returns_to_date = \
self.benchmark_cumulative_returns[:dt_loc + 1]
@@ -263,24 +276,49 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
self.sharpe[dt_loc] = sharpe_ratio(
self.algorithm_returns,
)
self.downside_risk[dt_loc] = downside_risk(
self.algorithm_returns
)
self.sortino[dt_loc] = sortino_ratio(
self.algorithm_returns,
_downside_risk=self.downside_risk[dt_loc]
)
try:
self.downside_risk[dt_loc] = downside_risk(
self.algorithm_returns
)
except Exception as e:
log.debug(
'unable to calculate downside risk returns: {}'.format(e)
)
self.downside_risk[dt_loc] = np.nan
try:
risk = self.downside_risk[dt_loc]
self.sortino[dt_loc] = sortino_ratio(
self.algorithm_returns,
_downside_risk=risk
)
except Exception as e:
log.debug(
'unable to calculate benchmark cum returns: {}'.format(e)
)
self.sortino[dt_loc] = np.nan
self.information[dt_loc] = information_ratio(
self.algorithm_returns,
self.benchmark_returns,
)
self.max_drawdown = max_drawdown(
self.algorithm_returns
)
try:
self.max_drawdown = max_drawdown(
self.algorithm_returns
)
except Exception as e:
log.debug(
'unable to calculate max drawdown: {}'.format(e)
)
self.max_drawdown = np.nan
self.max_drawdowns[dt_loc] = self.max_drawdown
self.max_leverage = self.calculate_max_leverage()
self.max_leverages[dt_loc] = self.max_leverage
warnings.resetwarnings()
def to_dict(self):
"""
Creates a dictionary representing the state of the risk report.
@@ -292,18 +330,18 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
rval = {
'trading_days': self.num_trading_days,
'benchmark_volatility':
self.benchmark_volatility[dt_loc],
self.benchmark_volatility[dt_loc],
'algo_volatility':
self.algorithm_volatility[dt_loc],
self.algorithm_volatility[dt_loc],
'treasury_period_return': self.treasury_period_return,
# Though the two following keys say period return,
# they would be more accurately called the cumulative return.
# However, the keys need to stay the same, for now, for backwards
# compatibility with existing consumers.
'algorithm_period_return':
self.algorithm_cumulative_returns[dt_loc],
self.algorithm_cumulative_returns[dt_loc],
'benchmark_period_return':
self.benchmark_cumulative_returns[dt_loc],
self.benchmark_cumulative_returns[dt_loc],
'beta': self.beta[dt_loc],
'alpha': self.alpha[dt_loc],
'sharpe': self.sharpe[dt_loc],
+29 -11
View File
@@ -14,6 +14,7 @@
# limitations under the License.
import functools
import warnings
import logbook
@@ -23,7 +24,7 @@ import numpy as np
import pandas as pd
from . import risk
from . risk import check_entry
from .risk import check_entry
from empyrical import (
alpha_beta_aligned,
@@ -36,7 +37,9 @@ from empyrical import (
sortino_ratio
)
log = logbook.Logger('Risk Period')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Risk Period', level=LOG_LEVEL)
choose_treasury = functools.partial(risk.choose_treasury,
risk.select_treasury_duration)
@@ -76,14 +79,20 @@ class RiskMetricsPeriod(object):
self.calculate_metrics()
def calculate_metrics(self):
self.benchmark_period_returns = \
cum_returns(self.benchmark_returns).iloc[-1]
warnings.filterwarnings('error')
try:
self.benchmark_period_returns = \
cum_returns(self.benchmark_returns).iloc[-1]
except Exception:
# TODO: why is there an error
self.benchmark_period_returns = 0
self.algorithm_period_returns = \
cum_returns(self.algorithm_returns).iloc[-1]
if not self.algorithm_returns.index.equals(
self.benchmark_returns.index
self.benchmark_returns.index
):
message = "Mismatch between benchmark_returns ({bm_count}) and \
algorithm_returns ({algo_count}) in range {start} : {end}"
@@ -126,10 +135,17 @@ class RiskMetricsPeriod(object):
self.downside_risk = downside_risk(
self.algorithm_returns.values
)
self.sortino = sortino_ratio(
self.algorithm_returns.values,
_downside_risk=self.downside_risk,
)
try:
risk = self.downside_risk
self.sortino = sortino_ratio(
self.algorithm_returns.values,
_downside_risk=risk,
)
except Exception:
# TODO: what causes it to error out?
self.sortino = 0
self.information = information_ratio(
self.algorithm_returns.values,
self.benchmark_returns.values,
@@ -138,11 +154,13 @@ class RiskMetricsPeriod(object):
self.algorithm_returns.values,
self.benchmark_returns.values,
)
self.excess_return = self.algorithm_period_returns - \
self.treasury_period_return
self.excess_return = self.algorithm_period_returns \
- self.treasury_period_return
self.max_drawdown = max_drawdown(self.algorithm_returns.values)
self.max_leverage = self.calculate_max_leverage()
warnings.resetwarnings()
def to_dict(self):
"""
Creates a dictionary representing the state of the risk report.
+3 -1
View File
@@ -63,7 +63,9 @@ from dateutil.relativedelta import relativedelta
from . period import RiskMetricsPeriod
log = logbook.Logger('Risk Report')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Risk Report', level=LOG_LEVEL)
class RiskReport(object):
+5 -2
View File
@@ -61,7 +61,9 @@ Risk Report
import logbook
import numpy as np
log = logbook.Logger('Risk')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Risk', level=LOG_LEVEL)
TREASURY_DURATIONS = [
@@ -158,7 +160,8 @@ def choose_treasury(select_treasury, treasury_curves, start_session,
)
break
if search_day and trading_calendar.name != 'OPEN': # Supress warning for 'OPEN' calendar
# Supress warning for 'OPEN' calendar
if search_day and trading_calendar.name != 'OPEN':
if (search_dist is None or search_dist > 1) and \
search_days[0] <= end_session <= search_days[-1]:
message = "No rate within 1 trading day of end date = \
+5 -3
View File
@@ -205,20 +205,22 @@ class VolumeShareSlippage(SlippageModel):
def process_order(self, data, order):
volume = data.current(order.asset, "volume")
min_trade_size = order.asset.min_trade_size
max_volume = self.volume_limit * volume
# price impact accounts for the total volume of transactions
# created against the current minute bar
remaining_volume = max_volume - self.volume_for_bar
if remaining_volume < 1:
if remaining_volume < min_trade_size:
# we can't fill any more transactions
raise LiquidityExceeded()
# the current order amount will be the min of the
# volume available in the bar or the open amount.
cur_volume = int(min(remaining_volume, abs(order.open_amount)))
cur_volume = min(remaining_volume, abs(order.open_amount))
if cur_volume < 1:
if cur_volume < min_trade_size:
return None, None
# tally the current amount into our total amount ordered.
+3 -1
View File
@@ -26,7 +26,9 @@ from catalyst.data.loader import load_market_data
from catalyst.utils.calendars import get_calendar
from catalyst.utils.memoize import remember_last
log = logbook.Logger('Trading')
from catalyst.constants import LOG_LEVEL
log = logbook.Logger('Trading', level=LOG_LEVEL)
DEFAULT_CAPITAL_BASE = 1e5
+1 -5
View File
@@ -65,14 +65,10 @@ def create_transaction(order, dt, price, amount):
# floor the amount to protect against non-whole number orders
# TODO: Investigate whether we can add a robust check in blotter
# and/or tradesimulation, as well.
amount_magnitude = int(abs(amount))
if amount_magnitude < 1:
raise Exception("Transaction magnitude must be at least 1.")
transaction = Transaction(
asset=order.asset,
amount=int(amount),
amount=amount,
dt=dt,
price=price,
order_id=order.id
-23
View File
@@ -20,9 +20,7 @@ cimport cython
from cpython cimport bool
cdef np.int64_t _nanos_in_minute = 60000000000
cdef np.int64_t _nanos_in_five_minutes = 5 * _nanos_in_minute
NANOS_IN_MINUTE = _nanos_in_minute
NANOS_IN_FIVE_MINUTES = _nanos_in_five_minutes
cpdef enum:
BAR = 0
@@ -117,24 +115,3 @@ cdef class MinuteSimulationClock:
yield minute, BAR
if minute_emission:
yield minute, MINUTE_END
cdef class FiveMinuteSimulationClock(MinuteSimulationClock):
@cython.boundscheck(False)
@cython.wraparound(False)
cdef dict calc_minutes_by_session(self):
cdef dict five_minutes_by_session
cdef int session_idx
cdef np.int64_t session_nano
cdef np.ndarray[np.int64_t, ndim=1] five_minutes_nanos
five_minutes_by_session = {}
for session_idx, session_nano in enumerate(self.sessions_nanos):
five_minutes_nanos = np.arange(
self.market_opens_nanos[session_idx],
self.market_closes_nanos[session_idx],
_nanos_in_five_minutes
)
five_minutes_by_session[session_nano] = pd.to_datetime(
five_minutes_nanos, utc=True, box=True
)
return five_minutes_by_session
+4 -3
View File
@@ -27,14 +27,15 @@ from catalyst.gens.sim_engine import (
BEFORE_TRADING_START_BAR
)
log = Logger('Trade Simulation')
from catalyst.constants import LOG_LEVEL
log = Logger('Trade Simulation', level=LOG_LEVEL)
class AlgorithmSimulator(object):
EMISSION_TO_PERF_KEY_MAP = {
'minute': 'minute_perf',
'5-minute': '5_minute_perf',
'daily': 'daily_perf'
}
@@ -202,7 +203,7 @@ class AlgorithmSimulator(object):
stack.enter_context(self.processor)
stack.enter_context(ZiplineAPI(self.algo))
if algo.data_frequency in set(('minute', '5-minute')):
if algo.data_frequency == 'minute':
def execute_order_cancellation_policy():
algo.blotter.execute_cancel_policy(SESSION_END)
View File
File diff suppressed because it is too large Load Diff
+8 -4
View File
@@ -7,6 +7,7 @@ from abc import (
)
from uuid import uuid4
import six
from six import (
iteritems,
with_metaclass,
@@ -33,7 +34,6 @@ from catalyst.utils.sharedoc import copydoc
class PipelineEngine(with_metaclass(ABCMeta)):
@abstractmethod
def run_pipeline(self, pipeline, start_date, end_date):
"""
@@ -118,6 +118,7 @@ class ExplodingPipelineEngine(PipelineEngine):
"""
A PipelineEngine that doesn't do anything.
"""
def run_pipeline(self, pipeline, start_date, end_date):
raise NoEngineRegistered(
"Attempted to run a pipeline but no pipeline "
@@ -484,8 +485,10 @@ class SimplePipelineEngine(PipelineEngine):
)
if isinstance(term, LoadableTerm):
term_key = loader_group_key(term)
# TODO: temp workaround
to_load = sorted(
loader_groups[loader_group_key(term)],
six.next(six.itervalues(loader_groups)),
key=lambda t: t.dataset
)
loader = get_loader(term)
@@ -565,9 +568,10 @@ class SimplePipelineEngine(PipelineEngine):
index=MultiIndex.from_arrays([empty_dates, empty_assets]),
)
resolved_assets = array(self._finder.retrieve_all(assets))
# TODO: not sure what's wrong with the resolved_assets
# resolved_assets = array(self._finder.retrieve_all(assets))
dates_kept = repeat_last_axis(dates.values, len(assets))[mask]
assets_kept = repeat_first_axis(resolved_assets, len(dates))[mask]
assets_kept = repeat_first_axis(assets, len(dates))[mask]
final_columns = {}
for name in data:
@@ -1,9 +1,6 @@
from .statistical import (
RollingPearson,
RollingLinearRegression,
RollingLinearRegressionOfReturns,
RollingPearsonOfReturns,
RollingSpearman,
RollingSpearmanOfReturns,
)
from .technical import (
+2 -2
View File
@@ -142,7 +142,7 @@ class TermGraph(object):
at the end of execution.
"""
refcounts = self.graph.out_degree()
for t in self.outputs.values():
for t in list(self.outputs.values()):
refcounts[t] += 1
for t in initial_terms:
@@ -238,7 +238,7 @@ class ExecutionPlan(TermGraph):
min_extra_rows=0):
super(ExecutionPlan, self).__init__(terms)
for term in terms.values():
for term in list(terms.values()):
self.set_extra_rows(
term,
all_dates,
@@ -41,10 +41,6 @@ class CryptoPricingLoader(PipelineLoader):
reader = bundle.daily_bar_reader
all_sessions = cal.all_sessions
elif data_frequency == '5-minute':
reader = bundle.five_minute_bar_reader
all_sessions = cal.all_five_minutes
elif data_frequency == 'minute':
reader = bundle.minute_bar_reader
all_sessions = cal.all_minutes
@@ -38,11 +38,11 @@ class USEquityPricingLoader(PipelineLoader):
def __init__(self, bundle, data_frequency, dataset):
if data_frequency == 'daily':
reader = bundle.daily_bar_reader
elif data_frequency == '5-minute':
reader = bundle.five_minute_bar_reader
elif daily_bar_reader == 'minute':
# TODO: This is currently broken, No Pipeline support for Catalyst
# if data_frequency == 'daily':
# reader = bundle.daily_bar_reader
# elif daily_bar_reader == 'minute':
if data_frequency == 'minute':
reader = bundle.minute_bar_reader
else:
raise ValueError(
@@ -53,10 +53,9 @@ class USEquityPricingLoader(PipelineLoader):
if data_frequency == 'daily':
all_sessions = cal.all_sessions
elif data_frequency == '5-minute':
reader = bundle.five_minute_bar_reader
all_sessions = cal.all_five_minutes
elif daily_bar_reader == 'minute':
# TODO: this cannot be right, but no pipeline support at the moment
# elif daily_bar_reader == 'minute':
elif data_frequency == 'minute':
reader = bundle.minute_bar_reader
all_sessions = cal.all_minutes
+1 -1
View File
@@ -231,7 +231,7 @@ class EventsLoader(PipelineLoader):
self.load_next_events(n, dates, sids, mask),
self.load_previous_events(p, dates, sids, mask),
)
@property
def columns(self):
return self._columns
-1
View File
@@ -180,4 +180,3 @@ class DataFrameLoader(PipelineLoader):
@property
def columns(self):
return self._columns

Some files were not shown because too many files have changed in this diff Show More