Compare commits

..
371 Commits
Author SHA1 Message Date
wassname b4faf9158d fix typos 2018-03-31 10:44:32 +08:00
wassname dfb1bf51bd fix tslib deprecation 2018-03-31 10:05:38 +08:00
wassname f946062265 update requirements 2018-03-31 10:05:04 +08:00
wassname 65c6322ba5 change pandas dependancy 2018-03-31 09:52:48 +08:00
wassname 4a2d5678ad use dt.normalize() in pd>=0.20 2018-03-31 09:51:15 +08:00
wassname 06399caa2b fix error converting tzaware to tzaware 2018-03-31 09:50:41 +08:00
Victor Grau Serrat 4c84ea8efc DOC: release notes 0.5.8 2018-03-29 10:23:38 -06:00
Victor Grau Serrat c85e698ee2 BLD: [mktplace] switch to mainnet 2018-03-29 10:00:35 -06:00
Victor Grau Serrat 423e30da1e Merge branch 'develop' 2018-03-29 08:28:05 -06:00
Victor Grau Serrat c29b1ef3c1 BLD: deployment of marketplace on mainnet 2018-03-29 08:26:09 -06:00
Victor Grau Serrat 17f9906df4 DOC: updated release notes for 0.5.7 2018-03-29 08:01:58 -06:00
Avishai WeingartenandGitHub 41b5135ed4 Merge pull request #220 from westurner/feature/fix-docker-tags
BLD: Dockerfile[-dev]: s/quantopian/enigmampc/g
2018-03-29 08:49:52 +03:00
Victor Grau Serrat af85ee31c9 BUG: [mktplace] progress counters start at 1, not 0 2018-03-27 18:12:08 -06:00
Victor Grau Serrat 132dffd239 BLD: [mktplace] progress indicator for publishing data 2018-03-27 13:22:30 -06:00
VictorandGitHub 887a7cc825 BUG: [mktplace] removing whitespace from datasets to register 2018-03-26 17:22:53 -06:00
AvishaiW 22249506e6 BUG: #214 #287 added arguments to the _reduce_ function in tha Asset class 2018-03-25 22:53:01 +03:00
Victor Grau Serrat b979ffd123 BLD: [mktplace] json handling improvement 2018-03-23 12:38:37 -06:00
Victor Grau Serrat 0db9950347 DOC: link to forum from install doc 2018-03-22 15:24:50 -06:00
Victor 7c4467d800 DOC: added forum button in README 2018-03-22 15:21:17 -06:00
Victor 30dbeaa5fb DOC: Updated README with link to the forum. 2018-03-22 15:21:13 -06:00
VictorandGitHub 4d6837b5d6 DOC: Updated README with link to the forum. 2018-03-22 15:18:55 -06:00
VictorandGitHub 098a4c4fc6 DOC: added forum button in README 2018-03-22 15:15:31 -06:00
Victor Grau Serrat f5cb6e38d6 DOC: fixed formatting of release notes 2018-03-22 12:13:55 -06:00
Victor Grau Serrat 1bd65397b6 Merge branch 'develop' 2018-03-22 12:02:15 -06:00
Victor Grau Serrat c768b207bc MAINT: [marketplace] output formatting address list 2018-03-22 12:01:21 -06:00
VictorandGitHub e18686d5c5 Merge pull request #278 from izokay/develop
BUG: Error when ingesting marketcap on windows
2018-03-22 12:23:29 -05:00
VictorandGitHub b7779cf363 MAINT: general bug fix for existing path across OS 2018-03-22 11:23:06 -06:00
Victor Grau Serrat 28819b8a32 DOC: updated release notes for 0.5.6 2018-03-21 22:29:21 -06:00
Victor Grau Serrat a56d7f34c7 BLD: [mktplace] support for most wallets, switch to mycrypto 2018-03-21 20:35:18 -05:00
AvishaiW 9f0b3303f1 BUG: #285 #271 changed benchmark to be constant, so it wouldn't ingest data at all, for now 2018-03-21 21:19:40 +02:00
EmbarAlmog 9d7a35658b ENH: when ingesting data of non-existing pair it is now throwing log warning. 2018-03-20 16:12:13 +02:00
Victor Grau Serrat c58cebd1eb ENH: progress on marketplace bundle ingestion 2018-03-19 11:50:39 -06:00
Frederic Fortier 027cdba474 Merge branch 'develop' 2018-03-19 13:12:08 -04:00
Frederic Fortier d223529100 DOC: updated release notes of 0.5.5 2018-03-19 13:11:31 -04:00
Frederic Fortier 1e02506ab4 Merge branch 'develop' 2018-03-19 13:07:06 -04:00
lenak25 2a97ade68e BLD: support hourly freq in live and backtest, as reported on issue #227 and issue #114 2018-03-19 16:44:44 +02:00
lenak25 9648767e9a STY: flake8 fixes 2018-03-19 15:53:06 +02:00
lenak25 98449b2088 BLD: fix issue #274 - a bug in which a wrong bar number was returned when requesting day freq history candles in backtest 2018-03-19 11:06:47 +02:00
Frederic Fortier 91d16aba3b DOC: documented the get_frequency function for additional clarity 2018-03-17 18:32:28 -04:00
izokayandGitHub 9eb649371b BUG: Error when ingesting on windows
Error message: Cannot create a file when that file already exists: '.catalyst\\data\\marketplace\\temp_bundles\\marketcap-hourly-2018' -> '.catalyst\\data\\marketplace\\marketcap'
2018-03-16 16:50:31 -04:00
izokayandGitHub 7f2ded65bc Merge pull request #2 from enigmampc/develop
Develop
2018-03-16 16:45:03 -04:00
Frederic Fortier b76b4458cb Merge branch 'vonpupp-fix_hourly_candles' into develop 2018-03-16 15:49:39 -04:00
Frederic Fortier decbdbf6ea Merge branch 'fix_hourly_candles' of https://github.com/vonpupp/catalyst into vonpupp-fix_hourly_candles 2018-03-16 15:49:28 -04:00
Albert De La Fuente Vigliotti 685ce25b85 Fix H candle support 2018-03-16 16:02:40 -03:00
Avishai WeingartenandGitHub 1cafcc1417 BUG: removed one out of two matplotlib appearences in 2.7 yml 2018-03-16 14:40:05 +02:00
VictorandGitHub 0d77854782 Merge pull request #275 from izokay/patch-1
typo on creating env for python 3.6
2018-03-15 15:18:07 -06:00
izokayandGitHub 4cb8d54d97 typo on creating env for python 3.6 2018-03-15 15:41:30 -04:00
Victor Grau Serrat 7b796a4276 MAINT: [mktplace] sign_msg opens browser window 2018-03-15 12:59:59 -04:00
AvishaiW 41a4c7072f DOC: fixed a mistake on the installation tutorial 2018-03-14 09:44:24 +02:00
Victor Grau Serrat 11302b3af9 Merge branch 'develop' 2018-03-14 00:52:33 -06:00
Victor Grau Serrat 5bb7eed072 MAINT: ref. mktplace to master, updated release notes 0.5.4 2018-03-14 00:51:49 -06:00
Victor Grau Serrat dbf3b6e6b2 MAINT: typo in marketplace help 2018-03-14 00:11:24 -06:00
Victor Grau Serrat 3e69449a6b BLD: marketplace switch to rinkeby post-audit 2018-03-14 00:11:24 -06:00
lenak25 69731b653d BLD: revert hourly freq support reported at issue #227 2018-03-13 18:43:48 +02:00
Victor Grau Serrat 127d779eb1 BUG: fix sanitize_df to min of int32 2018-03-12 16:52:54 -06:00
lenak25 8d86a5548f DOC: add ta_lib troubleshooting to the docs 2018-03-12 18:04:38 +02:00
lenak25 0a37cdec5b BLD: fix 'on the clock' candles fetch and request extra candles using a fixed time interval 2018-03-11 19:36:28 +02:00
Victor Grau Serrat 8f7d678170 BUG: [mktplace]: fix sanitize_df to handle 1-row DFs 2018-03-09 12:44:12 -07:00
Frederic Fortier 1de084a08f Merge remote-tracking branch 'origin/develop' into develop 2018-03-09 13:21:02 -05:00
Frederic Fortier 93ca4e990d BUG: fixed a dependency issue 2018-03-09 13:20:50 -05:00
Victor Grau Serrat 4694372496 BUG: [mktplace] ingest mismatch dest. folder 2018-03-09 09:56:09 -07:00
Victor Grau Serrat f0606b5ea4 BLD: [mktplace] clean --dataset param optional 2018-03-09 09:50:20 -07:00
AvishaiW b0dce13672 BUG: removed get_open_orders from buy_low_sell_high 2018-03-09 11:01:19 +02:00
Frederic Fortier fc9837b678 BUG: fixed an issue with extracting bundles 2018-03-08 17:27:25 -05:00
Victor Grau Serrat 5de89549ee MAINT: [mktplace] ingest --dataset parameter now optional 2018-03-08 10:33:16 -07:00
AvishaiW 586d7f2954 DOC: added warning that its not possible to start & end at a specific time 2018-03-08 18:34:25 +02:00
Victor Grau Serrat 218dc0bafd BUG: marketplace os.rename -> shutil.move 2018-03-07 22:47:27 -07:00
Victor Grau Serrat 89c080fce7 BLD: marketplace: dataset param to subscribe optional, fix "from" tx 2018-03-07 16:05:38 -07:00
Victor Grau Serrat 4a794aa035 BLD: show catalyst version at runtime 2018-03-05 21:59:18 -07:00
AvishaiW cb4668f093 BUG: #243 added a function which reduces open orders amount from calculated target/amount for target orders 2018-03-06 00:05:29 +02:00
lenak25 6092471180 DOC: add some commented TODOs 2018-03-05 20:42:14 +02:00
lenak25 09068a4c37 BLD: adjust the example to Python 3 2018-03-04 18:20:14 +02:00
lenak25 ed406a30ff BLD: fix issue #260 - always request more data to avoid empty bars and always give the exact bar number 2018-03-04 17:45:08 +02:00
AvishaiW aa520d5a8b STY: pep8 change in exchange_blotter 2018-03-04 17:24:22 +02:00
lenak25 e59f46dfd6 BLD: improve periods calculation 2018-03-04 13:47:08 +02:00
Victor Grau Serrat 5197ab6cc2 BUG: isolated Python3 depedency to the marketplace 2018-03-02 12:50:46 -07:00
Victor Grau Serrat d7b6cb8490 BUG: marketplace typo 2018-03-02 12:18:48 -07:00
Victor Grau Serrat fa0e9332bf MAINT: CLI info on marketplace cmds 2018-03-02 11:43:59 -07:00
VictorandGitHub d9d6a4e52d Merge pull request #257 from mattbornski/master
fix incompatibility with web3==4.0.0b11 from prior web3 versions
2018-03-02 11:38:52 -07:00
VictorandGitHub b4e5b699bd BUG: fix2 incompatibility with web3==4.0.0b11 2018-03-02 11:37:22 -07:00
VictorandGitHub f990ecf14d BUG: fix incompatibility with web3==4.0.0b11 2018-03-02 11:34:18 -07:00
lenak25 5d3a1c2f8b BLD: cosmetics 2018-03-02 00:39:28 +02:00
lenak25 3159ec7dc2 BLD: fix periods calculations and bundle unit-test 2018-03-02 00:36:13 +02:00
lenak25 8be0626fc9 BLD:refine unit-test 2018-03-01 18:16:57 +02:00
lenak25 868f17fd9d BLD:flake fixes 2018-03-01 16:39:42 +02:00
lenak25 b95cf465fc BLD:updating the forward fill to set volume to zero and others values to the previous close value 2018-03-01 16:09:09 +02:00
AvishaiW c4b10bae39 DOC: added- creating a virtual env for 3.6 2018-03-01 09:47:18 +02:00
AvishaiW 6c4f7afaea BUG: fixed removing files- check the path, not the file 2018-03-01 09:42:07 +02:00
Victor Grau Serrat b85219d5b4 MAINT: undoing last 2 unwanted commits 2018-02-28 18:06:36 -07:00
AvishaiW 082b342d02 Merge remote-tracking branch 'origin/develop' into cloud_conn 2018-03-01 01:11:46 +02:00
AvishaiW 37c1057ab6 BLD: added a cmd for running on the cloud (WIP) 2018-03-01 01:09:35 +02:00
Avishai WeingartenandGitHub 46e1a87a3d DOC: added troubleshooting for python3 2018-02-27 18:47:43 +02:00
Avishai WeingartenandGitHub cfafafb8fc BUG #252 fixed utc time and file erased 2018-02-27 09:47:02 +02:00
Matt Bornski 497212383a Python 3 returns bytes, the parsing functions are looking for strings 2018-02-26 15:44:32 -08:00
AvishaiW e5870ea60a BUG: fix #252 #253 and split state into paper and live 2018-02-26 09:21:00 +02:00
AvishaiW 8587fee0ce BUG: revert previous changes #249 2018-02-25 11:29:26 +02:00
Victor Grau Serrat 388535b09c BUG: reverts changed introduced in 00f232e2d7 2018-02-22 22:14:03 -07:00
Victor Grau Serrat 25e9f0f58f BUG: reverts changed introduced in 00f232e2d7 2018-02-22 22:09:51 -07:00
AvishaiW b4bd557273 BUG: fixes for issues #204 #237
-modified parameters for cancel_orders
-update portfolio after any change in
 the orders before sync
2018-02-23 00:38:51 +02:00
Victor Grau Serrat 2577b53518 MAINT: conda environment updates 2018-02-22 12:55:19 -07:00
Victor Grau Serrat 8fe3ab344e MAINT: conda environment updates 2018-02-22 12:54:36 -07:00
Victor bfd7e4b2dd Update python3.6-environment.yml 2018-02-22 12:54:36 -07:00
lenak25 50310576f9 BUG:fix an issue with wrong timestamps seen at tests.exchange.test_suites.test_suite_bundle.TestSuiteBundle#test_validate_bundles (which issue #230 uncovered) 2018-02-22 17:42:14 +02:00
lenak25 fea2ed104e BUG: fix issue #236: handle properly empty candles received from exchanges 2018-02-22 16:50:48 +02:00
embaral 127878413e DOC: added an option "catalyst live --help" to the documentation. 2018-02-22 14:45:14 +02:00
embaral 6a6ccf5595 Merge remote-tracking branch 'origin/develop' into develop 2018-02-22 14:37:46 +02:00
embaral d40585f56e DOC: added an option "catalyst live --help" to the documentation. 2018-02-22 14:34:00 +02:00
Victor Grau Serrat 4337abd60a DOC: linking example_algo to their sources 2018-02-21 15:51:34 -07:00
AvishaiW 92e0a7bb88 Merge branch 'develop' of https://github.com/enigmampc/catalyst into develop 2018-02-21 20:45:39 +02:00
AvishaiW 20f8a75f4a BUG: for issue #237, update positions before checking balances 2018-02-21 20:42:39 +02:00
Frederic Fortier 634d22fb06 Merge remote-tracking branch 'origin/develop' into develop 2018-02-21 13:07:23 -05:00
Frederic Fortier 7ee875ee70 BLD: adjusted sample algo 2018-02-21 13:06:57 -05:00
Victor Grau Serrat 9936b38e09 DOC: updated Visual C++ instructions for Windows & Python 3 2018-02-21 08:52:19 -07:00
AvishaiW 7e22674d40 Merge branch 'develop' of https://github.com/enigmampc/catalyst into develop 2018-02-20 19:18:10 +02:00
AvishaiW ab644bd732 BUG: modified dual_moving_average example
modified long_window to be larger than short_window
2018-02-20 19:17:03 +02:00
lenak25 2becc4157a MAINT: cosmetics 2018-02-20 14:51:25 +02:00
Victor Grau Serrat ec5fdecf91 DOC: marketplace code examples 2018-02-16 11:50:16 -07:00
Victor Grau Serrat bd88ba2277 DOC: marketplace code examples 2018-02-16 11:49:07 -07:00
AvishaiW a61857b37d BUG: changed the data, analyze gets in live
- fixed the bug following #229
- at the end of each day the stores the daily stats to local directory
- removes the stats folder at the begining of each run to avoid overloading the disk.
- removes old data (over a month) during the run to avoid overloading the disk
2018-02-15 20:22:15 +02:00
Frederic Fortier 866b0a215c BUG: for issue #227, made more mappings for hourly frequency 2018-02-14 23:30:53 -05:00
Frederic Fortier 2ba825db9b BUG: for issue #227, made more mappings for hourly frequency 2018-02-14 20:26:41 -05:00
Frederic Fortier 041665dee1 BUG: fixed issue with incremental ingestion 2018-02-14 13:37:51 -05:00
Frederic Fortier 8072ded532 BLD: clarified open order message 2018-02-14 12:25:44 -05:00
VictorandGitHub 9956b5462d Update python3.6-environment.yml 2018-02-14 09:27:53 -07:00
Victor Grau Serrat 92d3b98448 MAINT: updated conda install instructions for Python3 2018-02-13 12:14:11 -07:00
Victor Grau Serrat 46f34d64a0 MAINT: conda env for Python3 2018-02-13 12:06:06 -07:00
Victor Grau Serrat d494de7551 MAINT: conda env for Python3 2018-02-13 12:05:01 -07:00
AvishaiW 129778945f Merge branch 'develop' of https://github.com/enigmampc/catalyst into develop 2018-02-13 19:41:52 +02:00
AvishaiW f3183b267a BLD: added password into credentials
needed for exchanges such as 'gdax'
2018-02-13 19:41:37 +02:00
AvishaiW 6812aa0c5b BLD: added password into credentials
needed for exchanges such as 'gdax'
2018-02-13 19:39:38 +02:00
lenak25 89bf6742e3 MAINT: remove unnecessary Binance exclusion code 2018-02-13 16:57:03 +02:00
Frederic Fortier f45d673d94 BUG: fixed issue #227 by allowing H frequency 2018-02-13 00:11:20 -05:00
Frederic Fortier 67400f048b BLD: fix issue with removing extra candle in resampling 2018-02-12 22:39:03 -05:00
Frederic Fortier d64d6f191f BLD: upgraded CCXT 2018-02-12 16:44:25 -05:00
Frederic Fortier 5345bfc43d BLD: remove extra candle at the beginning of history bars after resampling 2018-02-12 13:59:19 -05:00
AvishaiW e9745be0e8 DOC: added use of end_date & start_date in live mode.
issues #225 & #213
2018-02-11 13:53:39 +02:00
Victor Grau Serrat bc8bf6941d MAINT: contract+abi pointing to master, not develop 2018-02-09 16:08:22 -08:00
Victor Grau Serrat f35444fabc DOC: updated algo as per #201 2018-02-09 15:16:14 -08:00
Victor Grau Serrat 2c1f810163 DOC: pycharm: improved upon #195 2018-02-09 11:10:32 -08:00
VictorandGitHub 77570f03eb Merge pull request #195 from izokay/develop
PyCharm Documentation
2018-02-09 11:17:16 -07:00
Victor Grau Serrat a8cfcead70 DOC: live-trading: improved upon #221 2018-02-09 10:13:52 -08:00
VictorandGitHub 00f579c729 Merge pull request #221 from westurner/feature/live-trading-doc-newline
DOC: live-trading.rst: add newline before ul
2018-02-09 11:10:14 -07:00
Frederic Fortier 49b6792399 Merge branch 'develop' 2018-02-09 12:01:57 -05:00
Frederic Fortier 6b1982274e DOC: updated release notes for 0.5.3 2018-02-09 02:44:45 -05:00
Frederic Fortier bc58640a3a Merge remote-tracking branch 'origin/develop' into develop 2018-02-09 02:41:08 -05:00
Frederic Fortier 511af7946e BUG: for issue #219, created another unit test which compares current price against last candle 2018-02-09 02:40:53 -05:00
Victor Grau Serrat dcdb3d9d7a MAINT: removing many warnings from building docs 2018-02-09 00:07:38 -07:00
westurner 262dffd4bf DOC: live-trading.rst: add newline before ul 2018-02-09 01:46:44 -05:00
westurner 2d5d2b21ee BLD: Dockerfile[-dev]: s/quantopian/enigmampc/g 2018-02-09 01:40:43 -05:00
Frederic Fortier 5c77cfc68d BUG: for issue #219, fixed a resampling issue 2018-02-09 01:38:22 -05:00
Frederic Fortier 11f8e36ff0 Merge remote-tracking branch 'origin/develop' into develop 2018-02-09 00:56:45 -05:00
Frederic Fortier 44e4e66f5c BLD: adding more log messages 2018-02-09 00:56:35 -05:00
Victor Grau Serrat 3c4c6c3dfd DOC: small edits, eliminating sphinx warnings 2018-02-08 22:10:57 -07:00
Victor Grau Serrat bebebc46dc DOC: small edits, eliminating sphinx warnings 2018-02-08 22:07:58 -07:00
Frederic Fortier 403d7f9c29 Merge branch 'develop' 2018-02-08 17:32:47 -05:00
Frederic Fortier 09ad397ee6 DOC: adjusted the release notes 2018-02-08 17:31:09 -05:00
Frederic Fortier e56e1f8e21 BUG: fixed an issue with open orders 2018-02-08 17:26:48 -05:00
Frederic Fortier 00f232e2d7 BUG: fixed sample algo 2018-02-08 17:10:41 -05:00
Frederic Fortier a820f66bdc BLD: adjusted unit test 2018-02-08 16:57:23 -05:00
Frederic Fortier 82d318a1c0 BUG: fixed issue #216 with bad candle data 2018-02-08 15:51:55 -05:00
Frederic Fortier 97afbf781e Merge branch 'master' into develop 2018-02-08 01:13:29 -05:00
Frederic Fortier 43a5f8858b DOC: updated release number 2018-02-08 01:11:09 -05:00
Frederic Fortier dc04cd8781 Merge branch 'master' into develop 2018-02-08 00:59:46 -05:00
Frederic Fortier 793b6e92d8 BLD: included marketplace dependencies 2018-02-08 00:47:25 -05:00
Frederic Fortier c0b9939580 Merge branch 'develop' 2018-02-08 00:33:55 -05:00
Frederic Fortier e248831719 BLD: adjusted sample algo 2018-02-08 00:27:56 -05:00
Frederic Fortier 25f1f6e641 BLD: upgraded CCXT 2018-02-08 00:18:48 -05:00
Frederic Fortier d2f9762fbf BLD: adjusting the marketplace sample algo 2018-02-08 00:02:40 -05:00
Victor Grau Serrat 7a89cfc02b BUG: marketplace: balance returns different types 2018-02-07 21:01:21 -07:00
Frederic Fortier 64e22ba27d BLD: Updated release notes for 5.0 2018-02-07 22:28:08 -05:00
Frederic Fortier 8c58916bb1 Merge remote-tracking branch 'origin/develop' into develop 2018-02-07 22:17:05 -05:00
Frederic Fortier 18cdd51680 BUG: fixed issue with order processing 2018-02-07 22:16:32 -05:00
Victor Grau Serrat db7b7639a0 MAINT: not listing test datasets 2018-02-07 17:34:11 -07:00
Frederic Fortier 1ad26b0c39 BLD: trying to format a line 2018-02-07 19:29:38 -05:00
Victor Grau Serrat e1abecb556 MAINT: updated catalyst logo 2018-02-07 17:09:49 -07:00
Victor Grau Serrat 6e642f45d8 MAINT: marketplace prompts 2018-02-07 14:44:11 -07:00
Frederic Fortier 9f9bfc9df0 BLD: dropping dataset index cols to avoid duplicates 2018-02-07 16:40:43 -05:00
Frederic Fortier 866b92910f BLD: fixed the marketplace api in the algo runtime 2018-02-07 13:10:07 -05:00
Victor Grau Serrat e071f6ec8d BLD: implemented grains, catch JSON malformed in addresses.json 2018-02-07 10:57:07 -07:00
lenak25 3eff58fcf9 Merge branch 'develop' of github.com:enigmampc/catalyst into develop 2018-02-07 12:49:12 +02:00
lenak25 540358d0f9 BLD: new marketplace contract address 2018-02-07 12:48:24 +02:00
Victor Grau Serrat 0e3be98d24 BLD: mmarketplace: switched encoding to Web3.toHex() 2018-02-07 01:44:16 -07:00
Victor Grau Serrat c39e075766 MAINT: constants points to contract address/abi in develop branch 2018-02-06 23:50:24 -07:00
Frederic Fortier 54935dd6f6 Merge remote-tracking branch 'origin/develop' into develop 2018-02-07 01:46:45 -05:00
Frederic Fortier 093660ab1a BLD: simplified unit tests until the next release 2018-02-07 01:46:33 -05:00
Victor Grau Serrat fb65839032 BLD: marketplace: getkeysecret is authenticated 2018-02-06 23:33:42 -07:00
Frederic Fortier 461a5942fb BUG: fixed Python 2 issue EXPERIMENTAL 2018-02-07 01:17:00 -05:00
Frederic Fortier eee2e1be88 BUG: fixed Python 2 issue 2018-02-07 00:48:17 -05:00
Frederic Fortier 2d43955abf BUG: fixed Python 2 issue 2018-02-07 00:45:33 -05:00
Frederic Fortier c5bed6e8c4 BLD: made some adjustments during testing 2018-02-06 21:15:30 -05:00
Frederic Fortier 2320a1432a BLD: Merge remote-tracking branch 'remotes/origin/data-marketplace' into develop 2018-02-06 14:34:07 -05:00
Frederic Fortier 5d74cd6f89 BLD: Merge remote-tracking branch 'remotes/origin/data-marketplace' into develop 2018-02-06 14:32:59 -05:00
Frederic Fortier 273408e9ed Merge remote-tracking branch 'origin/data-marketplace' into data-marketplace 2018-02-06 14:18:00 -05:00
Frederic Fortier 5ccddf6d21 BLD: removed dummy smart contract 2018-02-06 14:17:48 -05:00
Victor Grau Serrat d97890fc4e BLD: marketplace: moving AUTH_SERVER paths to /marketplace/* 2018-02-06 11:43:41 -07:00
Victor Grau Serrat 4c1a9b1dd7 BLD: marketplace added listing of datasets 2018-02-05 22:58:40 -07:00
Victor Grau Serrat 2168fb0d5b BUG: address json initialized with array of 1 dict, instead of dict 2018-02-05 22:17:21 -07:00
Frederic Fortier 3bc54a6c2e BLD: finalized the register implementation 2018-02-05 23:46:09 -05:00
Frederic Fortier 444fcbb2b3 BLD: adjustments for the new contract and developing register 2018-02-05 23:04:23 -05:00
Isan-Rivkin 881e3e5953 REV:constants.py revert 2018-02-04 08:51:27 -08:00
Isan-Rivkin 5889f4c74b BLD: added smart contract addresses 2018-02-04 08:45:06 -08:00
Isan-Rivkin ee93b16558 BLD:Solidity contract addressed added to constants 2018-02-04 08:42:42 -08:00
lenak25 e94c9db2ec Updated contract 2018-02-04 18:29:11 +02:00
Frederic Fortier 9f88e7a003 BUG: for issue #183, added more logic to catch order amount adjustments 2018-02-03 18:24:28 -05:00
Frederic Fortier b9bedfda21 BLD: enhancing registration features 2018-02-02 17:23:13 -05:00
Frederic Fortier d1529afeb3 BLD: enhancing registration features 2018-02-02 17:10:10 -05:00
Frederic Fortier 99dece953f BLD: ingesting marketplace bundles 2018-02-02 16:20:33 -05:00
Victor Grau Serrat 9ef000e529 BUG: balance returns different types 2018-02-02 12:06:57 -07:00
Victor Grau Serrat db4ee4c01e BLD: check for existence of dataset in ingestion & subscription 2018-02-01 23:42:40 -07:00
Victor Grau Serrat 0decdf5aab BLD: check for name duplicates when registering dataset 2018-02-01 22:30:24 -07:00
Victor Grau Serrat e460290056 BLD: better exception handling for failed multipart download 2018-02-01 21:59:30 -07:00
Frederic Fortier b9130fd968 BLD: added missing requirement 2018-02-01 22:03:16 -05:00
Frederic Fortier 37980f3580 BLD: working on marketplace integration 2018-02-01 20:43:48 -05:00
Victor Grau Serrat 56bd3db7a2 BLD: marketplace - ingestion downloads multiple files 2018-02-01 16:54:24 -07:00
Victor Grau Serrat 18123d7f7e BLD: marketplace constants in constants.py 2018-02-01 11:47:25 -07:00
Victor Grau Serrat a04a99373d BLD: marketplace: subscription to dataset 2018-02-01 08:07:17 -07:00
Victor Grau Serrat e906315969 BLD: marketplace: enigma contract files 2018-01-31 22:06:52 -07:00
Frederic Fortier a360a5fe3a Housekeeping 2018-01-31 23:57:22 -05:00
Frederic Fortier fcbdc131ec BLD: trying to catch as many trades as possible 2018-01-31 22:51:41 -05:00
Frederic Fortier 857a5d8a91 BUG: for issue #178, modified logic to track adjusted order amount 2018-01-31 21:57:33 -05:00
Frederic Fortier e914481325 BLD: minor adjustment 2018-01-31 17:19:09 -05:00
Frederic Fortier c0ba8b2ebb BUG: for issue #178, adjusted the fallback processing of orders for exchanges lacking a "my trades" api 2018-01-31 17:12:05 -05:00
Frederic Fortier 69507d1b00 BLD: for issue #178, fixed the "set" issue when fetching a ticker from positions 2018-01-31 16:32:22 -05:00
Frederic Fortier 311e357451 BLD: updated CCXT 2018-01-31 16:10:39 -05:00
Victor Grau Serrat d1cd95c492 BLD: marketplace subscribe + refactoring 2018-01-31 13:39:59 -07:00
Victor Grau Serrat 0f55f0e9a6 BLD: passing dataset to marketplace publish request 2018-01-31 10:50:10 -07:00
Victor Grau Serrat c26d78cf05 BLD: set AUTH_SERVER as a Catalyst constant 2018-01-30 11:20:06 -07: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 18c96303a6 BLD: improved unit test 2018-01-29 19:20:55 -05:00
Frederic Fortier 5c236a65f7 BUG: for issue #178, checking the order status instead of relying on the open amount 2018-01-29 19:20:34 -05:00
izokayandGitHub 1f29f7fdf1 Update beginner-tutorial.rst 2018-01-29 17:31:32 -05:00
izokayandGitHub cbae2465d2 Update beginner-tutorial.rst 2018-01-29 17:27:58 -05:00
izokayandGitHub 4a4e32846b Update beginner-tutorial.rst 2018-01-29 17:27:14 -05:00
izokayandGitHub 426fde40b1 Update beginner-tutorial.rst 2018-01-29 17:24:21 -05:00
izokayandGitHub e9a3bcf3e9 Update beginner-tutorial.rst 2018-01-29 17:23:22 -05:00
izokayandGitHub 5b77f5eeba Update beginner-tutorial.rst 2018-01-29 17:23:00 -05:00
izokayandGitHub 5417881a72 Update beginner-tutorial.rst 2018-01-29 17:21:53 -05:00
izokayandGitHub 1cadaf1ed0 Update beginner-tutorial.rst 2018-01-29 17:19:44 -05:00
izokayandGitHub 929e0a9a01 Update beginner-tutorial.rst 2018-01-29 17:18:16 -05:00
izokayandGitHub 15aadbb101 documentation for PyCharm 2018-01-29 17:14:15 -05:00
Frederic Fortier 7f021acb2e BUG: fixed catalyst import 2018-01-29 15:58:34 -05:00
Victor Grau Serrat 68faad4098 BLD: publish dataset in marketplace 2018-01-26 15:32:53 -07:00
Victor Grau Serrat ff989ba524 BLD: register dataset in marketplace 2018-01-25 22:39:54 -07:00
Frederic Fortier fa60457a0f BLD: misc adjustments to fetch all candles on the Binance exchange 2018-01-25 23:38:37 -05:00
Frederic Fortier 43737a9730 Merge branch 'alexiri-patch-1' into develop 2018-01-25 17:52:41 -05:00
Frederic Fortier e6fb708c1c Merge branch 'patch-1' of https://github.com/alexiri/catalyst into alexiri-patch-1 2018-01-25 17:52:22 -05:00
Frederic Fortier 8ff8e6458e BUG: fixed stats output issue #171 by adding orders and transactions in the header 2018-01-25 17:43:00 -05:00
Frederic Fortier 7d24433a42 Merge remote-tracking branch 'origin/develop' into develop 2018-01-25 17:41:02 -05:00
Frederic Fortier 7ae23e2340 BLD: conditionally fetching single or multi tickers for performance reasons 2018-01-25 17:40:45 -05:00
Avishai WeingartenandVictor Grau Serrat 940f625ec1 fix for click.echo
added sys.stdout to click.echo to prevent errors on jupyter
2018-01-25 12:46:27 -07:00
Victor Grau Serrat 2ec9aa2ca9 BLD: CLI implementation for the marketplace 2018-01-25 11:55:25 -07:00
Avishai WeingartenandGitHub c6dfd502d2 fix for click.echo
added sys.stdout to click.echo to prevent errors on jupyter
2018-01-25 16:02:44 +02:00
Frederic Fortier 0aa8c91577 BLD: for issue #174, re-implemented the fetch_tickers approach 2018-01-24 22:43:08 -05:00
Frederic Fortier c09f53449a BUG: fixed issue #176 with ignoring ticker errors instead of raising 2018-01-24 22:26:20 -05:00
Frederic Fortier f34a66e9c6 BUG: trying to fix issue #178 with Binance lot sizes 2018-01-24 21:54:05 -05:00
Victor Grau Serrat 04fed4140c BLD: sourcing contract address+abi from github 2018-01-24 14:12:59 -07:00
Victor Grau Serrat 76e183b5f7 BLD: contract address+abi on testnet 2018-01-24 12:48:42 -07:00
Frederic Fortier 911fb6e934 Merge branch 'gthouret-echo-usage-for-jupyter' 2018-01-24 00:33:52 -05:00
Frederic Fortier e8f98825e0 Merge branch 'treethought-empyrical-errors' into develop 2018-01-24 00:31:59 -05:00
Frederic Fortier 5619f6f451 Merge branch 'empyrical-errors' of https://github.com/treethought/catalyst into treethought-empyrical-errors 2018-01-24 00:31:49 -05:00
Cam Sweeney 8223d06d98 BUG: Use empyrical patches for persisting issue #126
The referenced issue was addressed via importing a set of patches
for empyrical. However the same error occurs occasionally when calling
the empyrical functions inside "/catalyst/finance/risk/period.py".

This PR simply applies 2 of the same patches in period.py.
I have only experienced problems with cum_returns and max_drawdown
thus far, but it is likely the other patches may be needed.
2018-01-23 02:42:55 -08:00
Frederic Fortier b47884a504 BLD: code formatting 2018-01-22 17:27:59 -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
Alex IribarrenandGitHub df4dd0f5a4 Update example-algos.rst
exchange_utils was moved.
2018-01-14 13:55:16 +01: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
Frederic Fortier 70b9fd8e03 BLD: completed data market place first integration and created an algo to test it 2018-01-12 00:39:14 -05:00
Frederic Fortier 21ad753fcf Merge remote-tracking branch 'origin/data-marketplace' into data-marketplace 2018-01-11 20:05:01 -05:00
Frederic Fortier 88aa7154db BLD: ingesting and cleaning marketplace data sources 2018-01-11 20:04:53 -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
VictorandGitHub b1d63c9a5b Update README.md 2018-01-11 12:15:49 -07:00
Frederic Fortier 3ea00e783c DOC: improved code comments 2018-01-11 14:08:52 -05:00
Frederic Fortier 9361570895 DOC: improved code comments 2018-01-11 14:08:02 -05:00
Frederic Fortier 50ded59c3c DOC: documented the marketplace structure 2018-01-11 14:01:00 -05: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 354e422be8 BLD: working on ingesting alt data sources 2018-01-10 21:54:09 -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
Frederic Fortier 1e4af12e3d BLD: improved smart contract and catalyst commands 2018-01-10 13:31:08 -05:00
Frederic Fortier e0c8178932 BLD: improved smart contract and catalyst commands 2018-01-09 22:42:09 -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 22a850ab14 BLD: testing basic smart contract 2018-01-09 20:41:03 -05:00
Frederic Fortier aab501aa17 BLD: first test of the marketplace smart contract 2018-01-09 19:08:53 -05:00
Frederic Fortier 94d5b4a4d5 BLD: defined first version of commands and marketplace class 2018-01-09 17:16:54 -05: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
116 changed files with 6578 additions and 2634 deletions
+1
View File
@@ -40,6 +40,7 @@ develop-eggs
coverage.xml
htmlcov
nosetests.xml
.python-version
# C Extensions
*.o
+2 -2
View File
@@ -1,11 +1,11 @@
#
# Dockerfile for an image with the currently checked out version of catalyst installed. To build:
#
# docker build -t quantopian/catalyst .
# docker build -t enigmampc/catalyst .
#
# To run the container:
#
# docker run -v /path/to/your/notebooks:/projects -v ~/.catalyst:/root/.catalyst -p 8888:8888/tcp --name catalyst -it quantopian/catalyst
# docker run -v /path/to/your/notebooks:/projects -v ~/.catalyst:/root/.catalyst -p 8888:8888/tcp --name catalyst -it enigmampc/catalyst
#
# To access Jupyter when running docker locally (you may need to add NAT rules):
#
+5 -5
View File
@@ -1,15 +1,15 @@
#
# Dockerfile for an image with the currently checked out version of catalyst installed. To build:
#
# docker build -t quantopian/catalystdev -f Dockerfile-dev .
# docker build -t enigmampc/catalystdev -f Dockerfile-dev .
#
# Note: the dev build requires a quantopian/catalyst image, which you can build as follows:
# Note: the dev build requires a enigmampc/catalyst image, which you can build as follows:
#
# docker build -t quantopian/catalyst -f Dockerfile .
# docker build -t enigmampc/catalyst -f Dockerfile .
#
# To run the container:
#
# docker run -v /path/to/your/notebooks:/projects -v ~/.catalyst:/root/.catalyst -p 8888:8888/tcp --name catalystdev -it quantopian/catalystdev
# docker run -v /path/to/your/notebooks:/projects -v ~/.catalyst:/root/.catalyst -p 8888:8888/tcp --name catalystdev -it enigmampc/catalystdev
#
# To access Jupyter when running docker locally (you may need to add NAT rules):
#
@@ -25,7 +25,7 @@
#
# docker exec -it catalystdev catalyst run -f /projects/my_algo.py --start 2015-1-1 --end 2016-1-1 /projects/result.pickle
#
FROM quantopian/catalyst
FROM enigmampc/catalyst
WORKDIR /catalyst
+11 -7
View File
@@ -1,10 +1,11 @@
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.png
:target: https://enigmampc.github.io/catalyst
:align: center
:alt: Enigma | Catalyst
|version tag|
|version status|
|forum|
|discord|
|twitter|
@@ -17,16 +18,16 @@ 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.
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst.
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.
existing trading algorithms, developer knowledge, and tutorials. Join us on the
`Catalyst Forum <https://catalyst.enigma.co/>`_ for questions around Catalyst,
algorithmic trading and technical support. We also have a
`Discord <https://discord.gg/SJK32GY>`_ group with the *#catalyst_dev* and
*#catalyst_setup* dedicated channels.
Overview
========
@@ -62,6 +63,9 @@ Go to our `Documentation Website <https://enigmampc.github.io/catalyst/>`_.
.. |version status| image:: https://img.shields.io/pypi/pyversions/enigma-catalyst.svg
:target: https://pypi.python.org/pypi/enigma-catalyst
.. |forum| image:: https://img.shields.io/badge/forum-join-green.svg
:target: https://catalyst.enigma.co/
.. |discord| image:: https://img.shields.io/badge/discord-join%20chat-green.svg
:target: https://discordapp.com/invite/SJK32GY
+168 -17
View File
@@ -3,8 +3,10 @@ import os
from functools import wraps
import click
import sys
import logbook
import pandas as pd
from catalyst.marketplace.marketplace import Marketplace
from six import text_type
from catalyst.data import bundles as bundles_module
@@ -257,7 +259,7 @@ def run(ctx,
if capital_base is None:
ctx.fail("must specify a capital base with '--capital-base'")
click.echo('Running in backtesting mode.')
click.echo('Running in backtesting mode.', sys.stdout)
perf = _run(
initialize=None,
@@ -282,13 +284,15 @@ def run(ctx,
exchange=exchange_name,
algo_namespace=algo_namespace,
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)
@@ -312,11 +316,11 @@ def catalyst_magic(line, cell=None):
'--algotext', cell,
'--output', os.devnull, # don't write the results by default
] + ([
# these options are set when running in line magic mode
# set a non None algo text to use the ipython user_ns
'--algotext', '',
'--local-namespace',
] if cell is None else []) + line.split(),
# these options are set when running in line magic mode
# set a non None algo text to use the ipython user_ns
'--algotext', '',
'--local-namespace',
] if cell is None else []) + line.split(),
'%s%%catalyst' % ((cell or '') and '%'),
# don't use system exit and propogate errors to the caller
standalone_mode=False,
@@ -393,6 +397,12 @@ def catalyst_magic(line, cell=None):
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,
@@ -406,6 +416,15 @@ def catalyst_magic(line, cell=None):
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,
@@ -418,7 +437,9 @@ def live(ctx,
exchange_name,
algo_namespace,
base_currency,
end,
live_graph,
auth_aliases,
simulate_orders):
"""Trade live with the given algorithm.
"""
@@ -441,10 +462,10 @@ def live(ctx,
ctx.fail("must specify a capital base with '--capital-base'")
if simulate_orders:
click.echo('Running in paper trading mode.')
click.echo('Running in paper trading mode.', sys.stdout)
else:
click.echo('Running in live trading mode.')
click.echo('Running in live trading mode.', sys.stdout)
perf = _run(
initialize=None,
@@ -460,7 +481,7 @@ def live(ctx,
bundle=None,
bundle_timestamp=None,
start=None,
end=None,
end=end,
output=output,
print_algo=print_algo,
local_namespace=local_namespace,
@@ -470,12 +491,14 @@ def live(ctx,
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))
click.echo(str(perf), sys.stdout)
elif output != os.devnull: # make the catalyst magic not write any data
perf.to_pickle(output)
@@ -557,7 +580,8 @@ def ingest_exchange(ctx, exchange_name, data_frequency, start, end,
exchange_bundle = ExchangeBundle(exchange_name)
click.echo('Ingesting exchange bundle {}...'.format(exchange_name))
click.echo('Trying to ingest exchange bundle {}...'.format(exchange_name),
sys.stdout)
exchange_bundle.ingest(
data_frequency=data_frequency,
include_symbols=include_symbols,
@@ -580,10 +604,11 @@ def ingest_exchange(ctx, exchange_name, data_frequency, start, end,
@click.pass_context
def clean_algo(ctx, algo_namespace):
click.echo(
'Cleaning algo state: {}'.format(algo_namespace)
'Cleaning algo state: {}'.format(algo_namespace),
sys.stdout
)
delete_algo_folder(algo_namespace)
click.echo('Done')
click.echo('Done', sys.stdout)
@main.command(name='clean-exchange')
@@ -610,11 +635,12 @@ def clean_exchange(ctx, exchange_name, data_frequency):
exchange_bundle = ExchangeBundle(exchange_name)
click.echo('Cleaning exchange bundle {}...'.format(exchange_name))
click.echo('Cleaning exchange bundle {}...'.format(exchange_name),
sys.stdout)
exchange_bundle.clean(
data_frequency=data_frequency,
)
click.echo('Done')
click.echo('Done', sys.stdout)
@main.command()
@@ -735,7 +761,132 @@ 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)
@main.group()
@click.pass_context
def marketplace(ctx):
"""Access the Enigma Data Marketplace to:\n
- Register and Publish new datasets (seller-side)\n
- Subscribe and Ingest premium datasets (buyer-side)\n
"""
pass
@marketplace.command()
@click.pass_context
def ls(ctx):
"""List all available datasets.
"""
click.echo('Listing of available data sources on the marketplace:',
sys.stdout)
marketplace = Marketplace()
marketplace.list()
@marketplace.command()
@click.option(
'--dataset',
default=None,
help='The name of the dataset to ingest from the Data Marketplace.',
)
@click.pass_context
def subscribe(ctx, dataset):
"""Subscribe to an existing dataset.
"""
marketplace = Marketplace()
marketplace.subscribe(dataset)
@marketplace.command()
@click.option(
'--dataset',
default=None,
help='The name of the dataset to ingest from the Data Marketplace.',
)
@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.pass_context
def ingest(ctx, dataset, data_frequency, start, end):
"""Ingest a dataset (requires subscription).
"""
marketplace = Marketplace()
marketplace.ingest(dataset, data_frequency, start, end)
@marketplace.command()
@click.option(
'--dataset',
default=None,
help='The name of the dataset to ingest from the Data Marketplace.',
)
@click.pass_context
def clean(ctx, dataset):
"""Clean/Remove local data for a given dataset.
"""
marketplace = Marketplace()
marketplace.clean(dataset)
@marketplace.command()
@click.pass_context
def register(ctx):
"""Register a new dataset.
"""
marketplace = Marketplace()
marketplace.register()
@marketplace.command()
@click.option(
'--dataset',
default=None,
help='The name of the Marketplace dataset to publish data for.',
)
@click.option(
'--datadir',
default=None,
help='The folder that contains the CSV data files to publish.',
)
@click.option(
'--watch/--no-watch',
is_flag=True,
default=False,
help='Whether to watch the datadir for live data.',
)
@click.pass_context
def publish(ctx, dataset, datadir, watch):
"""Publish data for a registered dataset.
"""
marketplace = Marketplace()
if dataset is None:
ctx.fail("must specify a dataset to publish data for "
" with '--dataset'\n")
if datadir is None:
ctx.fail("must specify a datadir where to find the files to publish "
" with '--datadir'\n")
marketplace.publish(dataset, datadir, watch)
if __name__ == '__main__':
+1 -2
View File
@@ -16,7 +16,6 @@ import warnings
from contextlib import contextmanager
from functools import wraps
from pandas.tslib import normalize_date
import pandas as pd
import numpy as np
@@ -564,7 +563,7 @@ cdef class BarData:
})
cdef bool _is_stale_for_asset(self, asset, dt, adjusted_dt, data_portal):
session_label = normalize_date(dt) # FIXME
session_label = dt.normalize_date() # FIXME
if not asset.is_alive_for_session(session_label):
return False
+8 -9
View File
@@ -21,7 +21,6 @@ import logbook
import pytz
import pandas as pd
from contextlib2 import ExitStack
from pandas.tseries.tools import normalize_date
import numpy as np
from itertools import chain, repeat
@@ -939,7 +938,7 @@ class TradingAlgorithm(object):
The field to query. The options have the following meanings:
arena : str
The arena from the simulation parameters. This will normally
be ``'backtest'`` but some systems may use this distinguish
be ``backtest`` but some systems may use this distinguish
live trading from backtesting.
data_frequency : {'daily', 'minute'}
data_frequency tells the algorithm if it is running with
@@ -954,7 +953,7 @@ class TradingAlgorithm(object):
The platform that the code is running on. By default this
will be the string 'catalyst'. This can allow algorithms to
know if they are running on the Quantopian platform instead.
* : dict[str -> any]
\* : dict[str -> any]
Returns all of the fields in a dictionary.
Returns
@@ -1032,7 +1031,7 @@ class TradingAlgorithm(object):
argument is the name of the column in the preprocessed dataframe
containing the symbols. This will be used along with the date
information to map the sids in the asset finder.
**kwargs
\*\*kwargs
Forwarded to :func:`pandas.read_csv`.
Returns
@@ -1156,7 +1155,7 @@ class TradingAlgorithm(object):
Parameters
----------
**kwargs
\*\*kwargs
The names and values to record.
Notes
@@ -1273,7 +1272,7 @@ class TradingAlgorithm(object):
Parameters
----------
*args : iterable[str]
\*args : iterable[str]
The ticker symbols to lookup.
Returns
@@ -1345,7 +1344,7 @@ class TradingAlgorithm(object):
# Make sure the asset exists, and that there is a last price for it.
# FIXME: we should use BarData's can_trade logic here, but I haven't
# yet found a good way to do that.
normalized_date = normalize_date(self.datetime)
normalized_date = self.datetime.normalize()
if normalized_date < asset.start_date:
raise CannotOrderDelistedAsset(
@@ -1392,7 +1391,7 @@ class TradingAlgorithm(object):
)
if asset.auto_close_date:
day = normalize_date(self.get_datetime())
day = self.get_datetime().normalize()
if day > min(asset.end_date, asset.auto_close_date):
# If we are after the asset's end date or auto close date, warn
@@ -2475,7 +2474,7 @@ class TradingAlgorithm(object):
"""
Internal implementation of `pipeline_output`.
"""
today = normalize_date(self.get_datetime())
today = self.get_datetime().normalize()
data = NO_DATA = object()
try:
data = self._pipeline_cache.unwrap(today)
+65 -8
View File
@@ -34,6 +34,7 @@ def attach_pipeline(pipeline, name, chunks=None):
:func:`catalyst.api.pipeline_output`
"""
def batch_market_order(share_counts):
"""Place a batch market order for multiple assets.
@@ -48,6 +49,7 @@ def batch_market_order(share_counts):
Index of ids for newly-created orders.
"""
def cancel_order(order_param):
"""Cancel an open order.
@@ -57,7 +59,9 @@ def cancel_order(order_param):
The order_id or order object to cancel.
"""
def continuous_future(root_symbol_str, offset=0, roll='volume', adjustment='mul'):
def continuous_future(root_symbol_str, offset=0, roll='volume',
adjustment='mul'):
"""Create a specifier for a continuous contract.
Parameters
@@ -81,7 +85,10 @@ def continuous_future(root_symbol_str, offset=0, roll='volume', adjustment='mul'
The continuous future specifier.
"""
def fetch_csv(url, pre_func=None, post_func=None, date_column='date', date_format=None, timezone='UTC', symbol=None, mask=True, symbol_column=None, special_params_checker=None, **kwargs):
def fetch_csv(url, pre_func=None, post_func=None, date_column='date',
date_format=None, timezone='UTC', symbol=None, mask=True,
symbol_column=None, special_params_checker=None, **kwargs):
"""Fetch a csv from a remote url and register the data so that it is
queryable from the ``data`` object.
@@ -125,6 +132,7 @@ def fetch_csv(url, pre_func=None, post_func=None, date_column='date', date_forma
A requests source that will pull data from the url specified.
"""
def future_symbol(symbol):
"""Lookup a futures contract with a given symbol.
@@ -144,6 +152,7 @@ def future_symbol(symbol):
Raised when no contract named 'symbol' is found.
"""
def get_datetime(tz=None):
"""
Returns the current simulation datetime.
@@ -159,6 +168,7 @@ dt : datetime
The current simulation datetime converted to ``tz``.
"""
def get_environment(field='platform'):
"""Query the execution environment.
@@ -198,6 +208,7 @@ def get_environment(field='platform'):
Raised when ``field`` is not a valid option.
"""
def get_order(order_id):
"""Lookup an order based on the order id returned from one of the
order functions.
@@ -213,10 +224,12 @@ def get_order(order_id):
The order object.
"""
def history(bar_count, frequency, field, ffill=True):
"""DEPRECATED: use ``data.history`` instead.
"""
def order(asset, amount, limit_price=None, stop_price=None, style=None):
"""Place an order.
@@ -258,7 +271,9 @@ def order(asset, amount, limit_price=None, stop_price=None, style=None):
:func:`catalyst.api.order_percent`
"""
def order_percent(asset, percent, limit_price=None, stop_price=None, style=None):
def order_percent(asset, percent, limit_price=None, stop_price=None,
style=None):
"""Place an order in the specified asset corresponding to the given
percent of the current portfolio value.
@@ -293,6 +308,7 @@ def order_percent(asset, percent, limit_price=None, stop_price=None, style=None)
:func:`catalyst.api.order_value`
"""
def order_target(asset, target, limit_price=None, stop_price=None, style=None):
"""Place an order to adjust a position to a target number of shares. If
the position doesn't already exist, this is equivalent to placing a new
@@ -344,7 +360,9 @@ def order_target(asset, target, limit_price=None, stop_price=None, style=None):
:func:`catalyst.api.order_target_value`
"""
def order_target_percent(asset, target, limit_price=None, stop_price=None, style=None):
def order_target_percent(asset, target, limit_price=None, stop_price=None,
style=None):
"""Place an order to adjust a position to a target percent of the
current portfolio value. If the position doesn't already exist, this is
equivalent to placing a new order. If the position does exist, this is
@@ -396,7 +414,9 @@ def order_target_percent(asset, target, limit_price=None, stop_price=None, style
:func:`catalyst.api.order_target_value`
"""
def order_target_value(asset, target, limit_price=None, stop_price=None, style=None):
def order_target_value(asset, target, limit_price=None, stop_price=None,
style=None):
"""Place an order to adjust a position to a target value. If
the position doesn't already exist, this is equivalent to placing a new
order. If the position does exist, this is equivalent to placing an
@@ -448,6 +468,7 @@ def order_target_value(asset, target, limit_price=None, stop_price=None, style=N
:func:`catalyst.api.order_target_percent`
"""
def order_value(asset, value, limit_price=None, stop_price=None, style=None):
"""Place an order by desired value rather than desired number of
shares.
@@ -488,6 +509,7 @@ def order_value(asset, value, limit_price=None, stop_price=None, style=None):
:func:`catalyst.api.order_percent`
"""
def pipeline_output(name):
"""Get the results of the pipeline that was attached with the name:
``name``.
@@ -514,6 +536,7 @@ def pipeline_output(name):
:meth:`catalyst.pipeline.engine.PipelineEngine.run_pipeline`
"""
def record(*args, **kwargs):
"""Track and record values each day.
@@ -529,7 +552,9 @@ def record(*args, **kwargs):
:func:`~catalyst.run_algorithm`.
"""
def schedule_function(func, date_rule=None, time_rule=None, half_days=True, calendar=None):
def schedule_function(func, date_rule=None, time_rule=None, half_days=True,
calendar=None):
"""Schedules a function to be called according to some timed rules.
Parameters
@@ -549,6 +574,7 @@ def schedule_function(func, date_rule=None, time_rule=None, half_days=True, cale
:class:`catalyst.api.time_rules`
"""
def set_asset_restrictions(restrictions, on_error='fail'):
"""Set a restriction on which assets can be ordered.
@@ -562,6 +588,7 @@ def set_asset_restrictions(restrictions, on_error='fail'):
catalyst.finance.asset_restrictions.Restrictions
"""
def set_benchmark(benchmark):
"""Set the benchmark asset.
@@ -576,6 +603,7 @@ def set_benchmark(benchmark):
automatically reinvested.
"""
def set_cancel_policy(cancel_policy):
"""Sets the order cancellation policy for the simulation.
@@ -590,6 +618,7 @@ def set_cancel_policy(cancel_policy):
:class:`catalyst.api.NeverCancel`
"""
def set_commission(commission):
"""Sets the commission model for the simulation.
@@ -605,6 +634,7 @@ def set_commission(commission):
:class:`catalyst.finance.commission.PerDollar`
"""
def set_do_not_order_list(restricted_list, on_error='fail'):
"""Set a restriction on which assets can be ordered.
@@ -614,11 +644,13 @@ def set_do_not_order_list(restricted_list, on_error='fail'):
The assets that cannot be ordered.
"""
def set_long_only(on_error='fail'):
"""Set a rule specifying that this algorithm cannot take short
positions.
"""
def set_max_leverage(max_leverage):
"""Set a limit on the maximum leverage of the algorithm.
@@ -629,6 +661,7 @@ def set_max_leverage(max_leverage):
be no maximum.
"""
def set_max_order_count(max_count, on_error='fail'):
"""Set a limit on the number of orders that can be placed in a single
day.
@@ -639,7 +672,9 @@ def set_max_order_count(max_count, on_error='fail'):
The maximum number of orders that can be placed on any single day.
"""
def set_max_order_size(asset=None, max_shares=None, max_notional=None, on_error='fail'):
def set_max_order_size(asset=None, max_shares=None, max_notional=None,
on_error='fail'):
"""Set a limit on the number of shares and/or dollar value of any single
order placed for sid. Limits are treated as absolute values and are
enforced at the time that the algo attempts to place an order for sid.
@@ -658,7 +693,9 @@ def set_max_order_size(asset=None, max_shares=None, max_notional=None, on_error=
The maximum value that can be ordered at one time.
"""
def set_max_position_size(asset=None, max_shares=None, max_notional=None, on_error='fail'):
def set_max_position_size(asset=None, max_shares=None, max_notional=None,
on_error='fail'):
"""Set a limit on the number of shares and/or dollar value held for the
given sid. Limits are treated as absolute values and are enforced at
the time that the algo attempts to place an order for sid. This means
@@ -681,6 +718,7 @@ def set_max_position_size(asset=None, max_shares=None, max_notional=None, on_err
The maximum value to hold for an asset.
"""
def set_slippage(slippage):
"""Set the slippage model for the simulation.
@@ -694,6 +732,7 @@ def set_slippage(slippage):
:class:`catalyst.finance.slippage.SlippageModel`
"""
def set_symbol_lookup_date(dt):
"""Set the date for which symbols will be resolved to their assets
(symbols may map to different firms or underlying assets at
@@ -705,6 +744,7 @@ def set_symbol_lookup_date(dt):
The new symbol lookup date.
"""
def sid(sid):
"""Lookup an Asset by its unique asset identifier.
@@ -724,6 +764,7 @@ def sid(sid):
When a requested ``sid`` does not map to any asset.
"""
def symbol(symbol_str):
"""Lookup an Equity by its ticker symbol.
@@ -748,6 +789,7 @@ def symbol(symbol_str):
:func:`catalyst.api.set_symbol_lookup_date`
"""
def symbols(*args):
"""Lookup multuple Equities as a list.
@@ -773,3 +815,18 @@ def symbols(*args):
:func:`catalyst.api.set_symbol_lookup_date`
"""
def get_dataset(ds_name, start=None, end=None):
"""
Lookup a data source from the marketplace
Parameters
----------
ds_name: str
start: pd.Timestamp
end: pd.Timestamp
Returns
-------
"""
+8 -3
View File
@@ -630,23 +630,28 @@ cdef class TradingPair(Asset):
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
# added arguments for catalyst
return (self.__class__, (self.symbol,
self.exchange,
self.start_date,
self.asset_name,
self.sid,
self.leverage,
self.end_daily,
self.end_minute,
self.end_date,
self.exchange_symbol,
self.first_traded,
self.auto_close_date,
self.exchange_full,
self.min_trade_size,
self.max_trade_size,
self.maker,
self.taker,
self.lot,
self.decimals,
self.taker,
self.maker))
self.trading_state,
self.data_source))
def make_asset_array(int size, Asset asset):
cdef np.ndarray out = np.empty([size], dtype=object)
+28
View File
@@ -15,4 +15,32 @@ SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M'
DATE_FORMAT = '%Y-%m-%d'
try:
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
except Exception as e:
print('unable to get catalyst path: {}'.format(e))
AUTO_INGEST = False
AUTH_SERVER = 'https://data.enigma.co'
ETH_REMOTE_NODE = 'https://mainnet.infura.io'
MARKETPLACE_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/' \
'catalyst/master/catalyst/marketplace/' \
'contract_marketplace_address.txt'
MARKETPLACE_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
'catalyst/master/catalyst/marketplace/' \
'contract_marketplace_abi.json'
ENIGMA_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/' \
'catalyst/master/catalyst/marketplace/' \
'contract_enigma_address.txt'
ENIGMA_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
'catalyst/master/catalyst/marketplace/' \
'contract_enigma_abi.json'
SUPPORTED_WALLETS = ['metamask', 'ledger', 'trezor', 'bitbox', 'keystore',
'key']
+4 -5
View File
@@ -20,7 +20,6 @@ import numpy as np
from numpy import float64, int64, nan
import pandas as pd
from pandas import isnull
from pandas.tslib import normalize_date
from six import iteritems
from six.moves import reduce
@@ -439,7 +438,7 @@ class DataPortal(object):
(isinstance(asset, (Asset, ContinuousFuture))))
def _get_fetcher_value(self, asset, field, dt):
day = normalize_date(dt)
day = dt.normalize()
try:
return \
@@ -1130,7 +1129,7 @@ class DataPortal(object):
if self._asset_start_dates[sid] > dt:
raise NoTradeDataAvailableTooEarly(
sid=sid,
dt=normalize_date(dt),
dt=dt.normalize(),
start_dt=start_date
)
@@ -1138,7 +1137,7 @@ class DataPortal(object):
if self._asset_end_dates[sid] < dt:
raise NoTradeDataAvailableTooLate(
sid=sid,
dt=normalize_date(dt),
dt=dt.normalize(),
end_dt=end_date
)
@@ -1262,7 +1261,7 @@ class DataPortal(object):
if self._extra_source_df is None:
return []
day = normalize_date(dt)
day = dt.normalize()
if day in self._extra_source_df.index:
assets = self._extra_source_df.loc[day]['sid']
+2 -2
View File
@@ -88,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)
+2 -3
View File
@@ -21,7 +21,6 @@ from abc import (
from numpy import concatenate
from lru import LRU
from pandas import isnull
from pandas.tslib import normalize_date
from toolz import sliding_window
from six import with_metaclass
@@ -93,8 +92,8 @@ class HistoryCompatibleUSEquityAdjustmentReader(object):
The adjustments as a dict of loc -> Float64Multiply
"""
sid = int(asset)
start = normalize_date(dts[0])
end = normalize_date(dts[-1])
start = dts[0].normalize()
end = dts[-1].normalize()
adjs = {}
if field != 'volume':
mergers = self._adjustments_reader.get_adjustments_for_sid(
+3 -2
View File
@@ -101,7 +101,7 @@ def load_crypto_market_data(trading_day=None, trading_days=None,
trading_day = get_calendar('OPEN').trading_day
# TODO: consider making configurable
bm_symbol = 'btc_usdt'
bm_symbol = 'btc_usd'
# if trading_days is None:
# trading_days = get_calendar('OPEN').schedule
@@ -144,8 +144,9 @@ def load_crypto_market_data(trading_day=None, trading_days=None,
# breaks things and it's only needed here
from catalyst.exchange.utils.factory import get_exchange
exchange = get_exchange(
exchange_name='poloniex', base_currency='usdt'
exchange_name='bitfinex', base_currency='usd'
)
exchange.init()
benchmark_asset = exchange.get_asset(bm_symbol)
+1 -2
View File
@@ -49,7 +49,6 @@ from pandas import (
to_datetime,
Timestamp,
)
from pandas.tslib import iNaT
from six import (
iteritems,
string_types,
@@ -422,7 +421,7 @@ class BcolzDailyBarWriter(object):
)
full_table.attrs['first_trading_day'] = (
earliest_date if earliest_date is not None else iNaT
earliest_date if earliest_date is not None else NaT
)
full_table.attrs['first_row'] = first_row
+3 -3
View File
@@ -23,7 +23,7 @@ from catalyst.api import (order_target_value, symbol, record,
def initialize(context):
context.ASSET_NAME = 'btc_usd'
context.ASSET_NAME = 'btc_usdt'
context.TARGET_HODL_RATIO = 0.8
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
@@ -140,9 +140,9 @@ if __name__ == '__main__':
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
exchange_name='poloniex',
algo_namespace='buy_and_hodl',
base_currency='usd',
base_currency='usdt',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
+3 -3
View File
@@ -27,7 +27,7 @@ import pandas as pd
def initialize(context):
context.asset = symbol('btc_usd')
context.asset = symbol('btc_usdt')
def handle_data(context, data):
@@ -41,9 +41,9 @@ if __name__ == '__main__':
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
exchange_name='bitfinex',
exchange_name='poloniex',
algo_namespace='buy_and_hodl',
base_currency='usd',
base_currency='usdt',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
+4 -5
View File
@@ -7,7 +7,6 @@ from catalyst.api import (
order_target_percent,
symbol,
record,
get_open_orders,
)
from catalyst.exchange.utils.stats_utils import get_pretty_stats
from catalyst.utils.run_algo import run_algorithm
@@ -60,7 +59,7 @@ def _handle_data(context, data):
rsi=rsi,
)
orders = get_open_orders(context.asset)
orders = context.blotter.open_orders
if orders:
log.info('skipping bar until all open orders execute')
return
@@ -143,14 +142,14 @@ def analyze(context, stats):
if __name__ == '__main__':
live = False
live = True
if live:
run_algorithm(
capital_base=0.001,
capital_base=1000,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='binance',
exchange_name='bittrex',
live=True,
algo_namespace=algo_namespace,
base_currency='btc',
+18 -16
View File
@@ -4,8 +4,7 @@ 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.api import (record, symbol, order_target_percent,)
from catalyst.exchange.utils.stats_utils import extract_transactions
NAMESPACE = 'dual_moving_average'
@@ -32,16 +31,18 @@ def handle_data(context, data):
# 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,
short_data = data.history(context.asset,
'price',
bar_count=short_window,
frequency="1m",
).mean()
long_mavg = data.history(context.asset,
frequency="1T",
)
short_mavg = short_data.mean()
long_data = data.history(context.asset,
'price',
bar_count=long_window,
frequency="1m",
).mean()
frequency="1T",
)
long_mavg = long_data.mean()
# Let's keep the price of our asset in a more handy variable
price = data.current(context.asset, 'price')
@@ -61,7 +62,7 @@ def handle_data(context, data):
# 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)
orders = context.blotter.open_orders
if len(orders) > 0:
return
@@ -82,9 +83,9 @@ def handle_data(context, data):
def analyze(context, perf):
# Get the base_currency that was passed as a parameter to the simulation
base_currency = context.exchanges.values()[0].base_currency.upper()
exchange = list(context.exchanges.values())[0]
base_currency = exchange.base_currency.upper()
# First chart: Plot portfolio value using base_currency
ax1 = plt.subplot(411)
@@ -92,7 +93,7 @@ def analyze(context, perf):
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))
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)
@@ -103,9 +104,9 @@ def analyze(context, perf):
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))
ax2.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
transaction_df = extract_transactions(perf)
if not transaction_df.empty:
@@ -135,19 +136,20 @@ def analyze(context, perf):
ax3.legend_.remove()
ax3.set_ylabel('Percent Change')
start, end = ax3.get_ylim()
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
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))
ax4.yaxis.set_ticks(np.arange(0, end, end / 5))
plt.show()
if __name__ == '__main__':
run_algorithm(
capital_base=1000,
data_frequency='minute',
@@ -0,0 +1,70 @@
import pandas as pd
import matplotlib.pyplot as plt
from catalyst import run_algorithm
from catalyst.api import symbol, get_dataset
START = '2017-01-01'
END = '2017-12-31'
def initialize(context):
pass
def handle_data(context, data):
context.github = get_dataset('github')
context.github.sort_index(level=0, inplace=True)
context.zec = data.history(symbol('zec_usdt'),
['price', ],
bar_count=365,
frequency="1d")
context.xmr = data.history(symbol('xmr_usdt'),
['price', ],
bar_count=365,
frequency="1d")
def analyze(context=None, results=None):
ax1 = plt.subplot(211)
idx = pd.IndexSlice
df = context.github.loc[START:END].loc[
idx[:, [b'ZEC']], ['commits']].reset_index(
level='symbol', drop=True)
df.plot(ax=ax1, color='blue')
ax1.legend(loc=2)
ax1.set_title('Zcash')
ax2 = ax1.twinx()
context.zec['price'].loc[START:END].plot(ax=ax2, color='green')
ax2.legend(loc=1)
ax3 = plt.subplot(212)
idx = pd.IndexSlice
df = context.github.loc[START:END].loc[
idx[:, [b'XMR']], ['commits']].reset_index(
level='symbol', drop=True)
df.plot(ax=ax3, color='blue')
ax3.legend(loc=2)
ax3.set_title('Monero')
ax4 = ax3.twinx()
context.xmr['price'].loc[START:END].plot(ax=ax4, color='green')
ax4.legend(loc=1)
plt.show()
if __name__ == '__main__':
run_algorithm(
capital_base=1000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace='algo-github',
base_currency='usdt',
live=False,
start=pd.to_datetime(END, utc=True),
end=pd.to_datetime(END, utc=True),
)
@@ -0,0 +1,237 @@
# 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 pandas as pd
import talib
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import symbol, record, order_target_percent, get_dataset
from catalyst.exchange.utils.stats_utils import set_print_settings, \
get_pretty_stats
# 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.
df = get_dataset('testmarketcap2') # type: pd.DataFrame
# Picking a specific date in our DataFrame
first_dt = df.index.get_level_values(0)[0]
# Since we use a MultiIndex with date / symbol, picking a date will
# result in a new DataFrame for the selected date with a single
# symbol index
df = df.xs(first_dt, level=0)
# Keep only the top coins by market cap
df = df.loc[df['market_cap_usd'].isin(df['market_cap_usd'].nlargest(100))]
set_print_settings()
df.sort_values(by=['market_cap_usd'], ascending=True, inplace=True)
print('the marketplace data:\n{}'.format(df))
# Pick the 5 assets with the lowest market cap for trading
quote_currency = 'eth'
exchange = context.exchanges[next(iter(context.exchanges))]
symbols = [a.symbol for a in exchange.assets
if a.start_date < context.datetime]
context.assets = []
for currency, price in df['market_cap_usd'].iteritems():
if len(context.assets) >= 5:
break
s = '{}_{}'.format(currency.decode('utf-8'), quote_currency)
if s in symbols:
context.assets.append(symbol(s))
context.base_price = None
context.current_day = None
context.RSI_OVERSOLD = 55
context.RSI_OVERBOUGHT = 60
context.CANDLE_SIZE = '5T'
context.start_time = time.time()
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 = dict()
context.current_day = today
# Preparing dictionaries for asset-level data points
volumes = dict()
rsis = dict()
price_values = dict()
cash = context.portfolio.cash
for asset in context.assets:
# We're computing the volume-weighted-average-price of the security
# defined above, in the context.assets 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(
asset,
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(asset, 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 asset not in context.base_price:
# context.base_price[asset] = price
#
# base_price = context.base_price[asset]
# price_change = (price - base_price) / base_price
# Tracking the relevant data
volumes[asset] = current['volume']
rsis[asset] = rsi[-1]
price_values[asset] = price
# price_changes[asset] = price_change
# We are trying to avoid over-trading by limiting our trades to
# one per day.
if asset in context.traded_today:
continue
# Exit if we cannot trade
if not data.can_trade(asset):
continue
# 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[asset].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
target = 1.0 / len(context.assets)
order_target_percent(
asset, target, limit_price=limit_price
)
context.traded_today[asset] = 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(
asset, 0, limit_price=limit_price
)
context.traded_today[asset] = True
# 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(
current_price=price_values,
volume=volumes,
rsi=rsis,
cash=cash,
)
def analyze(context=None, perf=None):
stats = get_pretty_stats(perf)
print('the algo stats:\n{}'.format(stats))
pass
if __name__ == '__main__':
# The execution mode: backtest or live
live = False
if live:
run_algorithm(
capital_base=0.1,
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=100,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace=NAMESPACE,
base_currency='eth',
start=pd.to_datetime('2017-10-01', utc=True),
end=pd.to_datetime('2017-10-15', utc=True),
)
log.info('saved perf stats: {}'.format(out))
+15 -14
View File
@@ -33,18 +33,18 @@ def initialize(context):
# parameters or values you're going to use.
# In our example, we're looking at Neo in Ether.
context.market = symbol('eth_btc')
context.market = symbol('bnb_eth')
context.base_price = None
context.current_day = None
context.RSI_OVERSOLD = 50
context.RSI_OVERBOUGHT = 65
context.CANDLE_SIZE = '5T'
context.RSI_OVERSOLD = 60
context.RSI_OVERBOUGHT = 70
context.CANDLE_SIZE = '15T'
context.start_time = time.time()
# context.set_commission(maker=0.1, taker=0.2)
context.set_slippage(spread=0.0001)
context.set_commission(maker=0.001, taker=0.002)
context.set_slippage(spread=0.001)
def handle_data(context, data):
@@ -114,7 +114,7 @@ def handle_data(context, data):
# 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)
orders = context.blotter.open_orders
if len(orders) > 0:
log.info('exiting because orders are open: {}'.format(orders))
return
@@ -161,7 +161,7 @@ def analyze(context=None, perf=None):
import matplotlib.pyplot as plt
# The base currency of the algo exchange
base_currency = context.exchanges.values()[0].base_currency.upper()
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
@@ -244,21 +244,22 @@ def analyze(context=None, perf=None):
if __name__ == '__main__':
# The execution mode: backtest or live
live = False
live = True
if live:
run_algorithm(
capital_base=0.03,
capital_base=0.1,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
exchange_name='binance',
live=True,
algo_namespace=NAMESPACE,
base_currency='btc',
base_currency='eth',
live_graph=False,
simulate_orders=False,
stats_output=None,
# auth_aliases=dict(poloniex='auth2')
)
else:
@@ -273,14 +274,14 @@ if __name__ == '__main__':
# -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,
capital_base=0.035,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
base_currency='eth',
base_currency='btc',
start=pd.to_datetime('2017-10-01', utc=True),
end=pd.to_datetime('2017-11-10', utc=True),
output=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))
+3 -2
View File
@@ -66,7 +66,7 @@ def handle_data(context, data):
# Define portfolio optimization parameters
n_portfolios = 50000
results_array = np.zeros((3 + context.nassets, n_portfolios))
for p in xrange(n_portfolios):
for p in range(n_portfolios):
weights = np.random.random(context.nassets)
weights /= np.sum(weights)
w = np.asmatrix(weights)
@@ -146,4 +146,5 @@ if __name__ == '__main__':
start=start,
end=end,
exchange_name='poloniex',
capital_base=100000, )
capital_base=100000,
base_currency='usdt', )
+1 -1
View File
@@ -175,7 +175,7 @@ def handle_data(context, data):
def analyze(context=None, results=None):
import matplotlib.pyplot as plt
base_currency = context.exchanges.values()[0].base_currency.upper()
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)
+2 -2
View File
@@ -57,7 +57,7 @@ def analyze(context, perf):
log.info('the stats: {}'.format(get_pretty_stats(perf)))
# The base currency of the algo exchange
base_currency = context.exchanges.values()[0].base_currency.upper()
base_currency = list(context.exchanges.values())[0].base_currency.upper()
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
@@ -114,7 +114,7 @@ def analyze(context, perf):
if __name__ == '__main__':
mode = 'backtest'
mode = 'live'
if mode == 'backtest':
run_algorithm(
+3 -3
View File
@@ -41,8 +41,8 @@ from catalyst.exchange.utils.exchange_utils import get_exchange_symbols
def initialize(context):
context.i = -1 # minute counter
context.exchange = context.exchanges.values()[0].name.lower()
context.base_currency = context.exchanges.values()[0].base_currency.lower()
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):
@@ -65,7 +65,7 @@ def handle_data(context, data):
minutes = 30
# get lookback_days of history data: that is 'lookback' number of bins
lookback = one_day_in_minutes / minutes * lookback_days
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:
+354 -81
View File
@@ -6,23 +6,29 @@ from collections import defaultdict
import ccxt
import pandas as pd
import six
from catalyst.assets._assets import TradingPair
from ccxt import ExchangeNotAvailable, InvalidOrder
from ccxt import InvalidOrder, NetworkError, \
ExchangeError
from logbook import Logger
from six import string_types
from catalyst.algorithm import MarketOrder
from catalyst.assets._assets import TradingPair
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.exchange import Exchange
from catalyst.exchange.exchange_bundle import ExchangeBundle
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
ExchangeNotFoundError, CreateOrderError, InvalidHistoryTimeframeError
ExchangeNotFoundError, CreateOrderError, InvalidHistoryTimeframeError, \
UnsupportedHistoryFrequencyError
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
from catalyst.exchange.utils.exchange_utils import mixin_market_params, \
from_ms_timestamp, get_epoch, get_exchange_folder, get_catalyst_symbol, \
get_exchange_folder, get_catalyst_symbol, \
get_exchange_auth
from catalyst.exchange.utils.datetime_utils import from_ms_timestamp, \
get_epoch, \
get_periods_range
from catalyst.finance.order import Order, ORDER_STATUS
from catalyst.finance.transaction import Transaction
log = Logger('CCXT', level=LOG_LEVEL)
@@ -37,7 +43,8 @@ SUPPORTED_EXCHANGES = dict(
class CCXT(Exchange):
def __init__(self, exchange_name, key, secret, base_currency):
def __init__(self, exchange_name, key,
secret, password, base_currency):
log.debug(
'finding {} in CCXT exchanges:\n{}'.format(
exchange_name, ccxt.exchanges
@@ -54,7 +61,9 @@ class CCXT(Exchange):
self.api = exchange_attr({
'apiKey': key,
'secret': secret,
'password': password,
})
self.api.enableRateLimit = True
except Exception:
raise ExchangeNotFoundError(exchange_name=exchange_name)
@@ -70,6 +79,7 @@ class CCXT(Exchange):
self.max_requests_per_minute = 60
self.low_balance_threshold = 0.1
self.request_cpt = dict()
self._common_symbols = dict()
self.bundle = ExchangeBundle(self.name)
self.markets = None
@@ -105,7 +115,12 @@ class CCXT(Exchange):
with open(filename, 'w+') as f:
json.dump(self.markets, f, indent=4)
except ExchangeNotAvailable as e:
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch markets {}: {}'.format(
self.name, e
)
)
raise ExchangeRequestError(error=e)
self.load_assets()
@@ -175,6 +190,9 @@ class CCXT(Exchange):
if data_frequency == 'minute' and not freq.endswith('T'):
continue
elif data_frequency == 'hourly' and not freq.endswith('D'):
continue
elif data_frequency == 'daily' and not freq.endswith('D'):
continue
@@ -210,6 +228,21 @@ class CCXT(Exchange):
)
return market
def substitute_currency_code(self, currency, source='catalyst'):
if source == 'catalyst':
currency = currency.upper()
key = self.api.common_currency_code(currency)
self._common_symbols[key] = currency.lower()
return key
else:
if currency in self._common_symbols:
return self._common_symbols[currency]
else:
return currency.lower()
def get_symbol(self, asset_or_symbol, source='catalyst'):
"""
The CCXT symbol.
@@ -217,6 +250,7 @@ class CCXT(Exchange):
Parameters
----------
asset_or_symbol
source
Returns
-------
@@ -226,7 +260,13 @@ class CCXT(Exchange):
if source == 'ccxt':
if isinstance(asset_or_symbol, string_types):
parts = asset_or_symbol.split('/')
return '{}_{}'.format(parts[0].lower(), parts[1].lower())
base_currency = self.substitute_currency_code(
parts[0], source
)
quote_currency = self.substitute_currency_code(
parts[1], source
)
return '{}_{}'.format(base_currency, quote_currency)
else:
return asset_or_symbol.symbol
@@ -237,7 +277,13 @@ class CCXT(Exchange):
) else asset_or_symbol.symbol
parts = symbol.split('_')
return '{}/{}'.format(parts[0].upper(), parts[1].upper())
base_currency = self.substitute_currency_code(
parts[0], source
)
quote_currency = self.substitute_currency_code(
parts[1], source
)
return '{}/{}'.format(base_currency, quote_currency)
@staticmethod
def map_frequency(value, source='ccxt', raise_error=True):
@@ -362,7 +408,7 @@ class CCXT(Exchange):
timeframe, source='ccxt', raise_error=raise_error
)
def get_candles(self, freq, assets, bar_count=None, start_dt=None,
def get_candles(self, freq, assets, bar_count=1, start_dt=None,
end_dt=None):
is_single = (isinstance(assets, TradingPair))
if is_single:
@@ -371,17 +417,39 @@ class CCXT(Exchange):
symbols = self.get_symbols(assets)
timeframe = CCXT.get_timeframe(freq)
ms = None
if start_dt is not None:
delta = start_dt - get_epoch()
ms = int(delta.total_seconds()) * 1000
if timeframe not in self.api.timeframes:
freqs = [CCXT.get_frequency(t) for t in self.api.timeframes]
raise UnsupportedHistoryFrequencyError(
exchange=self.name,
freq=freq,
freqs=freqs,
)
if start_dt is not None and end_dt is not None:
raise ValueError(
'Please provide either start_dt or end_dt, not both.'
)
if start_dt is None:
if end_dt is None:
end_dt = pd.Timestamp.utcnow()
dt_range = get_periods_range(
end_dt=end_dt,
periods=bar_count,
freq=freq,
)
start_dt = dt_range[0]
delta = start_dt - get_epoch()
since = int(delta.total_seconds()) * 1000
candles = dict()
for asset in assets:
for index, asset in enumerate(assets):
ohlcvs = self.api.fetch_ohlcv(
symbol=symbols[0],
symbol=symbols[index],
timeframe=timeframe,
since=ms,
since=since,
limit=bar_count,
params={}
)
@@ -398,6 +466,9 @@ class CCXT(Exchange):
close=ohlcv[4],
volume=ohlcv[5]
))
candles[asset] = sorted(
candles[asset], key=lambda c: c['last_traded']
)
if is_single:
return six.next(six.itervalues(candles))
@@ -408,6 +479,7 @@ class CCXT(Exchange):
def _fetch_symbol_map(self, is_local):
try:
return self.fetch_symbol_map(is_local)
except ExchangeSymbolsNotFound:
return None
@@ -559,8 +631,12 @@ class CCXT(Exchange):
for key in balances:
balances_lower[key.lower()] = balances[key]
except Exception as e:
log.debug('error retrieving balances: {}', e)
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch balance {}: {}'.format(
self.name, e
)
)
raise ExchangeRequestError(error=e)
return balances_lower
@@ -682,18 +758,23 @@ class CCXT(Exchange):
side = 'buy' if amount > 0 else 'sell'
if hasattr(self.api, 'amount_to_lots'):
adj_amount = self.api.amount_to_lots(
symbol=symbol,
amount=abs(amount),
)
if adj_amount != abs(amount):
log.info(
'adjusted order amount {} to {} based on lot size'.format(
abs(amount), adj_amount,
# TODO: is this right?
if self.api.markets is None:
self.api.load_markets()
# https://github.com/ccxt/ccxt/issues/1483
adj_amount = round(abs(amount), asset.decimals)
market = self.api.markets[symbol]
if 'lots' in market and market['lots'] > amount:
raise CreateOrderError(
exchange=self.name,
e='order amount lower than the smallest lot: {}'.format(
amount
)
)
else:
adj_amount = abs(amount)
adj_amount = round(abs(amount), asset.decimals)
try:
result = self.api.create_order(
@@ -703,14 +784,34 @@ class CCXT(Exchange):
amount=adj_amount,
price=price
)
except ExchangeNotAvailable as e:
log.debug('unable to create order: {}'.format(e))
raise ExchangeRequestError(error=e)
except InvalidOrder as e:
log.warn('the exchange rejected the order: {}'.format(e))
raise CreateOrderError(exchange=self.name, error=e)
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to create order {} / {}: {}'.format(
self.name, symbol, e
)
)
raise ExchangeRequestError(error=e)
exchange_amount = None
if 'amount' in result and result['amount'] != adj_amount:
exchange_amount = result['amount']
elif 'info' in result:
if 'origQty' in result['info']:
exchange_amount = float(result['info']['origQty'])
if exchange_amount:
log.info(
'order amount adjusted by {} from {} to {}'.format(
self.name, adj_amount, exchange_amount
)
)
adj_amount = exchange_amount
if 'info' not in result:
raise ValueError('cannot use order without info attribute')
@@ -735,18 +836,128 @@ class CCXT(Exchange):
limit=None,
params=dict()
)
except Exception as e:
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch open orders {} / {}: {}'.format(
self.name, asset.symbol, e
)
)
raise ExchangeRequestError(error=e)
orders = []
for order_status in result:
order, executed_price = self._create_order(order_status)
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, asset_or_symbol=None):
def _process_order_fallback(self, order):
"""
Fallback method for exchanges which do not play nice with
fetch-my-trades. Apparently, about 60% of exchanges will return
the correct executed values with this method. Others will support
fetch-my-trades.
Parameters
----------
order: Order
Returns
-------
float
"""
exc_order, price = self.get_order(
order.id, order.asset, return_price=True
)
order.status = exc_order.status
order.commission = exc_order.commission
order.filled = exc_order.amount
transactions = []
if exc_order.status == ORDER_STATUS.FILLED:
if order.amount > exc_order.amount:
log.warn(
'executed order amount {} differs '
'from original'.format(
exc_order.amount, order.amount
)
)
order.check_triggers(
price=price,
dt=exc_order.dt,
)
transaction = Transaction(
asset=order.asset,
amount=order.amount,
dt=pd.Timestamp.utcnow(),
price=price,
order_id=order.id,
commission=order.commission,
)
transactions.append(transaction)
return transactions
def process_order(self, order):
# TODO: move to parent class after tracking features in the parent
if not self.api.has['fetchMyTrades']:
return self._process_order_fallback(order)
try:
all_trades = self.get_trades(order.asset)
except ExchangeRequestError as e:
log.warn(
'unable to fetch account trades, trying an alternate '
'method to find executed order {} / {}: {}'.format(
order.id, order.asset.symbol, e
)
)
return self._process_order_fallback(order)
transactions = []
trades = [t for t in all_trades if t['order'] == order.id]
if not trades:
log.debug(
'order {} / {} not found in trades'.format(
order.id, order.asset.symbol
)
)
return transactions
trades.sort(key=lambda t: t['timestamp'], reverse=False)
order.filled = 0
order.commission = 0
for trade in trades:
# status property will update automatically
filled = trade['amount'] * order.direction
order.filled += filled
commission = 0
if 'fee' in trade and 'cost' in trade['fee']:
commission = trade['fee']['cost']
order.commission += commission
order.check_triggers(
price=trade['price'],
dt=pd.to_datetime(trade['timestamp'], unit='ms', utc=True),
)
transaction = Transaction(
asset=order.asset,
amount=filled,
dt=pd.Timestamp.utcnow(),
price=trade['price'],
order_id=order.id,
commission=commission
)
transactions.append(transaction)
order.broker_order_id = ', '.join([t['id'] for t in trades])
return transactions
def get_order(self, order_id, asset_or_symbol=None, return_price=False):
if asset_or_symbol is None:
log.debug(
'order not found in memory, the request might fail '
@@ -758,12 +969,22 @@ class CCXT(Exchange):
order_status = self.api.fetch_order(id=order_id, symbol=symbol)
order, executed_price = self._create_order(order_status)
except Exception as e:
if return_price:
return order, executed_price
else:
return order
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch order {} / {}: {}'.format(
self.name, order_id, e
)
)
raise ExchangeRequestError(error=e)
return order, executed_price
def cancel_order(self, order_param, asset_or_symbol=None):
def cancel_order(self, order_param,
asset_or_symbol=None, params={}):
order_id = order_param.id \
if isinstance(order_param, Order) else order_param
@@ -775,12 +996,18 @@ class CCXT(Exchange):
try:
symbol = self.get_symbol(asset_or_symbol) \
if asset_or_symbol is not None else None
self.api.cancel_order(id=order_id, symbol=symbol)
self.api.cancel_order(id=order_id,
symbol=symbol, params= params)
except Exception as e:
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to cancel order {} / {}: {}'.format(
self.name, order_id, e
)
)
raise ExchangeRequestError(error=e)
def tickers(self, assets):
def tickers(self, assets, on_ticker_error='raise'):
"""
Retrieve current tick data for the given assets
@@ -793,48 +1020,70 @@ class CCXT(Exchange):
list[dict[str, float]
"""
tickers = dict()
try:
for asset in assets:
symbol = self.get_symbol(asset)
# TODO: use fetch_tickers() for efficiency
# I tried using fetch_tickers() but noticed some
# inconsistencies, see issue:
# https://github.com/ccxt/ccxt/issues/870
ticker = self.api.fetch_ticker(symbol=symbol)
if not ticker:
log.warn('ticker not found for {} {}'.format(
self.name, symbol
))
continue
if len(assets) == 1:
try:
symbol = self.get_symbol(assets[0])
log.debug('fetching single ticker: {}'.format(symbol))
results = dict()
results[symbol] = self.api.fetch_ticker(symbol=symbol)
ticker['last_traded'] = from_ms_timestamp(ticker['timestamp'])
if 'last_price' not in ticker:
# TODO: any more exceptions?
ticker['last_price'] = ticker['last']
if 'baseVolume' in ticker and ticker['baseVolume'] is not None:
# Using the volume represented in the base currency
ticker['volume'] = ticker['baseVolume']
elif 'info' in ticker and 'bidQty' in ticker['info'] \
and 'askQty' in ticker['info']:
ticker['volume'] = float(ticker['info']['bidQty']) + \
float(ticker['info']['askQty'])
else:
ticker['volume'] = 0
tickers[asset] = ticker
except ExchangeNotAvailable as e:
log.warn(
'unable to fetch ticker: {} {}'.format(
self.name, asset.symbol
except (ExchangeError, NetworkError,) as e:
log.warn(
'unable to fetch ticker {} / {}: {}'.format(
self.name, symbol, e
)
)
)
raise ExchangeRequestError(error=e)
raise ExchangeRequestError(error=e)
elif len(assets) > 1:
symbols = self.get_symbols(assets)
try:
log.debug('fetching multiple tickers: {}'.format(symbols))
results = self.api.fetch_tickers(symbols=symbols)
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch tickers {} / {}: {}'.format(
self.name, symbols, e
)
)
raise ExchangeRequestError(error=e)
else:
raise ValueError('Cannot request tickers with not assets.')
tickers = dict()
for asset in assets:
symbol = self.get_symbol(asset)
if symbol not in results:
msg = 'ticker not found {} / {}'.format(
self.name, symbol
)
log.warn(msg)
if on_ticker_error == 'warn':
continue
else:
raise ExchangeRequestError(error=msg)
ticker = results[symbol]
ticker['last_traded'] = from_ms_timestamp(ticker['timestamp'])
if 'last_price' not in ticker:
# TODO: any more exceptions?
ticker['last_price'] = ticker['last']
if 'baseVolume' in ticker and ticker['baseVolume'] is not None:
# Using the volume represented in the base currency
ticker['volume'] = ticker['baseVolume']
elif 'info' in ticker and 'bidQty' in ticker['info'] \
and 'askQty' in ticker['info']:
ticker['volume'] = float(ticker['info']['bidQty']) + \
float(ticker['info']['askQty'])
else:
ticker['volume'] = 0
tickers[asset] = ticker
return tickers
@@ -864,3 +1113,27 @@ class CCXT(Exchange):
))
return result
def get_trades(self, asset, my_trades=True, start_dt=None, limit=100):
if not my_trades:
raise NotImplemented(
'get_trades only supports "my trades"'
)
# TODO: is it possible to sort this? Limit is useless otherwise.
ccxt_symbol = self.get_symbol(asset)
try:
trades = self.api.fetch_my_trades(
symbol=ccxt_symbol,
since=start_dt,
limit=limit,
)
except (ExchangeError, NetworkError) as e:
log.warn(
'unable to fetch trades {} / {}: {}'.format(
self.name, asset.symbol, e
)
)
raise ExchangeRequestError(error=e)
return trades
+113 -56
View File
@@ -5,20 +5,22 @@ from time import sleep
import numpy as np
import pandas as pd
from logbook import Logger
from catalyst.constants import LOG_LEVEL
from catalyst.data.data_portal import BASE_FIELDS
from catalyst.exchange.exchange_bundle import ExchangeBundle
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
SymbolNotFoundOnExchange, \
PricingDataNotLoadedError, \
NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError, \
NoDataAvailableOnExchange, NoValueForField, \
NoCandlesReceivedFromExchange, \
TickerNotFoundError, NotEnoughCashError
from catalyst.exchange.utils.bundle_utils import get_start_dt, \
get_delta, get_periods, get_periods_range
from catalyst.exchange.utils.datetime_utils import get_delta, \
get_periods_range, \
get_periods, get_start_dt, get_frequency, \
get_candles_number_from_minutes
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols, \
get_frequency, resample_history_df, has_bundle
resample_history_df, has_bundle, get_candles_df
from logbook import Logger
log = Logger('Exchange', level=LOG_LEVEL)
@@ -178,6 +180,7 @@ class Exchange:
if symbols is None:
# Make a distinct list of all symbols
symbols = list(set([asset.symbol for asset in self.assets]))
symbols.sort()
if quote_currency is not None:
for symbol in symbols[:]:
@@ -196,12 +199,8 @@ class Exchange:
)
assets.append(asset)
except SymbolNotFoundOnExchange:
log.debug(
'skipping non-existent market {} {}'.format(
self.name, symbol
)
)
except SymbolNotFoundOnExchange as e:
log.warn(e)
return assets
def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False,
@@ -234,11 +233,15 @@ class Exchange:
"""
asset = None
# TODO: temp mapping, fix to use a single symbol convention
og_symbol = symbol
symbol = self.get_symbol(symbol) if not is_exchange_symbol else symbol
log.debug(
'searching assets for: {} {}'.format(
self.name, symbol
)
)
# TODO: simplify and loose the loop
for a in self.assets:
if asset is not None:
break
@@ -250,7 +253,8 @@ class Exchange:
elif data_frequency is not None:
applies = (
(
data_frequency == 'minute' and a.end_minute is not None)
data_frequency == 'minute' and
a.end_minute is not None)
or (
data_frequency == 'daily' and a.end_daily is not None)
)
@@ -260,7 +264,8 @@ class Exchange:
# The symbol provided may use the Catalyst or the exchange
# convention
key = a.exchange_symbol if is_exchange_symbol else a.symbol
key = a.exchange_symbol if \
is_exchange_symbol else self.get_symbol(a)
if not asset and key.lower() == symbol.lower():
if applies:
asset = a
@@ -276,7 +281,7 @@ class Exchange:
supported_symbols = sorted([a.symbol for a in self.assets])
raise SymbolNotFoundOnExchange(
symbol=symbol,
symbol=og_symbol,
exchange=self.name.title(),
supported_symbols=supported_symbols
)
@@ -434,7 +439,7 @@ class Exchange:
series = pd.Series(values, index=dates)
periods = get_periods_range(
start_dt, end_dt, data_frequency
start_dt=start_dt, end_dt=end_dt, freq=data_frequency
)
# TODO: ensure that this working as expected, if not use fillna
series = series.reindex(
@@ -496,47 +501,62 @@ class Exchange:
"""
freq, candle_size, unit, data_frequency = get_frequency(
frequency, data_frequency
frequency, data_frequency, supported_freqs=['T', 'D', 'H']
)
adj_bar_count = candle_size * bar_count
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
# we want to avoid receiving empty candles
# so we request more than needed
# TODO: consider defining a const per asset
# and/or some retry mechanism (in each iteration request more data)
kExtra_minutes_candles = 150
requested_bar_count = bar_count + \
get_candles_number_from_minutes(unit,
candle_size,
kExtra_minutes_candles)
# The get_history method supports multiple asset
candles = self.get_candles(
freq=freq,
assets=assets,
bar_count=bar_count,
start_dt=start_dt if not is_current else None,
bar_count=requested_bar_count,
end_dt=end_dt if not is_current else None,
)
series = dict()
# candles sanity check - verify no empty candles were received:
for asset in candles:
asset_series = self.get_series_from_candles(
candles=candles[asset],
start_dt=start_dt,
end_dt=end_dt,
data_frequency=frequency,
field=field,
)
if end_dt is not None:
delta = get_delta(candle_size, data_frequency)
adj_end_dt = end_dt - delta
last_traded = asset_series.index[-1]
if not candles[asset]:
raise NoCandlesReceivedFromExchange(
bar_count=requested_bar_count,
end_dt=end_dt,
asset=asset,
exchange=self.name)
if last_traded < adj_end_dt:
raise LastCandleTooEarlyError(
last_traded=last_traded,
end_dt=adj_end_dt,
exchange=self.name,
)
series[asset] = asset_series
# for avoiding unnecessary forward fill end_dt is taken back one second
forward_fill_till_dt = end_dt - timedelta(seconds=1)
series = get_candles_df(candles=candles,
field=field,
freq=frequency,
bar_count=requested_bar_count,
end_dt=forward_fill_till_dt)
# TODO: consider how to approach this edge case
# delta_candle_size = candle_size * 60 if unit == 'H' else candle_size
# Checking to make sure that the dates match
# delta = get_delta(delta_candle_size, data_frequency)
# adj_end_dt = end_dt - delta
# last_traded = asset_series.index[-1]
# if last_traded < adj_end_dt:
# raise LastCandleTooEarlyError(
# last_traded=last_traded,
# end_dt=adj_end_dt,
# exchange=self.name,
# )
df = pd.DataFrame(series)
df.dropna(inplace=True)
return df
return df.tail(bar_count)
def get_history_window_with_bundle(self,
assets,
@@ -584,11 +604,12 @@ class Exchange:
A dataframe containing the requested data.
"""
# TODO: this function needs some work,
# we're currently using it just for benchmark data
freq, candle_size, unit, data_frequency = get_frequency(
frequency, data_frequency
frequency, data_frequency, supported_freqs=['T', 'D']
)
adj_bar_count = candle_size * bar_count
try:
series = self.bundle.get_history_window_series_and_load(
assets=assets,
@@ -610,20 +631,19 @@ class Exchange:
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
trailing_dt = \
series[asset].index[-1] + get_delta(1, data_frequency) \
if asset in series else start_dt
if asset in series else start_dt
# The get_history method supports multiple asset
# Use the original frequency to let each api optimize
# the size of result sets
trailing_bar_count = get_periods(
trailing_bars = get_periods(
trailing_dt, end_dt, freq
)
candles = self.get_candles(
freq=freq,
assets=asset,
bar_count=trailing_bar_count,
start_dt=start_dt,
end_dt=end_dt
end_dt=end_dt,
bar_count=trailing_bars if trailing_bars < 500 else 500,
)
last_value = series[asset].iloc(0) if asset in series \
@@ -661,7 +681,8 @@ class Exchange:
else:
return free, False
def sync_positions(self, positions, cash=None, check_balances=False):
def sync_positions(self, positions, cash=None,
check_balances=False):
"""
Update the portfolio cash and position balances based on the
latest ticker prices.
@@ -699,8 +720,8 @@ class Exchange:
)
positions_value = 0.0
if positions is not None:
assets = set([position.asset for position in positions])
if positions:
assets = list(set([position.asset for position in positions]))
tickers = self.tickers(assets)
for position in positions:
@@ -901,7 +922,24 @@ class Exchange:
pass
@abstractmethod
def cancel_order(self, order_param, symbol_or_asset=None):
def process_order(self, order):
"""
Similar to get_order but looks only for executed orders.
Parameters
----------
order: Order
Returns
-------
float
Avg execution price
"""
@abstractmethod
def cancel_order(self, order_param,
symbol_or_asset=None, params={}):
"""Cancel an open order.
Parameters
@@ -910,12 +948,12 @@ class Exchange:
The order_id or order object to cancel.
symbol_or_asset: str|TradingPair
The catalyst symbol, some exchanges need this
params:
"""
pass
@abstractmethod
def get_candles(self, freq, assets, bar_count=None,
start_dt=None, end_dt=None):
def get_candles(self, freq, assets, bar_count, start_dt=None, end_dt=None):
"""
Retrieve OHLCV candles for the given assets
@@ -955,13 +993,15 @@ class Exchange:
pass
@abc.abstractmethod
def tickers(self, assets):
def tickers(self, assets, on_ticker_error='raise'):
"""
Retrieve current tick data for the given assets
Parameters
----------
assets: list[TradingPair]
on_ticker_error: str [raise|warn]
How to handle an error when retrieving a single ticker.
Returns
-------
@@ -980,7 +1020,7 @@ class Exchange:
@abc.abstractmethod
def get_orderbook(self, asset, order_type, limit):
"""
Retrieve the the orderbook for the given trading pair.
Retrieve the orderbook for the given trading pair.
Parameters
----------
@@ -994,3 +1034,20 @@ class Exchange:
list[dict[str, float]
"""
pass
@abc.abstractmethod
def get_trades(self, asset, my_trades, start_dt, limit):
"""
Retrieve a list of trades.
Parameters
----------
my_trades: bool
List only my trades.
start_dt
limit
Returns
-------
"""
+261 -73
View File
@@ -16,13 +16,11 @@ import signal
import sys
from datetime import timedelta
from os import listdir
from os.path import isfile, join
import logbook
import pandas as pd
from redo import retry
from os.path import isfile, join, exists
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
@@ -38,17 +36,21 @@ from catalyst.exchange.utils.exchange_utils import (
get_algo_folder,
get_algo_df,
save_algo_df,
clear_frame_stats_directory,
remove_old_files,
group_assets_by_exchange, )
from catalyst.exchange.utils.stats_utils import get_pretty_stats, stats_to_s3, \
stats_to_algo_folder
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.marketplace.marketplace import Marketplace
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)
@@ -67,8 +69,8 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
self.current_day = None
if self.simulate_orders is None \
and self.sim_params.arena == 'backtest':
if self.simulate_orders is None and \
self.sim_params.arena == 'backtest':
self.simulate_orders = True
# Operations with retry features
@@ -93,6 +95,8 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
attempts=self.attempts,
)
self._marketplace = None
@staticmethod
def __convert_order_params_for_blotter(limit_price, stop_price, style):
"""
@@ -130,7 +134,7 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
@api_method
def set_commission(self, maker=None, taker=None):
key = self.blotter.commission_models.keys()[0]
key = list(self.blotter.commission_models.keys())[0]
if maker is not None:
self.blotter.commission_models[key].maker = maker
@@ -139,7 +143,7 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
@api_method
def set_slippage(self, spread=None):
key = self.blotter.slippage_models.keys()[0]
key = list(self.blotter.slippage_models.keys())[0]
if spread is not None:
self.blotter.slippage_models[key].spread = spread
@@ -159,6 +163,25 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
style)
return amount, style
def _calculate_order_target_amount(self, asset, target):
"""
removes order amounts so we won't run into issues
when two orders are placed one after the other.
it then proceeds to removing positions amount at TradingAlgorithm
:param asset:
:param target:
:return: target
"""
if asset in self.blotter.open_orders:
for open_order in self.blotter.open_orders[asset]:
current_amount = open_order.amount
target -= current_amount
target = super(ExchangeTradingAlgorithmBase, self). \
_calculate_order_target_amount(asset, target)
return target
def round_order(self, amount, asset):
"""
We need fractions with cryptocurrencies
@@ -168,6 +191,15 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
"""
return round_nearest(amount, asset.min_trade_size)
@api_method
def get_dataset(self, data_source_name, start=None, end=None):
if self._marketplace is None:
self._marketplace = Marketplace()
return self._marketplace.get_dataset(
data_source_name, start, end,
)
@api_method
@preprocess(symbol_str=ensure_upper_case)
def symbol(self, symbol_str, exchange_name=None):
@@ -271,9 +303,9 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
# Merging latest recorded variables
stats.update(self.recorded_vars)
stats['positions'] = cum.position_tracker.get_positions_list()
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'] = []
@@ -304,6 +336,7 @@ class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase):
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):
@@ -351,23 +384,40 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
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')
# erase the frame_stats folder to avoid overloading the disk
error = clear_frame_stats_directory(self.algo_namespace)
if error:
log.warning(error)
self.custom_signals_stats = \
get_algo_df(self.algo_namespace, 'custom_signals_stats')
# in order to save paper & live files separately
self.mode_name = 'paper' if kwargs['simulate_orders'] else 'live'
self.exposure_stats = \
get_algo_df(self.algo_namespace, 'exposure_stats')
self.pnl_stats = get_algo_df(
self.algo_namespace,
'pnl_stats_{}'.format(self.mode_name),
)
self.custom_signals_stats = get_algo_df(
self.algo_namespace,
'custom_signals_stats_{}'.format(self.mode_name)
)
self.exposure_stats = get_algo_df(
self.algo_namespace,
'exposure_stats_{}'.format(self.mode_name)
)
self.is_running = True
self.stats_minutes = 1
self._last_orders = []
self._last_open_orders = []
self.trading_client = None
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
@@ -378,9 +428,20 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
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):
"""
when algorithm comes to an end this function is called.
extracts the stats and calls analyze.
after finishing, it exits the run.
Parameters
----------
Returns
-------
"""
self.is_running = False
if self._analyze is None:
@@ -390,17 +451,31 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
log.info('Exiting the algorithm. Calling `analyze()` '
'before exiting the algorithm.')
# add the last day stats which is not saved in the directory
current_stats = pd.DataFrame(self.frame_stats)
current_stats.set_index('period_close', drop=False, inplace=True)
# get the location of the directory
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))]
folder = join(algo_folder, 'frame_stats')
daily_perf_list = []
for item in files:
filename = join(folder, item)
with open(filename, 'rb') as handle:
daily_perf_list.append(pickle.load(handle))
if exists(folder):
files = [f for f in listdir(folder) if isfile(join(folder, f))]
stats = pd.DataFrame(daily_perf_list)
period_stats_list = []
for item in files:
filename = join(folder, item)
with open(filename, 'rb') as handle:
perf_period = pickle.load(handle)
period_stats_list.extend(perf_period)
stats = pd.DataFrame(period_stats_list)
stats.set_index('period_close', drop=False, inplace=True)
stats = pd.concat([stats, current_stats])
else:
stats = current_stats
self.analyze(stats)
@@ -460,43 +535,69 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
return self._clock
def get_generator(self):
if self.trading_client is not None:
return self.trading_client.transform()
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_{}'.format(self.mode_name),
)
if self.state is None:
self.state = {}
perf = None
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
perf = get_algo_object(
cum_perf = get_algo_object(
algo_name=self.algo_namespace,
key='cumulative_performance',
key='cumulative_performance_{}'.format(self.mode_name),
)
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_{}'.format(self.mode_name),
)
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
# Call the simulation trading algorithm for side-effects:
# it creates the perf tracker
# TradingAlgorithm._create_generator(self, self.sim_params)
if perf is not None:
tracker.cumulative_performance = perf
period = self.perf_tracker.todays_performance
period.starting_cash = perf.ending_cash
period.starting_exposure = perf.ending_exposure
period.starting_value = perf.ending_value
period.position_tracker = perf.position_tracker
self.trading_client = ExchangeAlgorithmExecutor(
algo=self,
sim_params=self.sim_params,
@@ -506,6 +607,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
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):
@@ -523,10 +629,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
positions, returning the available cash, and raising error
if the data goes out of sync.
Parameters
----------
attempt_index: int
Returns
-------
float
@@ -559,10 +661,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
if base_currency is None:
base_currency = exchange.base_currency
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=self.portfolio.cash,
cash=required_cash,
)
total_cash += cash
total_positions_value += positions_value
@@ -606,7 +715,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
)
self.pnl_stats = pd.concat([self.pnl_stats, df])
save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats)
save_algo_df(
self.algo_namespace,
'pnl_stats_{}'.format(self.mode_name),
self.pnl_stats,
)
def add_custom_signals_stats(self, period_stats):
"""
@@ -627,8 +740,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
)
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
save_algo_df(self.algo_namespace, 'custom_signals_stats',
self.custom_signals_stats)
save_algo_df(
self.algo_namespace,
'custom_signals_stats_{}'.format(self.mode_name),
self.custom_signals_stats,
)
def add_exposure_stats(self, period_stats):
"""
@@ -655,9 +771,43 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
self.exposure_stats = pd.concat([self.exposure_stats, df])
save_algo_df(
self.algo_namespace, 'exposure_stats', self.exposure_stats
self.algo_namespace,
'exposure_stats_{}'.format(self.mode_name),
self.exposure_stats
)
def nullify_frame_stats(self, now):
"""
Save all period_stats to local directory
erase old files from the folder and nullify
self.frame_stats
Parameters
----------
now: Timestamp
Returns
-------
"""
save_algo_object(
algo_name=self.algo_namespace,
key=now.floor('1D').strftime('%Y-%m-%d'),
obj=self.frame_stats,
rel_path='frame_stats'
)
error = remove_old_files(
algo_name=self.algo_namespace,
today=now,
rel_path='frame_stats'
)
if error:
log.warning(error)
self.frame_stats = list()
def handle_data(self, data):
"""
Wrapper around the handle_data method of each algo.
@@ -670,17 +820,27 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
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.nullify_frame_stats(now=data.current_dt)
self.performance_needs_update = False
new_orders = self.perf_tracker.todays_performance.orders_by_id.keys()
if new_orders != self._last_orders:
last_orders_list = list(self.blotter.orders.keys())
open_orders_list = list(self.blotter.open_orders.keys())
if last_orders_list != self._last_orders or \
open_orders_list != self._last_open_orders:
self.performance_needs_update = True
self._last_orders = new_orders
# Saving current order positions
# to detect changes in the next frame
self._last_orders = copy.deepcopy(last_orders_list)
self._last_open_orders = copy.deepcopy(open_orders_list)
if self.performance_needs_update:
self.perf_tracker.update_performance()
@@ -697,7 +857,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
self.portfolio_needs_update = False
log.info(
'got totals from exchanges, cash: {} positions: {}'.format(
'portfolio balances, cash: {}, positions: {}'.format(
cash, positions_value
)
)
@@ -709,18 +869,34 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
# 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',
key='cumulative_performance_{}'.format(self.mode_name),
obj=self.perf_tracker.cumulative_performance,
)
self.current_day = data.current_dt.floor('1D')
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_{}'.format(self.mode_name)
)
log.debug('saving context.state object')
save_algo_object(
algo_name=self.algo_namespace,
key='context.state_{}'.format(self.mode_name),
obj=self.state)
def _process_stats(self, data):
today = data.current_dt.floor('1D')
@@ -736,6 +912,8 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
# Saving the last hour in memory
self.frame_stats.append(frame_stats)
# creating and saving the pnl_stats into the local
# directory
self.add_pnl_stats(frame_stats)
if self.recorded_vars:
self.add_custom_signals_stats(frame_stats)
@@ -763,12 +941,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
start_dt=today,
end_dt=data.current_dt
)
save_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
obj=daily_stats,
rel_path='daily_perf'
)
return recorded_cols
@@ -779,6 +951,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
csv_bytes = stats_to_algo_folder(
stats=self.frame_stats,
algo_namespace=self.algo_namespace,
folder_name='stats_{}'.format(self.mode_name),
recorded_cols=recorded_cols,
)
except Exception as e:
@@ -806,6 +979,13 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
raise NotImplementedError()
def _get_open_orders(self, asset=None):
if self.simulate_orders:
raise ValueError(
'The get_open_orders() method only works in live mode. '
'The purpose is to list open orders on the exchange '
'regardless who placed them. To list the open orders of '
'this algo, use `context.blotter.open_orders`.'
)
if asset:
exchange = self.exchanges[asset.exchange]
return exchange.get_open_orders(asset)
@@ -839,13 +1019,15 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
If an asset is passed then this will return a list of the open
orders for this asset.
"""
# TODO: should this be a shortcut to the open orders in the blotter?
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,))
args=(asset,)
)
@api_method
def get_order(self, order_id, exchange_name):
@@ -874,13 +1056,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
args=(order_id,))
@api_method
def cancel_order(self, order_param, exchange_name):
def cancel_order(self, order_param, exchange_name,
symbol=None, params={}):
"""Cancel an open order.
Parameters
----------
order_param : str or Order
The order_id or order object to cancel.
exchange_name: name of exchange from
which you want to cancel the order
symbol:
params:
"""
exchange = self.exchanges[exchange_name]
@@ -894,4 +1082,4 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
sleeptime=self.attempts['retry_sleeptime'],
retry_exceptions=(ExchangeRequestError,),
cleanup=lambda: log.warn('cancelling order again.'),
args=(order_id,))
args=(order_id, symbol, params))
+1 -2
View File
@@ -1,8 +1,7 @@
import pandas as pd
from logbook import Logger
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.utils.factory import find_exchanges
from logbook import Logger
log = Logger('ExchangeAssetFinder', level=LOG_LEVEL)
+44 -41
View File
@@ -1,8 +1,9 @@
import numpy as np
import pandas as pd
from catalyst.assets._assets import TradingPair
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
@@ -42,6 +43,11 @@ class TradingPairFeeSchedule(CommissionModel):
)
)
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.
@@ -55,15 +61,14 @@ class TradingPairFeeSchedule(CommissionModel):
cost = abs(transaction.amount) * transaction.price
asset = order.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
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
and order.limit_reached else taker
fee = cost * multiplier
return fee
@@ -90,7 +95,6 @@ class TradingPairFixedSlippage(SlippageModel):
def simulate(self, data, asset, orders_for_asset):
self._volume_for_bar = 0
price = data.current(asset, 'close')
dt = data.current_dt
@@ -100,18 +104,20 @@ class TradingPairFixedSlippage(SlippageModel):
order.check_triggers(price, dt)
if not order.triggered:
log.debug('order has not reached the trigger at current '
'price {}'.format(price))
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
)
transaction = create_transaction(
order, dt, execution_price, execution_volume
)
self._volume_for_bar += abs(transaction.amount)
yield order, transaction
self._volume_for_bar += abs(transaction.amount)
yield order, transaction
def process_order(self, data, order):
price = data.current(order.asset, 'close')
@@ -202,34 +208,29 @@ class ExchangeBlotter(Blotter):
for order in self.open_orders[asset]:
log.debug('found open order: {}'.format(order.id))
new_order, executed_price = exchange.get_order(order.id, asset)
log.debug(
'got updated order {} {}'.format(
new_order, executed_price
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.status == ORDER_STATUS.FILLED:
avg_price = np.average(
a=[t.price for t in transactions],
weights=[t.amount for t in transactions],
)
)
order.status = new_order.status
if order.status == ORDER_STATUS.FILLED:
order.commission = new_order.commission
if order.amount != new_order.amount:
log.warn(
'executed order amount {} differs '
'from original'.format(
new_order.amount, order.amount
)
ostatus = 'filled' if order.open_amount == 0 else 'partial'
log.info(
'{} order {} / {}: {}, avg price: {}'.format(
ostatus,
order.id,
asset.symbol,
order.filled,
avg_price,
)
order.amount = new_order.amount
transaction = Transaction(
asset=order.asset,
amount=order.amount,
dt=pd.Timestamp.utcnow(),
price=executed_price,
order_id=order.id,
commission=order.commission
)
yield order, transaction
for transaction in transactions:
yield order, transaction
elif order.status == ORDER_STATUS.CANCELLED:
yield order, None
@@ -237,9 +238,12 @@ class ExchangeBlotter(Blotter):
else:
delta = pd.Timestamp.utcnow() - order.dt
log.info(
'order {order_id} still open after {delta}'.format(
'{exchange} order {order_id} for {symbol} still open '
'after {delta}'.format(
exchange=exchange.name,
order_id=order.id,
delta=delta
delta=delta,
symbol=order.asset.symbol,
)
)
@@ -250,7 +254,6 @@ class ExchangeBlotter(Blotter):
for order, txn in self.check_open_orders():
order.dt = txn.dt
transactions.append(txn)
if not order.open:
+40 -49
View File
@@ -8,12 +8,8 @@ from operator import is_not
import numpy as np
import pandas as pd
import pytz
from catalyst.assets._assets import TradingPair
from logbook import Logger
from pytz import UTC
from six import itervalues
from catalyst import get_calendar
from catalyst.assets._assets import TradingPair
from catalyst.constants import DATE_TIME_FORMAT, AUTO_INGEST
from catalyst.constants import LOG_LEVEL
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
@@ -25,13 +21,16 @@ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
NoDataAvailableOnExchange, \
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
get_bcolz_chunk, get_month_start_end, \
get_year_start_end, get_df_from_arrays, get_start_dt, get_period_label, \
get_delta, get_assets
get_bcolz_chunk, get_df_from_arrays, get_assets
from catalyst.exchange.utils.datetime_utils import get_start_dt, \
get_period_label, get_month_start_end, get_year_start_end
from catalyst.exchange.utils.exchange_utils import get_exchange_folder, \
save_exchange_symbols, mixin_market_params, get_catalyst_symbol
from catalyst.utils.cli import maybe_show_progress
from catalyst.utils.paths import ensure_directory
from logbook import Logger
from pytz import UTC
from six import itervalues
log = Logger('exchange_bundle', level=LOG_LEVEL)
@@ -233,12 +232,12 @@ class ExchangeBundle:
problem = '{name} ({start_dt} to {end_dt}) has empty ' \
'periods: {dates}'.format(
name=asset.symbol,
start_dt=asset.start_date.strftime(
DATE_TIME_FORMAT),
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
dates=[date.strftime(
DATE_TIME_FORMAT) for date in dates])
name=asset.symbol,
start_dt=asset.start_date.strftime(
DATE_TIME_FORMAT),
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
dates=[date.strftime(
DATE_TIME_FORMAT) for date in dates])
if empty_rows_behavior == 'warn':
log.warn(problem)
@@ -599,8 +598,9 @@ class ExchangeBundle:
# we want to give an end_date far in time
writer = self.get_writer(start_dt, end_dt, data_frequency)
if show_breakdown:
for asset in chunks:
with maybe_show_progress(
if chunks:
for asset in chunks:
with maybe_show_progress(
chunks[asset],
show_progress,
label='Ingesting {frequency} price data for '
@@ -608,6 +608,30 @@ class ExchangeBundle:
exchange=self.exchange_name,
frequency=data_frequency,
symbol=asset.symbol
)) as it:
for chunk in it:
problems += self.ingest_ctable(
asset=chunk['asset'],
data_frequency=data_frequency,
period=chunk['period'],
writer=writer,
empty_rows_behavior='strip',
cleanup=True
)
else:
all_chunks = list(chain.from_iterable(itervalues(chunks)))
# We sort the chunks by end date to ingest most recent data first
if all_chunks:
all_chunks.sort(
key=lambda chunk: pd.to_datetime(chunk['period'])
)
with maybe_show_progress(
all_chunks,
show_progress,
label='Ingesting {frequency} price data on '
'{exchange}'.format(
exchange=self.exchange_name,
frequency=data_frequency,
)) as it:
for chunk in it:
problems += self.ingest_ctable(
@@ -618,30 +642,6 @@ class ExchangeBundle:
empty_rows_behavior='strip',
cleanup=True
)
else:
all_chunks = list(chain.from_iterable(itervalues(chunks)))
# We sort the chunks by end date to ingest most recent data first
all_chunks.sort(
key=lambda chunk: pd.to_datetime(chunk['period'])
)
with maybe_show_progress(
all_chunks,
show_progress,
label='Ingesting {frequency} price data on '
'{exchange}'.format(
exchange=self.exchange_name,
frequency=data_frequency,
)) as it:
for chunk in it:
problems += self.ingest_ctable(
asset=chunk['asset'],
data_frequency=data_frequency,
period=chunk['period'],
writer=writer,
empty_rows_behavior='strip',
cleanup=True
)
if show_report and len(problems) > 0:
log.info('problems during ingestion:{}\n'.format(
@@ -831,7 +831,6 @@ class ExchangeBundle:
field,
data_frequency,
algo_end_dt=None,
trailing_bar_count=None,
force_auto_ingest=False
):
"""
@@ -859,7 +858,6 @@ class ExchangeBundle:
bar_count=bar_count,
field=field,
data_frequency=data_frequency,
trailing_bar_count=trailing_bar_count,
)
return pd.DataFrame(series)
@@ -888,7 +886,6 @@ class ExchangeBundle:
field=field,
data_frequency=data_frequency,
reset_reader=True,
trailing_bar_count=trailing_bar_count,
)
return series
@@ -899,7 +896,6 @@ class ExchangeBundle:
bar_count=bar_count,
field=field,
data_frequency=data_frequency,
trailing_bar_count=trailing_bar_count,
)
return pd.DataFrame(series)
@@ -963,17 +959,12 @@ class ExchangeBundle:
bar_count,
field,
data_frequency,
trailing_bar_count=None,
reset_reader=False):
start_dt = get_start_dt(end_dt, bar_count, data_frequency, False)
start_dt, _ = self.get_adj_dates(
start_dt, end_dt, assets, data_frequency
)
if trailing_bar_count:
delta = get_delta(trailing_bar_count, data_frequency)
end_dt += delta
# This is an attempt to resolve some caching with the reader
# when auto-ingesting data.
# TODO: needs more work
+9 -9
View File
@@ -3,17 +3,17 @@ import abc
import numpy as np
import pandas as pd
from catalyst.assets._assets import TradingPair
from logbook import Logger
from redo import retry
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 get_frequency, \
resample_history_df, group_assets_by_exchange
from catalyst.exchange.utils.exchange_utils import resample_history_df, \
group_assets_by_exchange
from catalyst.exchange.utils.datetime_utils import get_frequency, get_start_dt
from logbook import Logger
from redo import retry
log = Logger('DataPortalExchange', level=LOG_LEVEL)
@@ -292,13 +292,13 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
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
frequency, data_frequency, supported_freqs=['T', 'D']
)
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')
@@ -310,10 +310,10 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
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)
start_dt = get_start_dt(end_dt, adj_bar_count, adj_data_frequency)
df = resample_history_df(pd.DataFrame(series), freq, field, start_dt)
return df
def get_exchange_spot_value(self,
+14
View File
@@ -100,6 +100,13 @@ class InvalidHistoryFrequencyError(ZiplineError):
).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.'
@@ -315,3 +322,10 @@ class BalanceTooLowError(ZiplineError):
'add positions to hold a free amount greater than {amount}, or clean '
'the state of this algo and restart.'
).strip()
class NoCandlesReceivedFromExchange(ZiplineError):
msg = (
'Although requesting {bar_count} candles until {end_dt} of asset {asset}, '
'an empty list of candles was received for {exchange}.'
).strip()
+1 -2
View File
@@ -1,8 +1,7 @@
import numpy as np
from logbook import Logger
from catalyst.constants import LOG_LEVEL
from catalyst.protocol import Portfolio, Positions, Position
from logbook import Logger
log = Logger('ExchangePortfolio', level=LOG_LEVEL)
+5 -6
View File
@@ -11,12 +11,6 @@
# 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 logbook import Logger
from numpy import (
iinfo,
uint32,
)
from catalyst.constants import LOG_LEVEL
from catalyst.data.us_equity_pricing import BcolzDailyBarReader
from catalyst.errors import NoFurtherDataError
@@ -26,6 +20,11 @@ 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
+2 -3
View File
@@ -1,13 +1,12 @@
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
from catalyst.constants import LOG_LEVEL
from catalyst.exchange.utils.stats_utils import prepare_stats
log = Logger('LiveGraphClock', level=LOG_LEVEL)
+1 -2
View File
@@ -14,14 +14,13 @@
from time import sleep
import pandas as pd
from catalyst.constants import LOG_LEVEL
from catalyst.gens.sim_engine import (
BAR,
SESSION_START
)
from logbook import Logger
from catalyst.constants import LOG_LEVEL
log = Logger('ExchangeClock', level=LOG_LEVEL)
+12 -212
View File
@@ -1,11 +1,18 @@
import calendar
import os
import tarfile
from datetime import timedelta, datetime, date
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
import pytz
from catalyst.data.bundles.core import download_without_progress
from catalyst.exchange.utils.exchange_utils import get_exchange_bundles_folder
@@ -14,41 +21,6 @@ EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
API_URL = 'http://data.enigma.co/api/v1'
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_bcolz_chunk(exchange_name, symbol, data_frequency, period):
"""
Download and extract a bcolz bundle.
@@ -78,8 +50,8 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
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)
exchange=exchange_name,
name=name)
bytes = download_without_progress(url)
with tarfile.open('r', fileobj=bytes) as tar:
@@ -88,178 +60,6 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
return path
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(start_dt, end_dt, freq):
"""
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'
return pd.date_range(start_dt, end_dt, freq=freq)
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, end_dt, 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
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_df_from_arrays(arrays, periods):
"""
A DataFrame from the specified OHCLV arrays.
+362
View File
@@ -0,0 +1,362 @@
import calendar
import math
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 'h' if unit == 'H' 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, supported_freqs=['D', 'H', 'T']):
"""
Takes an arbitrary candle size (e.g. 15T) and converts to the lowest
common denominator supported by the data bundles (e.g. 1T). The data
bundles only support 1T and 1D frequencies. If another frequency
is requested, Catalyst must request the underlying data and resample.
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)
data_frequency = 'minute'
elif unit.lower() == 'h':
data_frequency = 'minute'
if 'H' in supported_freqs:
unit = 'H'
alias = '{}H'.format(candle_size)
else:
candle_size = candle_size * 60
alias = '{}T'.format(candle_size)
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)
def get_candles_number_from_minutes(unit, candle_size, minutes):
"""
Get the number of bars needed for the given time interval
in minutes.
Notes
-----
Supports only "T", "D" and "H" units
Parameters
----------
unit: str
candle_size : int
minutes: int
Returns
-------
int
"""
if unit == "T":
res = (float(minutes) / candle_size)
elif unit == "H":
res = (minutes / 60.0) / candle_size
else: # unit == "D"
res = (minutes / 1440.0) / candle_size
return int(math.ceil(res))
+118 -100
View File
@@ -2,7 +2,6 @@ import hashlib
import json
import os
import pickle
import re
import shutil
from datetime import date, datetime
@@ -12,8 +11,7 @@ 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, \
InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias
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, \
@@ -128,9 +126,12 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None):
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):
download_exchange_symbols(exchange_name, environ)
pd.Timestamp('now', tz='UTC') - last_modified_time(
filename)).days > 1):
try:
download_exchange_symbols(exchange_name, environ)
except Exception:
pass
if os.path.isfile(filename):
with open(filename) as data_file:
@@ -190,7 +191,7 @@ def get_symbols_string(assets):
return ', '.join([asset.symbol for asset in array])
def get_exchange_auth(exchange_name, environ=None):
def get_exchange_auth(exchange_name, alias=None, environ=None):
"""
The de-serialized contend of the exchange's auth.json file.
@@ -205,7 +206,8 @@ def get_exchange_auth(exchange_name, environ=None):
"""
exchange_folder = get_exchange_folder(exchange_name, environ)
filename = os.path.join(exchange_folder, 'auth.json')
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:
@@ -271,6 +273,7 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None, how='pickle'):
key: str
environ:
rel_path: str
how: str
Returns
-------
@@ -314,6 +317,7 @@ def save_algo_object(algo_name, key, obj, environ=None, rel_path=None,
obj: Object
environ:
rel_path: str
how: str
"""
folder = get_algo_folder(algo_name, environ)
@@ -390,6 +394,71 @@ def save_algo_df(algo_name, key, df, environ=None, rel_path=None):
df.to_csv(handle, encoding='UTF_8')
def clear_frame_stats_directory(algo_name):
"""
remove the outdated directory
to avoid overloading the disk
Parameters
----------
algo_name: str
Returns
-------
error: str
"""
error = None
algo_folder = get_algo_folder(algo_name)
folder = os.path.join(algo_folder, 'frame_stats')
if os.path.exists(folder):
try:
shutil.rmtree(folder)
except OSError:
error = 'unable to remove {}, the analyze ' \
'data will be inconsistent'.format(folder)
return error
def remove_old_files(algo_name, today, rel_path, environ=None):
"""
remove old files from a directory
to avoid overloading the disk
Parameters
----------
algo_name: str
today: Timestamp
rel_path: str
environ:
Returns
-------
error: str
"""
error = None
algo_folder = get_algo_folder(algo_name, environ)
folder = os.path.join(algo_folder, rel_path)
ensure_directory(folder)
# run on all files in the folder
for f in os.listdir(folder):
try:
file_path = os.path.join(folder, f)
creation_unix = os.path.getctime(file_path)
creation_time = pd.to_datetime(creation_unix, unit='s', utc=True)
# if the file is older than 30 days erase it
if today - pd.DateOffset(30) > creation_time:
os.unlink(file_path)
except OSError:
error = 'unable to erase files in {}'.format(folder)
return error
def get_exchange_minute_writer_root(exchange_name, environ=None):
"""
The minute writer folder for the exchange.
@@ -510,73 +579,7 @@ def get_common_assets(exchanges):
return assets
def get_frequency(freq, data_frequency):
"""
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 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':
alias = '{}D'.format(candle_size)
if data_frequency == 'minute':
data_frequency = 'daily'
elif unit.lower() == 'm' or 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 resample_history_df(df, freq, field):
def resample_history_df(df, freq, field, start_dt=None):
"""
Resample the OHCLV DataFrame using the specified frequency.
@@ -604,7 +607,16 @@ def resample_history_df(df, freq, field):
else:
raise ValueError('Invalid field.')
resampled_df = df.resample(freq).agg(agg)
resampled_df = df.resample(
freq, closed='left', label='left'
).agg(agg) # type: pd.DataFrame
# Because the samples are closed left, we get one more candle at
# the beginning then the requested number for bars. Removing this
# candle to avoid confusion.
if start_dt and not resampled_df.empty:
resampled_df = resampled_df[resampled_df.index >= start_dt]
return resampled_df
@@ -630,8 +642,9 @@ def mixin_market_params(exchange_name, params, market):
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:
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']
@@ -649,14 +662,6 @@ def mixin_market_params(exchange_name, params, market):
params['lot'] = params['min_trade_size']
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)
def group_assets_by_exchange(assets):
exchange_assets = dict()
for asset in assets:
@@ -711,23 +716,36 @@ def save_asset_data(folder, df, decimals=8):
)
def get_candles_df(candles, field, freq, bar_count, end_dt,
previous_value=None):
def forward_fill_df_if_needed(df, periods):
df = df.reindex(periods)
# volume should always be 0 (if there were no trades in this interval)
df['volume'] = df['volume'].fillna(0.0)
# ie pull the last close into this close
df['close'] = df.fillna(method='pad')
# now copy the close that was pulled down from the last timestep
# into this row, across into o/h/l
df['open'] = df['open'].fillna(df['close'])
df['low'] = df['low'].fillna(df['close'])
df['high'] = df['high'].fillna(df['close'])
return df
def transform_candles_to_df(candles):
return pd.DataFrame(candles).set_index('last_traded')
def get_candles_df(candles, field, freq, bar_count, end_dt):
all_series = dict()
for asset in candles:
periods = pd.date_range(end=end_dt, periods=bar_count, freq=freq)
asset_df = transform_candles_to_df(candles[asset])
rounded_end_dt = end_dt.floor(freq)
periods = pd.date_range(end=rounded_end_dt,
periods=bar_count,
freq=freq)
asset_df = forward_fill_df_if_needed(asset_df, periods)
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
all_series[asset] = pd.Series(asset_df[field])
df = pd.DataFrame(all_series)
df.dropna(inplace=True)
+5 -4
View File
@@ -1,25 +1,24 @@
import os
from logbook import Logger
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):
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)
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:
@@ -34,6 +33,8 @@ def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
exchange_name=exchange_name,
key=exchange_auth['key'],
secret=exchange_auth['secret'],
password=exchange_auth['password'] if 'password'
in exchange_auth.keys() else '',
base_currency=base_currency,
)
exchange_cache[key] = exchange
@@ -3,9 +3,8 @@ import re
from json import JSONEncoder
import pandas as pd
from six import string_types
from catalyst.constants import DATE_TIME_FORMAT
from six import string_types
class ExchangeJSONEncoder(json.JSONEncoder):
+47 -25
View File
@@ -8,9 +8,9 @@ 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 = []
@@ -44,7 +44,7 @@ def crossover(source, target):
"""
if isinstance(target, numbers.Number):
if source[-1] is np.nan or source[-2] is np.nan \
or target is np.nan:
or target is np.nan:
return False
if source[-1] >= target > source[-2]:
@@ -54,7 +54,7 @@ def crossover(source, target):
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:
or target[-1] is np.nan or target[-2] is np.nan:
return False
if source[-1] > target[-1] and source[-2] < target[-2]:
@@ -81,7 +81,7 @@ def crossunder(source, target):
"""
if isinstance(target, numbers.Number):
if source[-1] is np.nan or source[-2] is np.nan \
or target is np.nan:
or target is np.nan:
return False
if source[-1] < target <= source[-2]:
@@ -90,7 +90,7 @@ def crossunder(source, target):
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:
or target[-1] is np.nan or target[-2] is np.nan:
return False
if source[-1] < target[-1] and source[-2] >= target[-2]:
@@ -229,7 +229,10 @@ def prepare_stats(stats, recorded_cols=list()):
asset_values)
df = pd.DataFrame(stats)
df['orders'] = df['orders'].apply(lambda orders: len(orders))
df['transactions'] = df['transactions'].apply(
lambda transactions: len(transactions)
)
index_cols = [
'period_close', 'starting_cash', 'ending_cash', 'portfolio_value',
'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions',
@@ -241,11 +244,6 @@ def prepare_stats(stats, recorded_cols=list()):
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)
@@ -261,7 +259,14 @@ def prepare_stats(stats, recorded_cols=list()):
return df, columns
def get_pretty_stats(stats, recorded_cols=None, num_rows=10):
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.
@@ -280,18 +285,18 @@ def get_pretty_stats(stats, recorded_cols=None, num_rows=10):
"""
if isinstance(stats, pd.DataFrame):
stats = stats.T.to_dict().values()
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
display_stats = stats[-num_rows:] if len(stats) > num_rows else stats
df, columns = prepare_stats(
display_stats, recorded_cols=recorded_cols
)
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)
set_print_settings()
return df.to_string(columns=columns)
@@ -352,9 +357,13 @@ def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None,
pid = os.getpid()
parts = uri.split('//')
obj = s3.Object(parts[1], '{}/{}-{}-{}.csv'.format(
folder, timestr, algo_namespace, pid
))
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)
@@ -387,7 +396,8 @@ def email_error(algo_name, dt, e, environ=None):
)})
def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
def stats_to_algo_folder(stats, algo_namespace,
folder_name, recorded_cols=None):
"""
Saves the performance stats to the algo local folder.
@@ -395,6 +405,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
----------
stats: list[Object]
algo_namespace: str
folder_name: str
recorded_cols: list[str]
Returns
@@ -407,7 +418,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
timestr = time.strftime('%Y%m%d')
folder = get_algo_folder(algo_namespace)
stats_folder = os.path.join(folder, 'stats')
stats_folder = os.path.join(folder, folder_name)
ensure_directory(stats_folder)
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
@@ -439,6 +450,17 @@ def df_to_string(df):
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
+6 -7
View File
@@ -3,7 +3,6 @@ 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
@@ -63,14 +62,14 @@ def output_df(df, assets, name=None):
"""
if isinstance(assets, TradingPair):
exchange_folder = assets.exchange
asset_folder = assets.symbol
asset_folder = '{}_{}'.format(assets.exchange, assets.symbol)
else:
exchange_folder = ','.join([asset.exchange for asset in assets])
asset_folder = ','.join([asset.symbol for asset in assets])
asset_folder = ','.join(
['{}_{}'.format(a.exchange, a.symbol) for a in assets]
)
folder = os.path.join(
tempfile.gettempdir(), 'catalyst', exchange_folder, asset_folder
tempfile.gettempdir(), 'catalyst', asset_folder
)
ensure_directory(folder)
@@ -80,4 +79,4 @@ def output_df(df, assets, name=None):
path = os.path.join(folder, '{}.csv'.format(name))
df.to_csv(path)
return path
return path, folder
+1 -2
View File
@@ -62,7 +62,6 @@ from __future__ import division
import logbook
import pandas as pd
from pandas.tseries.tools import normalize_date
from catalyst.finance.performance.period import PerformancePeriod
from catalyst.errors import NoFurtherDataError
@@ -344,7 +343,7 @@ class PerformanceTracker(object):
"""
self.position_tracker.sync_last_sale_prices(dt, False, data_portal)
self.update_performance()
todays_date = normalize_date(dt)
todays_date = dt.normalize()
account = self.get_account(False)
bench_returns = self.all_benchmark_returns.loc[todays_date:dt]
+1 -2
View File
@@ -18,7 +18,6 @@ import logbook
import numpy as np
import pandas as pd
from pandas.tseries.tools import normalize_date
from six import iteritems
@@ -80,7 +79,7 @@ class RiskMetricsCumulative(object):
# on the first day.
self.day_before_start = self.start_session - self.sessions.freq
last_day = normalize_date(sim_params.end_session)
last_day = sim_params.end_session.normalize()
if last_day not in self.sessions:
last_day = pd.tseries.index.DatetimeIndex(
[last_day]
+4 -2
View File
@@ -29,13 +29,15 @@ from .risk import check_entry
from empyrical import (
alpha_beta_aligned,
annual_volatility,
cum_returns,
downside_risk,
information_ratio,
max_drawdown,
sharpe_ratio,
sortino_ratio
)
from catalyst.patches.stats import (
max_drawdown,
cum_returns,
)
from catalyst.constants import LOG_LEVEL
+20 -8
View File
@@ -16,7 +16,6 @@ from functools import partial
import logbook
import pandas as pd
from pandas.tslib import normalize_date
from six import string_types
from sqlalchemy import create_engine
@@ -95,11 +94,24 @@ class TradingEnvironment(object):
if not trading_calendar:
trading_calendar = get_calendar("NYSE")
self.benchmark_returns, self.treasury_curves = load(
trading_calendar.day,
trading_calendar.schedule.index,
self.bm_symbol,
)
# todo: uncomment and add a well defined benchmark
# self.benchmark_returns, self.treasury_curves = load(
# trading_calendar.day,
# trading_calendar.schedule.index,
# self.bm_symbol,
# exchange=exchange,
# )
start_data = get_calendar('OPEN').first_trading_session
end_data = pd.Timestamp.utcnow()
treasure_cols = ['1month', '3month', '6month', '1year', '2year',
'3year', '5year', '7year', '10year', '20year', '30year']
self.benchmark_returns = pd.DataFrame(data=0.001,
index=pd.date_range(start_data, end_data),
columns=['close'])
self.treasury_curves = pd.DataFrame(data=0.001,
index=pd.date_range(start_data, end_data),
columns=treasure_cols)
self.exchange_tz = exchange_tz
@@ -151,8 +163,8 @@ class SimulationParameters(object):
# chop off any minutes or hours on the given start and end dates,
# as we only support session labels here (and we represent session
# labels as midnight UTC).
self._start_session = normalize_date(start_session)
self._end_session = normalize_date(end_session)
self._start_session = start_session.normalize()
self._end_session = end_session.normalize()
self._capital_base = capital_base
self._emission_rate = emission_rate
+1 -2
View File
@@ -14,7 +14,6 @@
# limitations under the License.
from contextlib2 import ExitStack
from logbook import Logger, Processor
from pandas.tslib import normalize_date
from catalyst.protocol import BarData
from catalyst.utils.api_support import ZiplineAPI
from six import viewkeys
@@ -229,7 +228,7 @@ class AlgorithmSimulator(object):
elif action == SESSION_END:
# End of the session.
if emission_rate == 'daily':
handle_benchmark(normalize_date(dt))
handle_benchmark(dt).normalize()
execute_order_cancellation_policy()
yield self._get_daily_message(dt, algo, algo.perf_tracker)
@@ -0,0 +1,302 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "INITIAL_SUPPLY",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_subtractedValue",
"type": "uint256"
}
],
"name": "decreaseApproval",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "getAfterApproveTest",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_addedValue",
"type": "uint256"
}
],
"name": "increaseApproval",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"name": "testValue",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]
@@ -0,0 +1 @@
0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
0xa64927358a82254be92eb1f1cb01de68d1787004
+814
View File
@@ -0,0 +1,814 @@
from __future__ import print_function
import glob
import json
import os
import re
import shutil
import sys
import time
import webbrowser
import bcolz
import logbook
import pandas as pd
import requests
from requests_toolbelt import MultipartDecoder
from requests_toolbelt.multipart.decoder import \
NonMultipartContentTypeException
from catalyst.constants import (
LOG_LEVEL, AUTH_SERVER, ETH_REMOTE_NODE, MARKETPLACE_CONTRACT,
MARKETPLACE_CONTRACT_ABI, ENIGMA_CONTRACT, ENIGMA_CONTRACT_ABI)
from catalyst.exchange.utils.stats_utils import set_print_settings
from catalyst.marketplace.marketplace_errors import (
MarketplacePubAddressEmpty, MarketplaceDatasetNotFound,
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
MarketplaceNoCSVFiles, MarketplaceRequiresPython3)
from catalyst.marketplace.utils.auth_utils import get_key_secret, \
get_signed_headers
from catalyst.marketplace.utils.bundle_utils import merge_bundles
from catalyst.marketplace.utils.eth_utils import bin_hex, from_grains, \
to_grains
from catalyst.marketplace.utils.path_utils import get_bundle_folder, \
get_data_source_folder, get_marketplace_folder, \
get_user_pubaddr, get_temp_bundles_folder, extract_bundle
from catalyst.utils.paths import ensure_directory
if sys.version_info.major < 3:
import urllib
else:
import urllib.request as urllib
log = logbook.Logger('Marketplace', level=LOG_LEVEL)
class Marketplace:
def __init__(self):
global Web3
try:
from web3 import Web3, HTTPProvider
except ImportError:
raise MarketplaceRequiresPython3()
self.addresses = get_user_pubaddr()
if self.addresses[0]['pubAddr'] == '':
raise MarketplacePubAddressEmpty(
filename=os.path.join(
get_marketplace_folder(), 'addresses.json')
)
self.default_account = self.addresses[0]['pubAddr']
self.web3 = Web3(HTTPProvider(ETH_REMOTE_NODE))
contract_url = urllib.urlopen(MARKETPLACE_CONTRACT)
self.mkt_contract_address = Web3.toChecksumAddress(
contract_url.readline().decode(
contract_url.info().get_content_charset()).strip())
abi_url = urllib.urlopen(MARKETPLACE_CONTRACT_ABI)
abi_url = abi_url.read().decode(
abi_url.info().get_content_charset())
abi = json.loads(abi_url)
self.mkt_contract = self.web3.eth.contract(
self.mkt_contract_address,
abi=abi,
)
contract_url = urllib.urlopen(ENIGMA_CONTRACT)
self.eng_contract_address = Web3.toChecksumAddress(
contract_url.readline().decode(
contract_url.info().get_content_charset()).strip())
abi_url = urllib.urlopen(ENIGMA_CONTRACT_ABI)
abi_url = abi_url.read().decode(
abi_url.info().get_content_charset())
abi = json.loads(abi_url)
self.eng_contract = self.web3.eth.contract(
self.eng_contract_address,
abi=abi,
)
# def get_data_sources_map(self):
# return [
# dict(
# name='Marketcap',
# desc='The marketcap value in USD.',
# start_date=pd.to_datetime('2017-01-01'),
# end_date=pd.to_datetime('2018-01-15'),
# data_frequencies=['daily'],
# ),
# dict(
# name='GitHub',
# desc='The rate of development activity on GitHub.',
# start_date=pd.to_datetime('2017-01-01'),
# end_date=pd.to_datetime('2018-01-15'),
# data_frequencies=['daily', 'hour'],
# ),
# dict(
# name='Influencers',
# desc='Tweets & related sentiments by selected influencers.',
# start_date=pd.to_datetime('2017-01-01'),
# end_date=pd.to_datetime('2018-01-15'),
# data_frequencies=['daily', 'hour', 'minute'],
# ),
# ]
def to_text(self, hex):
return Web3.toText(hex).rstrip('\0')
def choose_pubaddr(self):
if len(self.addresses) == 1:
address = self.addresses[0]['pubAddr']
address_i = 0
print('Using {} for this transaction.'.format(address))
else:
while True:
for i in range(0, len(self.addresses)):
print('{}\t{}\t{}\t{}'.format(
i,
self.addresses[i]['pubAddr'],
self.addresses[i]['wallet'].ljust(10),
self.addresses[i]['desc'])
)
address_i = int(input('Choose your address associated with '
'this transaction: [default: 0] ') or 0)
if not (0 <= address_i < len(self.addresses)):
print('Please choose a number between 0 and {}\n'.format(
len(self.addresses) - 1))
else:
address = Web3.toChecksumAddress(
self.addresses[address_i]['pubAddr'])
break
return address, address_i
def sign_transaction(self, tx):
url = 'https://www.mycrypto.com/#offline-transaction'
print('\nVisit {url} and enter the following parameters:\n\n'
'From Address:\t\t{_from}\n'
'\n\tClick the "Generate Information" button\n\n'
'To Address:\t\t{to}\n'
'Value / Amount to Send:\t{value}\n'
'Gas Limit:\t\t{gas}\n'
'Gas Price:\t\t[Accept the default value]\n'
'Nonce:\t\t\t{nonce}\n'
'Data:\t\t\t{data}\n'.format(
url=url,
_from=tx['from'],
to=tx['to'],
value=tx['value'],
gas=tx['gas'],
nonce=tx['nonce'],
data=tx['data'], )
)
webbrowser.open_new(url)
signed_tx = input('Copy and Paste the "Signed Transaction" '
'field here:\n')
if signed_tx.startswith('0x'):
signed_tx = signed_tx[2:]
return signed_tx
def check_transaction(self, tx_hash):
if 'ropsten' in ETH_REMOTE_NODE:
etherscan = 'https://ropsten.etherscan.io/tx/'
elif 'rinkeby' in ETH_REMOTE_NODE:
etherscan = 'https://rinkeby.etherscan.io/tx/'
else:
etherscan = 'https://etherscan.io/tx/'
etherscan = '{}{}'.format(etherscan, tx_hash)
print('\nYou can check the outcome of your transaction here:\n'
'{}\n\n'.format(etherscan))
def _list(self):
data_sources = self.mkt_contract.functions.getAllProviders().call()
data = []
for index, data_source in enumerate(data_sources):
if index > 0:
if 'test' not in Web3.toText(data_source).lower():
data.append(
dict(
dataset=self.to_text(data_source)
)
)
return pd.DataFrame(data)
def list(self):
df = self._list()
set_print_settings()
if df.empty:
print('There are no datasets available yet.')
else:
print(df)
def subscribe(self, dataset=None):
if dataset is None:
df_sets = self._list()
if df_sets.empty:
print('There are no datasets available yet.')
return
set_print_settings()
while True:
print(df_sets)
dataset_num = input('Choose the dataset you want to '
'subscribe to [0..{}]: '.format(
df_sets.size - 1))
try:
dataset_num = int(dataset_num)
except ValueError:
print('Enter a number between 0 and {}'.format(
df_sets.size - 1))
else:
if dataset_num not in range(0, df_sets.size):
print('Enter a number between 0 and {}'.format(
df_sets.size - 1))
else:
dataset = df_sets.iloc[dataset_num]['dataset']
break
dataset = dataset.lower()
address = self.choose_pubaddr()[0]
provider_info = self.mkt_contract.functions.getDataProviderInfo(
Web3.toHex(dataset)
).call()
if not provider_info[4]:
print('The requested "{}" dataset is not registered in '
'the Data Marketplace.'.format(dataset))
return
grains = provider_info[1]
price = from_grains(grains)
subscribed = self.mkt_contract.functions.checkAddressSubscription(
address, Web3.toHex(dataset)
).call()
if subscribed[5]:
print(
'\nYou are already subscribed to the "{}" dataset.\n'
'Your subscription started on {} UTC, and is valid until '
'{} UTC.'.format(
dataset,
pd.to_datetime(subscribed[3], unit='s', utc=True),
pd.to_datetime(subscribed[4], unit='s', utc=True)
)
)
return
print('\nThe price for a monthly subscription to this dataset is'
' {} ENG'.format(price))
print(
'Checking that the ENG balance in {} is greater than {} '
'ENG... '.format(address, price), end=''
)
wallet_address = address[2:]
balance = self.web3.eth.call({
'from': address,
'to': self.eng_contract_address,
'data': '0x70a08231000000000000000000000000{}'.format(
wallet_address
)
})
try:
balance = Web3.toInt(balance) # web3 >= 4.0.0b7
except TypeError:
balance = Web3.toInt(hexstr=balance) # web3 <= 4.0.0b6
if balance > grains:
print('OK.')
else:
print('FAIL.\n\nAddress {} balance is {} ENG,\nwhich is lower '
'than the price of the dataset that you are trying to\n'
'buy: {} ENG. Get enough ENG to cover the costs of the '
'monthly\nsubscription for what you are trying to buy, '
'and try again.'.format(
address, from_grains(balance), price))
return
while True:
agree_pay = input('Please confirm that you agree to pay {} ENG '
'for a monthly subscription to the dataset "{}" '
'starting today. [default: Y] '.format(
price, dataset)) or 'y'
if agree_pay.lower() not in ('y', 'n'):
print("Please answer Y or N.")
else:
if agree_pay.lower() == 'y':
break
else:
return
print('Ready to subscribe to dataset {}.\n'.format(dataset))
print('In order to execute the subscription, you will need to sign '
'two different transactions:\n'
'1. First transaction is to authorize the Marketplace contract '
'to spend {} ENG on your behalf.\n'
'2. Second transaction is the actual subscription for the '
'desired dataset'.format(price))
tx = self.eng_contract.functions.approve(
self.mkt_contract_address,
grains,
).buildTransaction(
{'from': address,
'nonce': self.web3.eth.getTransactionCount(address)}
)
signed_tx = self.sign_transaction(tx)
try:
tx_hash = '0x{}'.format(
bin_hex(self.web3.eth.sendRawTransaction(signed_tx))
)
print(
'\nThis is the TxHash for this transaction: {}'.format(tx_hash)
)
except Exception as e:
print('Unable to subscribe to data source: {}'.format(e))
return
self.check_transaction(tx_hash)
print('Waiting for the first transaction to succeed...')
while True:
try:
if self.web3.eth.getTransactionReceipt(tx_hash).status:
break
else:
print('\nTransaction failed. Aborting...')
return
except AttributeError:
pass
for i in range(0, 10):
print('.', end='', flush=True)
time.sleep(1)
print('\nFirst transaction successful!\n'
'Now processing second transaction.')
tx = self.mkt_contract.functions.subscribe(
Web3.toHex(dataset),
).buildTransaction({
'from': address,
'nonce': self.web3.eth.getTransactionCount(address)})
signed_tx = self.sign_transaction(tx)
try:
tx_hash = '0x{}'.format(bin_hex(
self.web3.eth.sendRawTransaction(signed_tx)))
print('\nThis is the TxHash for this transaction: '
'{}'.format(tx_hash))
except Exception as e:
print('Unable to subscribe to data source: {}'.format(e))
return
self.check_transaction(tx_hash)
print('Waiting for the second transaction to succeed...')
while True:
try:
if self.web3.eth.getTransactionReceipt(tx_hash).status:
break
else:
print('\nTransaction failed. Aborting...')
return
except AttributeError:
pass
for i in range(0, 10):
print('.', end='', flush=True)
time.sleep(1)
print('\nSecond transaction successful!\n'
'You have successfully subscribed to dataset {} with'
'address {}.\n'
'You can now ingest this dataset anytime during the '
'next month by running the following command:\n'
'catalyst marketplace ingest --dataset={}'.format(
dataset, address, dataset))
def process_temp_bundle(self, ds_name, path):
"""
Merge the temp bundle into the main bundle for the specified
data source.
Parameters
----------
ds_name
path
Returns
-------
"""
tmp_bundle = extract_bundle(path)
bundle_folder = get_data_source_folder(ds_name)
ensure_directory(bundle_folder)
if os.listdir(bundle_folder):
zsource = bcolz.ctable(rootdir=tmp_bundle, mode='r')
ztarget = bcolz.ctable(rootdir=bundle_folder, mode='r')
merge_bundles(zsource, ztarget)
else:
shutil.rmtree(bundle_folder, ignore_errors=True)
os.rename(tmp_bundle, bundle_folder)
def ingest(self, ds_name=None, start=None, end=None, force_download=False):
if ds_name is None:
df_sets = self._list()
if df_sets.empty:
print('There are no datasets available yet.')
return
set_print_settings()
while True:
print(df_sets)
dataset_num = input('Choose the dataset you want to '
'ingest [0..{}]: '.format(
df_sets.size - 1))
try:
dataset_num = int(dataset_num)
except ValueError:
print('Enter a number between 0 and {}'.format(
df_sets.size - 1))
else:
if dataset_num not in range(0, df_sets.size):
print('Enter a number between 0 and {}'.format(
df_sets.size - 1))
else:
ds_name = df_sets.iloc[dataset_num]['dataset']
break
# ds_name = ds_name.lower()
# TODO: catch error conditions
provider_info = self.mkt_contract.functions.getDataProviderInfo(
Web3.toHex(ds_name)
).call()
if not provider_info[4]:
print('The requested "{}" dataset is not registered in '
'the Data Marketplace.'.format(ds_name))
return
address, address_i = self.choose_pubaddr()
fns = self.mkt_contract.functions
check_sub = fns.checkAddressSubscription(
address, Web3.toHex(ds_name)
).call()
if check_sub[0] != address or self.to_text(check_sub[1]) != ds_name:
print('You are not subscribed to dataset "{}" with address {}. '
'Plese subscribe first.'.format(ds_name, address))
return
if not check_sub[5]:
print('Your subscription to dataset "{}" expired on {} UTC.'
'Please renew your subscription by running:\n'
'catalyst marketplace subscribe --dataset={}'.format(
ds_name,
pd.to_datetime(check_sub[4], unit='s', utc=True),
ds_name)
)
if 'key' in self.addresses[address_i]:
key = self.addresses[address_i]['key']
secret = self.addresses[address_i]['secret']
else:
key, secret = get_key_secret(address,
self.addresses[address_i]['wallet'])
headers = get_signed_headers(ds_name, key, secret)
log.info('Starting download of dataset for ingestion...')
r = requests.post(
'{}/marketplace/ingest'.format(AUTH_SERVER),
headers=headers,
stream=True,
)
if r.status_code == 200:
log.info('Dataset downloaded successfully. Processing dataset...')
target_path = get_temp_bundles_folder()
try:
decoder = MultipartDecoder.from_response(r)
# with maybe_show_progress(
# iter(decoder.parts),
# True,
# label='Processing files') as part:
counter = 1
for part in decoder.parts:
log.info("Processing file {} of {}".format(
counter, len(decoder.parts)))
h = part.headers[b'Content-Disposition'].decode('utf-8')
# Extracting the filename from the header
name = re.search(r'filename="(.*)"', h).group(1)
filename = os.path.join(target_path, name)
with open(filename, 'wb') as f:
# for chunk in part.content.iter_content(
# chunk_size=1024):
# if chunk: # filter out keep-alive new chunks
# f.write(chunk)
f.write(part.content)
self.process_temp_bundle(ds_name, filename)
counter += 1
except NonMultipartContentTypeException:
response = r.json()
raise MarketplaceHTTPRequest(
request='ingest dataset',
error=response,
)
else:
raise MarketplaceHTTPRequest(
request='ingest dataset',
error=r.status_code,
)
log.info('{} ingested successfully'.format(ds_name))
def get_dataset(self, ds_name, start=None, end=None):
ds_name = ds_name.lower()
# TODO: filter ctable by start and end date
bundle_folder = get_data_source_folder(ds_name)
z = bcolz.ctable(rootdir=bundle_folder, mode='r')
df = z.todataframe() # type: pd.DataFrame
df.set_index(['date', 'symbol'], drop=True, inplace=True)
# TODO: implement the filter more carefully
# if start and end is None:
# df = df.xs(start, level=0)
return df
def clean(self, ds_name=None, data_frequency=None):
if ds_name is None:
mktplace_root = get_marketplace_folder()
folders = [os.path.basename(f.rstrip('/'))
for f in glob.glob('{}/*/'.format(mktplace_root))
if 'temp_bundles' not in f]
while True:
for idx, f in enumerate(folders):
print('{}\t{}'.format(idx, f))
dataset_num = input('Choose the dataset you want to '
'clean [0..{}]: '.format(
len(folders) - 1))
try:
dataset_num = int(dataset_num)
except ValueError:
print('Enter a number between 0 and {}'.format(
len(folders) - 1))
else:
if dataset_num not in range(0, len(folders)):
print('Enter a number between 0 and {}'.format(
len(folders) - 1))
else:
ds_name = folders[dataset_num]
break
ds_name = ds_name.lower()
if data_frequency is None:
folder = get_data_source_folder(ds_name)
else:
folder = get_bundle_folder(ds_name, data_frequency)
shutil.rmtree(folder)
def create_metadata(self, key, secret, ds_name, data_frequency, desc,
has_history=True, has_live=True):
"""
Returns
-------
"""
headers = get_signed_headers(ds_name, key, secret)
r = requests.post(
'{}/marketplace/register'.format(AUTH_SERVER),
json=dict(
ds_name=ds_name,
desc=desc,
data_frequency=data_frequency,
has_history=has_history,
has_live=has_live,
),
headers=headers,
)
if r.status_code != 200:
raise MarketplaceHTTPRequest(
request='register', error=r.status_code
)
if 'error' in r.json():
raise MarketplaceHTTPRequest(
request='upload file', error=r.json()['error']
)
def register(self):
while True:
desc = input('Enter the name of the dataset to register: ')
dataset = desc.lower().strip()
provider_info = self.mkt_contract.functions.getDataProviderInfo(
Web3.toHex(dataset)
).call()
if provider_info[4]:
print('There is already a dataset registered under '
'the name "{}". Please choose a different '
'name.'.format(dataset))
else:
break
price = int(
input(
'Enter the price for a monthly subscription to '
'this dataset in ENG: '
)
)
while True:
freq = input('Enter the data frequency [daily, hourly, minute]: ')
if freq.lower() not in ('daily', 'hourly', 'minute'):
print('Not a valid frequency.')
else:
break
while True:
reg_pub = input(
'Does it include historical data? [default: Y]: '
) or 'y'
if reg_pub.lower() not in ('y', 'n'):
print('Please answer Y or N.')
else:
if reg_pub.lower() == 'y':
has_history = True
else:
has_history = False
break
while True:
reg_pub = input(
'Doest it include live data? [default: Y]: '
) or 'y'
if reg_pub.lower() not in ('y', 'n'):
print('Please answer Y or N.')
else:
if reg_pub.lower() == 'y':
has_live = True
else:
has_live = False
break
address, address_i = self.choose_pubaddr()
if 'key' in self.addresses[address_i]:
key = self.addresses[address_i]['key']
secret = self.addresses[address_i]['secret']
else:
key, secret = get_key_secret(address,
self.addresses[address_i]['wallet'])
grains = to_grains(price)
tx = self.mkt_contract.functions.register(
Web3.toHex(dataset),
grains,
address,
).buildTransaction(
{'from': address,
'nonce': self.web3.eth.getTransactionCount(address)}
)
signed_tx = self.sign_transaction(tx)
try:
tx_hash = '0x{}'.format(
bin_hex(self.web3.eth.sendRawTransaction(signed_tx))
)
print(
'\nThis is the TxHash for this transaction: {}'.format(tx_hash)
)
except Exception as e:
print('Unable to register the requested dataset: {}'.format(e))
return
self.check_transaction(tx_hash)
print('Waiting for the transaction to succeed...')
while True:
try:
if self.web3.eth.getTransactionReceipt(tx_hash).status:
break
else:
print('\nTransaction failed. Aborting...')
return
except AttributeError:
pass
for i in range(0, 10):
print('.', end='', flush=True)
time.sleep(1)
print('\nWarming up the {} dataset'.format(dataset))
self.create_metadata(
key=key,
secret=secret,
ds_name=dataset,
data_frequency=freq,
desc=desc,
has_history=has_history,
has_live=has_live,
)
print('\n{} registered successfully'.format(dataset))
def publish(self, dataset, datadir, watch):
dataset = dataset.lower()
provider_info = self.mkt_contract.functions.getDataProviderInfo(
Web3.toHex(dataset)
).call()
if not provider_info[4]:
raise MarketplaceDatasetNotFound(dataset=dataset)
match = next(
(l for l in self.addresses if l['pubAddr'] == provider_info[0]),
None
)
if not match:
raise MarketplaceNoAddressMatch(
dataset=dataset,
address=provider_info[0])
print('Using address: {} to publish this dataset.'.format(
provider_info[0]))
if 'key' in match:
key = match['key']
secret = match['secret']
else:
key, secret = get_key_secret(provider_info[0], match['wallet'])
filenames = glob.glob(os.path.join(datadir, '*.csv'))
if not filenames:
raise MarketplaceNoCSVFiles(datadir=datadir)
files = []
for idx, file in enumerate(filenames):
log.info('Uploading file {} of {}: {}'.format(
idx+1, len(filenames), file))
files = []
files.append(('file', open(file, 'rb')))
headers = get_signed_headers(dataset, key, secret)
r = requests.post('{}/marketplace/publish'.format(AUTH_SERVER),
files=files,
headers=headers)
if r.status_code != 200:
raise MarketplaceHTTPRequest(request='upload file',
error=r.status_code)
if 'error' in r.json():
raise MarketplaceHTTPRequest(request='upload file',
error=r.json()['error'])
log.info('File processed successfully.')
print('\nDataset {} uploaded and processed successfully.'.format(
dataset))
@@ -0,0 +1,97 @@
import sys
import traceback
from catalyst.errors import ZiplineError
def silent_except_hook(exctype, excvalue, exctraceback):
if exctype in [MarketplacePubAddressEmpty, MarketplaceDatasetNotFound,
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
MarketplaceNoCSVFiles, MarketplaceContractDataNoMatch,
MarketplaceSubscriptionExpired, MarketplaceJSONError,
MarketplaceWalletNotSupported, MarketplaceEmptySignature,
MarketplaceRequiresPython3]:
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 MarketplacePubAddressEmpty(ZiplineError):
msg = (
'Please enter your public address to use in the Data Marketplace '
'in the following file: {filename}'
).strip()
class MarketplaceDatasetNotFound(ZiplineError):
msg = (
'The dataset "{dataset}" is not registered in the Data Marketplace.'
).strip()
class MarketplaceNoAddressMatch(ZiplineError):
msg = (
'The address registered with the dataset {dataset}: {address} '
'does not match any of your addresses.'
).strip()
class MarketplaceHTTPRequest(ZiplineError):
msg = (
'Request to remote server to {request} failed: {error}'
).strip()
class MarketplaceNoCSVFiles(ZiplineError):
msg = (
'No CSV files found on {datadir} to upload.'
)
class MarketplaceContractDataNoMatch(ZiplineError):
msg = (
'The information found on the contract does not match the '
'requested data:\n{params}.'
)
class MarketplaceSubscriptionExpired(ZiplineError):
msg = (
'Your subscription to dataset "{dataset}" expired on {date} '
'and is no longer active. You have to subscribe again running the '
'following command:\n'
'catalyst marketplace subscribe --dataset={dataset}'
)
class MarketplaceWalletNotSupported(ZiplineError):
msg = (
'Wallet {wallet} is not supported.'
)
class MarketplaceEmptySignature(ZiplineError):
msg = (
'Signature cannot be empty.'
)
class MarketplaceJSONError(ZiplineError):
msg = (
'The configuration file {file} is malformed. Please correct '
'the following error:\n{error}'
)
class MarketplaceRequiresPython3(ZiplineError):
msg = (
'\nCatalyst requires Python3 to access the Enigma Data Marketplace.\n'
'If you want to use the Data Marketplace, you need to reinstall '
'Catalyst\nwith Python3. See the documentation website for additional '
'information.')
+141
View File
@@ -0,0 +1,141 @@
import hashlib
import hmac
import webbrowser
import requests
import time
from catalyst.marketplace.marketplace_errors import (
MarketplaceHTTPRequest, MarketplaceWalletNotSupported,
MarketplaceEmptySignature)
from catalyst.marketplace.utils.path_utils import (
get_user_pubaddr, save_user_pubaddr)
from catalyst.constants import AUTH_SERVER, SUPPORTED_WALLETS
def get_key_secret(pubAddr, wallet):
"""
Obtain a new key/secret pair from authentication server
Parameters
----------
pubAddr: str
dataset: str
Returns
-------
key: str
secret: str
"""
session = requests.Session()
response = session.get('{}/marketplace/getkeysecret'.format(AUTH_SERVER),
headers={
'Authorization': 'Digest username="{0}"'.format(
pubAddr)})
if response.status_code != 401:
raise MarketplaceHTTPRequest(request=str('obtain key/secret'),
error='Unexpected response code: '
'{}'.format(response.status_code))
header = response.headers.get('WWW-Authenticate')
auth_type, auth_info = header.split(None, 1)
d = requests.utils.parse_dict_header(auth_info)
nonce = 'Catalyst nonce: 0x{}'.format(d['nonce'])
if wallet in SUPPORTED_WALLETS:
url = 'https://www.mycrypto.com/signmsg.html'
print('\nObtaining a key/secret pair to streamline all future '
'requests with the authentication server.\n'
'Visit {url} and sign the '
'following message (copy the entire line, without the '
'line break at the end):\n\n{nonce}'.format(
url=url,
nonce=nonce))
webbrowser.open_new(url)
signature = input('\nCopy and Paste the "sig" field from '
'the signature here (without the double quotes, '
'only the HEX value):\n')
else:
raise MarketplaceWalletNotSupported(wallet=wallet)
if signature is None:
raise MarketplaceEmptySignature()
signature = signature[2:]
r = int(signature[0:64], base=16)
s = int(signature[64:128], base=16)
v = int(signature[128:130], base=16)
vrs = [v, r, s]
response = session.get('{}/marketplace/getkeysecret'.format(AUTH_SERVER),
headers={
'Authorization': 'Digest username="{0}",realm="{1}",'
'nonce="{2}",uri="/marketplace/getkeysecret",response="{3}",'
'opaque="{4}"'.format(pubAddr,
d['realm'],
d['nonce'],
','.join(str(e) for e in vrs+[wallet]),
d['opaque'])})
if response.status_code == 200:
if 'error' in response.json():
raise MarketplaceHTTPRequest(request=str('obtain key/secret'),
error=str(response.json()['error']))
else:
addresses = get_user_pubaddr()
match = next((l for l in addresses if
l['pubAddr'].lower() == pubAddr.lower()), None)
match['key'] = response.json()['key']
match['secret'] = response.json()['secret']
addresses[addresses.index(match)] = match
save_user_pubaddr(addresses)
print('Key/secret pair retrieved successfully from server.')
return match['key'], match['secret']
else:
raise MarketplaceHTTPRequest(request=str('obtain key/secret'),
error=response.status_code)
def get_signed_headers(ds_name, key, secret):
"""
Return a new request header including the key / secret signature
Parameters
----------
ds_name
key
secret
Returns
-------
"""
nonce = str(int(time.time() * 1000))
signature = hmac.new(
secret.encode('utf-8'),
'{}{}'.format(ds_name, nonce).encode('utf-8'),
hashlib.sha512
).hexdigest()
headers = {
'Sign': signature,
'Key': key,
'Nonce': nonce,
'Dataset': ds_name,
}
return headers
@@ -0,0 +1,94 @@
import os
import random
import re
import shutil
import bcolz
import numpy as np
import pandas as pd
from six import string_types
def merge_bundles(zsource, ztarget):
"""
Merge
Parameters
----------
zsource
ztarget
Returns
-------
"""
# TODO: find a way to do this iteratively instead of in-memory
df_source = zsource.todataframe()
df_target = ztarget.todataframe()
df = pd.concat(
[df_source, df_target], ignore_index=True
) # type: pd.DataFrame
df.drop_duplicates(inplace=True)
df.set_index(['date', 'symbol'], drop=False, inplace=True)
sanitize_df(df)
dirname = os.path.basename(ztarget.rootdir)
bak_dir = ztarget.rootdir.replace(dirname, '.{}'.format(dirname))
shutil.move(ztarget.rootdir, bak_dir)
z = bcolz.ctable.fromdataframe(df=df, rootdir=ztarget.rootdir)
shutil.rmtree(bak_dir)
return z
def sanitize_df(df):
# Using a sampling method to identify dates for efficiency with
# large datasets
if len(df) > 100:
indexes = random.sample(range(0, len(df) - 1), 100)
elif len(df) > 1:
indexes = range(0, len(df) - 1)
else:
indexes = [0, ]
for column in df.columns:
is_date = False
for index in indexes:
value = df[column].iloc[index]
if not isinstance(value, string_types):
continue
# TODO: assuming that the date is at least daily
exp = re.compile(r'^\d{4}-\d{2}-\d{2}.*$')
matches = exp.findall(value)
if matches:
is_date = True
break
if is_date:
df[column] = pd.to_datetime(df[column])
else:
try:
ser = safely_reduce_dtype(df[column])
df[column] = ser
except Exception:
pass
return df
def safely_reduce_dtype(ser): # pandas.Series or numpy.array
orig_dtype = "".join(
[x for x in ser.dtype.name if x.isalpha()]) # float/int
mx = 1
for val in ser.values:
new_itemsize = np.min_scalar_type(val).itemsize
if mx < new_itemsize:
mx = new_itemsize
if orig_dtype == 'int':
mx = max(mx, 4)
new_dtype = orig_dtype + str(mx * 8)
return ser.astype(new_dtype)
+82
View File
@@ -0,0 +1,82 @@
import binascii
# def bytes32(string):
# """
# Convert string to bytes32 data type for smart contract
# Parameters
# ----------
# string: str
# Returns
# -------
# list
# """
# return binascii.hexlify(string.encode('utf-8'))
# def b32_str(bytes32):
# """
# Convert bytes32 to string
# Parameters
# ----------
# input: bytes object
# Returns
# -------
# str
# """
# return binascii.unhexlify(
# bytes32.decode('utf-8').rstrip('\0')).decode('ascii')
def bin_hex(binary):
"""
Convert bytes32 to string
Parameters
----------
input: bytes object
Returns
-------
str
"""
return binascii.hexlify(binary).decode('utf-8')
def from_grains(amount):
"""
Convert from grains to cryptocurrency
Parameters
----------
input: amount
Returns
-------
int
"""
return amount // 10 ** 8
def to_grains(amount):
"""
Convert from cryptocurrency to grains
Parameters
----------
input: amount
Returns
-------
int
"""
return amount * 10 ** 8
+213
View File
@@ -0,0 +1,213 @@
import os
import json
import tarfile
from catalyst.constants import SUPPORTED_WALLETS
from catalyst.utils.deprecate import deprecated
from catalyst.utils.paths import data_root, ensure_directory
from catalyst.marketplace.marketplace_errors import MarketplaceJSONError
def get_marketplace_folder(environ=None):
"""
The root path of the marketplace folder.
Parameters
----------
environ:
Returns
-------
str
"""
if not environ:
environ = os.environ
root = data_root(environ)
marketplace_folder = os.path.join(root, 'marketplace')
ensure_directory(marketplace_folder)
return marketplace_folder
def get_data_source_folder(data_source_name, environ=None):
"""
The root path of an data_source folder.
Parameters
----------
data_source_name: str
environ:
Returns
-------
str
"""
if not environ:
environ = os.environ
root = data_root(environ)
data_source_folder = os.path.join(root, 'marketplace', data_source_name)
ensure_directory(data_source_folder)
return data_source_folder
@deprecated
def get_bundle_folder(data_source_name, data_frequency, environ=None):
data_source_folder = get_data_source_folder(data_source_name, environ)
bundle_folder = os.path.join(data_source_folder, data_frequency)
ensure_directory(bundle_folder)
return bundle_folder
def get_temp_bundles_folder(environ=None):
"""
The temp folder for bundle downloads by algo name.
Parameters
----------
ds_name: str
environ:
Returns
-------
str
"""
root = data_root(environ)
folder = os.path.join(root, 'marketplace', 'temp_bundles')
ensure_directory(folder)
return folder
def extract_bundle(tar_filename):
"""
Extract a bcolz bundle.
Parameters
----------
ds_name
Returns
-------
str
"""
target_path = tar_filename.replace('.tar.gz', '')
with tarfile.open(tar_filename, 'r') as tar:
tar.extractall(target_path)
return target_path
def get_user_pubaddr(environ=None):
"""
The de-serialized contend of the user's addresses.json file.
Parameters
----------
environ:
Returns
-------
Object
"""
marketplace_folder = get_marketplace_folder(environ)
filename = os.path.join(marketplace_folder, 'addresses.json')
if os.path.isfile(filename):
with open(filename) as data_file:
try:
data = json.load(data_file)
except json.decoder.JSONDecodeError as e:
raise MarketplaceJSONError(file=filename, error=e)
try:
d = data[0]['pubAddr']
except Exception as e:
data = [data, ]
changed = False
for idx, d in enumerate(data):
try:
if d['wallet'] not in SUPPORTED_WALLETS:
data[idx]['wallet'] = _choose_wallet(
d['pubAddr'], False)
changed = True
except KeyError:
data[idx]['wallet'] = _choose_wallet(
d['pubAddr'], True)
changed = True
if changed:
save_user_pubaddr(data)
return data
else:
data = []
data.append(dict(pubAddr='', desc='', wallet=''))
with open(filename, 'w') as f:
json.dump(data, f, sort_keys=False, indent=2,
separators=(',', ':'))
return data
def _choose_wallet(pubAddr, missing):
while True:
if missing:
print('\nYou need to specify a wallet for address '
'{}.'.format(pubAddr))
else:
print('\nThe wallet specified for address {} is not '
'supported.'.format(pubAddr))
print('Please choose among the following options:')
for idx, wallet in enumerate(SUPPORTED_WALLETS):
print('{}\t{}'.format(idx, wallet))
lw = len(SUPPORTED_WALLETS)-1
w = input('Choose a number between 0 and {}: '.format(
lw))
try:
w = int(w)
except ValueError:
print('Enter a number between 0 and {}'.format(lw))
else:
if w not in range(0, lw+1):
print('Enter a number between 0 and '
'{}'.format(lw))
else:
return SUPPORTED_WALLETS[w]
def save_user_pubaddr(data, environ=None):
"""
Saves the user's public addresses and their related metadata in
the corresponding addresses.json file.
Parameters
----------
data: dict
Returns
-------
True
"""
marketplace_folder = get_marketplace_folder(environ)
filename = os.path.join(marketplace_folder, 'addresses.json')
with open(filename, 'w') as f:
json.dump(data, f, sort_keys=False, indent=2,
separators=(',', ':'))
return True
+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,
+2 -2
View File
@@ -144,7 +144,7 @@ class SpecificEquityTrades(object):
for identifier in self.identifiers:
assets_by_identifier[identifier] = env.asset_finder.\
lookup_generic(identifier, datetime.now())[0]
self.sids = [asset.sid for asset in assets_by_identifier.values()]
self.sids = [asset.sid for asset in list(assets_by_identifier.values())]
for event in self.event_list:
event.sid = assets_by_identifier[event.sid].sid
@@ -167,7 +167,7 @@ class SpecificEquityTrades(object):
for identifier in self.identifiers:
assets_by_identifier[identifier] = env.asset_finder.\
lookup_generic(identifier, datetime.now())[0]
self.sids = [asset.sid for asset in assets_by_identifier.values()]
self.sids = [asset.sid for asset in list(assets_by_identifier.values())]
# Hash_value for downstream sorting.
self.arg_string = hash_args(*args, **kwargs)
+57
View File
@@ -0,0 +1,57 @@
import pandas as pd
from catalyst import run_algorithm
def initialize(context):
context.i = -1 # counts the minutes
context.exchange = 'cryptopia'
context.base_currency = 'btc'
context.coins = context.exchanges[context.exchange].assets
context.coins = [c for c in context.coins if
c.quote_currency == context.base_currency]
def handle_data(context, data):
# current date formatted into a string
today = data.current_dt
# update universe everyday
new_day = 60 * 24 # assuming data_frequency='minute'
if not context.i % new_day:
context.coins = context.exchanges[context.exchange].assets
context.coins = [c for c in context.coins if
c.quote_currency == context.base_currency]
# get data every 30 minutes
minutes = 1
if not context.i % minutes:
# we iterate for every pair in the current universe
for coin in context.coins:
pair = str(coin.symbol)
price = data.current(coin, 'price')
print(today, pair, price)
def analyze(context=None, results=None):
pass
if __name__ == '__main__':
start_date = pd.to_datetime('2018-01-17', utc=True)
end_date = pd.to_datetime('2018-01-18', utc=True)
performance = run_algorithm(
capital_base=1.0,
# amount of base_currency, not always in dollars unless usd
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='cryptopia',
data_frequency='minute',
base_currency='btc',
live=True,
live_graph=False,
simulate_orders=True,
algo_namespace='simple_universe'
)
+8
View File
@@ -0,0 +1,8 @@
import ccxt
bitfinex = ccxt.bitfinex()
bitfinex.verbose = True
ohlcvs = bitfinex.fetch_ohlcv('ETH/BTC', '30m', 1504224000000)
dt = bitfinex.iso8601(ohlcvs[0][0])
print(dt) # should print '2017-09-01T00:00:00.000Z'
@@ -0,0 +1,50 @@
import pandas as pd
from catalyst import run_algorithm
from catalyst.api import symbol
def initialize(context):
context.asset1 = symbol('fct_btc')
context.asset2 = symbol('btc_usdt')
context.coins = [context.asset1, context.asset2]
def handle_data(context, data):
df = data.history(context.coins,
'close',
bar_count=10,
frequency='5T',
)
print(df)
print(data.current(context.asset1, 'close'))
print(data.current(context.asset2, 'close'))
exit(0)
if __name__ == '__main__':
LIVE = True
if LIVE:
run_algorithm(
capital_base=1,
initialize=initialize,
handle_data=handle_data,
exchange_name='poloniex',
algo_namespace='test_multi_assets',
base_currency='usdt',
live=True,
simulate_orders=True,
)
else:
run_algorithm(
capital_base=1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
exchange_name='poloniex',
algo_namespace='test_multi_assets',
base_currency='usdt',
live=False,
start=pd.to_datetime('2017-12-1', utc=True),
end=pd.to_datetime('2017-12-1', utc=True),
)
+44
View File
@@ -0,0 +1,44 @@
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import order_target_percent
NAMESPACE = 'goose7'
log = Logger(NAMESPACE)
from catalyst.api import record, symbol
def initialize(context):
context.asset = symbol('trx_btc')
def handle_data(context, data):
price = data.current(context.asset, 'price')
record(btc=price)
# Only ordering if it does not have any position to avoid trying some
# tiny orders with the leftover btc
pos_amount = context.portfolio.positions[context.asset].amount
if pos_amount > 0:
return
# Adding a limit price to workaround an issue with performance
# calculations of market orders
order_target_percent(
context.asset, 1, limit_price=price * 1.01
)
if __name__ == '__main__':
run_algorithm(
capital_base=0.003,
initialize=initialize,
handle_data=handle_data,
exchange_name='binance',
live=True,
algo_namespace=NAMESPACE,
base_currency='btc',
live_graph=False,
simulate_orders=False,
)
+44
View File
@@ -0,0 +1,44 @@
import pandas as pd
from catalyst import run_algorithm
from catalyst.api import symbol
def initialize(context):
context.asset = symbol('btc_usdt')
def handle_data(context, data):
df = data.history(context.asset,
'close',
bar_count=10,
frequency='5T',
)
if __name__ == '__main__':
LIVE = True
if LIVE:
run_algorithm(
capital_base=1,
initialize=initialize,
handle_data=handle_data,
exchange_name='poloniex',
algo_namespace='test_algo',
base_currency='usdt',
live=True,
simulate_orders=True,
)
else:
run_algorithm(
capital_base=1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
exchange_name='poloniex',
algo_namespace='test_algo',
base_currency='usdt',
live=False,
start=pd.to_datetime('2017-12-1', utc=True),
end=pd.to_datetime('2017-12-1', utc=True),
)
+44
View File
@@ -0,0 +1,44 @@
import pandas as pd
from catalyst.utils.run_algo import run_algorithm
from catalyst.api import symbol
from exchange.utils.stats_utils import set_print_settings
def initialize(context):
context.i = 0
context.data = []
def handle_data(context, data):
prices = data.history(
symbol('xlm_eth'),
fields=['open', 'high', 'low', 'close'],
bar_count=50,
frequency='1T'
)
set_print_settings()
print(prices.tail(10))
context.data.append(prices)
context.i = context.i + 1
if context.i == 3:
context.interrupt_algorithm()
def analyze(context, prefs):
for dataset in context.data:
print(dataset[-2:])
if __name__ == '__main__':
run_algorithm(
capital_base=0.1,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='binance',
algo_namespace='Test candles',
base_currency='eth',
data_frequency='minute',
live=True,
simulate_orders=True)
+376
View File
@@ -0,0 +1,376 @@
# -*- coding: utf-8 -*-
# !/usr/bin/env python2
import sys
import os
import pandas as pd
import signal
# import talib
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import (
symbol,
record,
order,
order_target,
order_target_percent,
get_open_orders
)
from catalyst.finance import commission
# from base.telegrambot import TelegramBot
class GracefulKiller:
# Source: https://stackoverflow.com/a/31464349
def __init__(self, context):
self.kill_now = False
self.signal = 0
self.context = context
signal.signal(signal.SIGINT, self.exit_gracefully)
def exit_gracefully(self, signum, frame):
self.kill_now = True
self.signal = signum
if hasattr(self.context,
'telegram_bot') and self.context.telegram_bot is not None:
self.context.telegram_bot.updater.stop()
sys.exit(0)
def exit(self):
return self.kill_now
class SimulationParameters:
MODE = 'paper'
CAPITAL_BASE = 1000
"""
Capital base used on this simulation
"""
DATA_FREQUECY = 'minute'
EXCHANGE_NAME = 'bitfinex'
# EXCHANGE_NAME = 'binance'
"""
Exchange used on this simulation
"""
DATA_DIR = '/home/av/Dropbox/simulations/data'
ALGO_NAMESPACE = os.path.basename(__file__).split('.')[0]
ALGO_NAMESPACE_IMAGE = '{}/{}/{}.png'.format(DATA_DIR, 'images',
ALGO_NAMESPACE)
ALGO_NAMESPACE_RESULTS_TABLE = '{}/{}/{}.csv'.format(DATA_DIR, 'tables',
ALGO_NAMESPACE + '_results')
ALGO_NAMESPACE_TRANSACTIONS_TABLE = '{}/{}/{}.csv'.format(DATA_DIR,
'tables',
ALGO_NAMESPACE + '_transactions')
BASE_CURRENCY = 'usd'
# BASE_CURRENCY = 'usdt'
# SHORT PERIOD
START_DATE = '2017-09-07'
"""
Start date used on this simulation
"""
END_DATE = '2017-12-12'
"""
End date used on this simulation
"""
SKIP_FIRST_CANDLES = 0
# CANDLES_SAMPLE_RATE = 60
# CANDLES_SAMPLE_RATE = 30
CANDLES_SAMPLE_RATE = 1
"""
Candle interval used on this simulation (in minutes)
"""
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
# 30 minute interval ohlcv data (the standard data required for candlestick or
# indicators/signals)
# 30T means 30 minutes re-sampling of one minute data.
# CANDLES_FREQUENCY = '60T'
# CANDLES_FREQUENCY = '30T'
CANDLES_FREQUENCY = '1T'
CANDLES_BUFFER_SIZE = 48
COIN_PAIR = 'btc_usd'
# COIN_PAIR = 'btc_usdt'
"""
Coin pair used on this simulation
"""
# TRANSACTIONS
COMMISSION_FEE = 0.0030
BUY_MIN_AMOUNT = 5 # i.e: USD
SELL_MIN_AMOUNT = 0.001 # i.e: USD
BUY_SELL_PERCENTAGE = 1 # 0.50
BUY_PERCENTAGE = BUY_SELL_PERCENTAGE
SELL_PERCENTAGE = BUY_SELL_PERCENTAGE
BASE_PRICE = 'close'
"""
Base price used (close / Heiken Ashi)
"""
log = None
parameters = None
def print_facts(context):
context.log.info("""
Index: {}
Date: {}
Candle:
O: {}
H: {}
L: {}
C: {}
V: {}
Metrics:
...
Portfolio:
Base price: {}
Base coin (coin2/usd): {}
Amount (coin1/btc): {}
""".format(
# Facts
context.i,
context.curr_minute,
context.candles_open[-1],
context.candles_high[-1],
context.candles_low[-1],
context.candles_close[-1],
context.candles_volume[-1],
# Metrics
# ...
# Portfolio
context.curr_base_price,
context.portfolio.cash,
context.portfolio.positions[context.coin_pair].amount,
))
def print_facts_telegram(context):
price = context.curr_base_price
amount = context.portfolio.positions[context.coin_pair].amount
pnl = context.portfolio.pnl
capital_used = context.portfolio.capital_used
portfolio_value = context.portfolio.portfolio_value
portfolio_returns = context.portfolio.returns
starting_cash = context.portfolio.starting_cash
cash = context.portfolio.cash
msg = """
Status...
Price: {}
Starting cash: {}
Cash: {}
Capital used: {}
Amount: {}
Portfolio value: {}
Returns: {}
PnL: {}
""".format(
price,
starting_cash,
cash,
capital_used,
amount,
portfolio_value,
portfolio_returns,
pnl,
)
if hasattr(context, 'telegram_bot') and context.telegram_bot is not None:
context.telegram_bot.msg(msg)
def default_initialize(context):
# FIXME: set_benchmark
# set_benchmark(symbol(context.parameters.COIN_PAIR))
context.coin_pair = symbol(context.parameters.COIN_PAIR)
context.base_price = None
context.current_day = None
context.counter = -1
context.i = 0
context.candles_sample_rate = context.parameters.CANDLES_SAMPLE_RATE
context.candles_frequency = context.parameters.CANDLES_FREQUENCY
context.candles_buffer_size = context.parameters.CANDLES_BUFFER_SIZE
context.set_commission(
commission.PerShare(cost=context.parameters.COMMISSION_FEE))
def default_handle_data(context, data):
context.curr_minute = data.current_dt
context.counter += 1
if context.candles_sample_rate == 1:
context.i += 1
elif context.counter % context.candles_sample_rate != 0:
context.i += 1
return
if context.i < context.parameters.SKIP_FIRST_CANDLES:
return
context.candles_open = data.history(
context.coin_pair,
'open',
bar_count=context.candles_buffer_size,
frequency=context.candles_frequency)
context.candles_high = data.history(
context.coin_pair,
'high',
bar_count=context.candles_buffer_size,
frequency=context.candles_frequency)
context.candles_low = data.history(
context.coin_pair,
'low',
bar_count=context.candles_buffer_size,
frequency=context.candles_frequency)
context.candles_close = data.history(
context.coin_pair,
'price',
bar_count=context.candles_buffer_size,
frequency=context.candles_frequency)
context.candles_volume = data.history(
context.coin_pair,
'volume',
bar_count=context.candles_buffer_size,
frequency=context.candles_frequency)
# FIXME: Here is the error!
# The candles_close frame shows more or less always a value of 94, while
# bitcoin price is very different from that
print(context.candles_close)
context.base_prices = context.candles_close
cash = context.portfolio.cash
amount = context.portfolio.positions[context.coin_pair].amount
price = data.current(context.coin_pair, 'price')
order_id = None
context.last_base_price = context.base_prices[-2]
context.curr_base_price = context.base_prices[-1]
# TA calculations
# ...
# Sanity checks
# assert cash >= 0
if cash < 0:
import ipdb;
ipdb.set_trace() # BREAKPOINT
print_facts(context)
print_facts_telegram(context)
# Order management
net_shares = 0
if context.counter == 2:
brute_shares = (cash / price) * context.parameters.BUY_PERCENTAGE
share_commission_fee = brute_shares * context.parameters.COMMISSION_FEE
net_shares = brute_shares - share_commission_fee
buy_order_id = order(context.coin_pair, net_shares)
if context.counter == 3:
brute_shares = amount * context.parameters.SELL_PERCENTAGE
share_commission_fee = brute_shares * context.parameters.COMMISSION_FEE
net_shares = -(brute_shares - share_commission_fee)
sell_order_id = order(context.coin_pair, net_shares)
# Record
record(
price=price,
foo='bar',
# volume=current['volume'],
# price_change=price_change,
# Metrics
cash=cash,
# buy=context.buy,
# sell=context.sell
)
def default_analyze(context=None, perf=None):
pass
def initialize(context):
global log
context.parameters = parameters
context.log = Logger(context.parameters.ALGO_NAMESPACE)
log = context.log
default_initialize(context)
context.killer = GracefulKiller(context)
context.telegram_bot = None
# TELEGRAM_TOKEN='token'
# context.telegram_bot = TelegramBot()
# context.telegram_bot.initialize(TELEGRAM_TOKEN, context)
if __name__ == '__main__':
# Parameters:
parameters = SimulationParameters()
start_date = pd.to_datetime(parameters.START_DATE, utc=True)
end_date = pd.to_datetime(parameters.END_DATE, utc=True)
if parameters.MODE == 'backtest':
results = run_algorithm(
capital_base=parameters.CAPITAL_BASE,
data_frequency=parameters.DATA_FREQUECY,
initialize=initialize,
handle_data=default_handle_data,
analyze=default_analyze,
exchange_name=parameters.EXCHANGE_NAME,
algo_namespace=parameters.ALGO_NAMESPACE,
base_currency=parameters.BASE_CURRENCY,
start=start_date,
end=end_date,
live=False,
live_graph=False
)
returns_daily = results
results.to_csv('{}'.format(parameters.ALGO_NAMESPACE_RESULTS_TABLE))
# returns_daily = returns_minutely.add(1).groupby(pd.TimeGrouper('24H')).prod().add(-1)
# FIXME: pyfolio integration
# pf_data = pyfolio.utils.extract_rets_pos_txn_from_zipline(results)
# pf_data = pyfolio.utils.extract_rets_pos_txn_from_zipline(results[:'2017-01-01'])
# pyfolio.create_full_tear_sheet(*pf_data)
elif parameters.MODE == 'paper':
results = run_algorithm(
capital_base=parameters.CAPITAL_BASE,
data_frequency=parameters.DATA_FREQUECY,
initialize=initialize,
handle_data=default_handle_data,
analyze=default_analyze,
exchange_name=parameters.EXCHANGE_NAME,
algo_namespace=parameters.ALGO_NAMESPACE,
base_currency=parameters.BASE_CURRENCY,
live=True,
simulate_orders=True,
live_graph=False
)
elif parameters.MODE == 'live':
results = run_algorithm(
initialize=initialize,
handle_data=default_handle_data,
analyze=default_analyze,
exchange_name=parameters.EXCHANGE_NAME,
algo_namespace=parameters.ALGO_NAMESPACE,
base_currency=parameters.BASE_CURRENCY,
live=True,
live_graph=True
)
+49
View File
@@ -0,0 +1,49 @@
import pytz
from datetime import datetime
from catalyst.api import symbol
from catalyst.utils.run_algo import run_algorithm
coin = 'btc'
base_currency = 'usd'
n_candles = 5
def initialize(context):
context.symbol = symbol('%s_%s' % (coin, base_currency))
def handle_data_polo_partial_candles(context, data):
history = data.history(symbol('btc_usdt'), ['volume'],
bar_count=10,
frequency='4H')
print('\nnow: %s\n%s' % (data.current_dt, history))
if not hasattr(context, 'i'):
context.i = 0
context.i += 1
if context.i > 5:
raise Exception('stop')
live = False
if live:
run_algorithm(initialize=lambda ctx: True,
handle_data=handle_data_polo_partial_candles,
exchange_name='poloniex',
base_currency='usdt',
algo_namespace='ns',
live=True,
data_frequency='minute',
capital_base=3000)
else:
run_algorithm(initialize=lambda ctx: True,
handle_data=handle_data_polo_partial_candles,
exchange_name='poloniex',
base_currency='usdt',
algo_namespace='ns',
live=False,
data_frequency='minute',
capital_base=3000,
start=datetime(2018, 2, 2, 0, 0, 0, 0, pytz.utc),
end=datetime(2018, 2, 20, 0, 0, 0, 0, pytz.utc)
)
+32
View File
@@ -0,0 +1,32 @@
from catalyst.api import symbol
from catalyst.utils.run_algo import run_algorithm
coins = ['dash', 'btc', 'dash', 'etc', 'eth', 'ltc', 'nxt', 'rep', 'str', 'xmr', 'xrp', 'zec']
symbols = None
def initialize(context):
pass
def _handle_data(context, data):
global symbols
if symbols is None: symbols = [symbol(c + '_usdt') for c in coins]
print'getting history for: %s' % [s.symbol for s in symbols]
history = data.history(symbols,
['close', 'volume'],
bar_count=1, # EXCEPTION, Change to 2
frequency='5T')
#print 'history: %s' % history.shape
run_algorithm(initialize=initialize,
handle_data=_handle_data,
analyze=lambda _, results: True,
exchange_name='poloniex',
base_currency='usdt',
algo_namespace='issue-236',
live=True,
data_frequency='minute',
capital_base=3000,
simulate_orders=True)
+35
View File
@@ -0,0 +1,35 @@
import pytz
from datetime import datetime
from catalyst.api import symbol
from catalyst.utils.run_algo import run_algorithm
coin = 'btc'
base_currency = 'usd'
def initialize(context):
context.symbol = symbol('%s_%s' % (coin, base_currency))
def handle_data_polo_partial_candles(context, data):
history = data.history(symbol('btc_usdt'), ['volume'],
bar_count=10,
frequency='1D')
print('\nnow: %s\n%s' % (data.current_dt, history))
if not hasattr(context, 'i'):
context.i = 0
context.i += 1
if context.i > 5:
raise Exception('stop')
run_algorithm(initialize=lambda ctx: True,
handle_data=handle_data_polo_partial_candles,
exchange_name='poloniex',
base_currency='usdt',
algo_namespace='ns',
live=False,
data_frequency='minute',
capital_base=3000,
start=datetime(2018, 2, 2, 0, 0, 0, 0, pytz.utc),
end=datetime(2018, 2, 20, 0, 0, 0, 0, pytz.utc))
@@ -7,7 +7,7 @@ from pandas.tseries.holiday import (
USLaborDay,
USThanksgivingDay
)
from pandas.tslib import Timestamp
from pandas import Timestamp
from pytz import timezone
from catalyst.utils.calendars import TradingCalendar
+2 -5
View File
@@ -640,12 +640,9 @@ class TradingCalendar(with_metaclass(ABCMeta)):
"""
sched = self.schedule
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (
sched.at[session_label, 'market_open'].tz_localize('UTC'),
sched.at[session_label, 'market_close'].tz_localize('UTC'),
sched.at[session_label, 'market_open'],
sched.at[session_label, 'market_close'],
)
def session_open(self, session_label):
+6 -6
View File
@@ -117,9 +117,9 @@ def create_dividend(sid, payment, declared_date, ex_date, pay_date):
'net_amount': payment,
'payment_sid': None,
'ratio': None,
'declared_date': pd.tslib.normalize_date(declared_date),
'ex_date': pd.tslib.normalize_date(ex_date),
'pay_date': pd.tslib.normalize_date(pay_date),
'declared_date': declared_date.normalize(),
'ex_date': ex_date.normalize(),
'pay_date': pay_date.normalize(),
'type': DATASOURCE_TYPE.DIVIDEND,
'source_id': 'MockDividendSource'
})
@@ -134,9 +134,9 @@ def create_stock_dividend(sid, payment_sid, ratio, declared_date,
'ratio': ratio,
'net_amount': None,
'gross_amount': None,
'dt': pd.tslib.normalize_date(declared_date),
'ex_date': pd.tslib.normalize_date(ex_date),
'pay_date': pd.tslib.normalize_date(pay_date),
'dt': declared_date.normalize(),
'ex_date': ex_date.normalize(),
'pay_date': pay_date.normalize(),
'type': DATASOURCE_TYPE.DIVIDEND,
'source_id': 'MockDividendSource'
})
+214 -274
View File
@@ -8,13 +8,15 @@ from time import sleep
import click
import pandas as pd
from logbook import Logger
from six import string_types
import catalyst
from catalyst.data.bundles import load
from catalyst.data.data_portal import DataPortal
from catalyst.exchange.exchange_pricing_loader import ExchangePricingLoader, \
TradingPairPricing
from catalyst.exchange.utils.factory import get_exchange
from logbook import Logger
try:
from pygments import highlight
@@ -22,7 +24,7 @@ try:
from pygments.formatters import TerminalFormatter
PYGMENTS = True
except:
except ImportError:
PYGMENTS = False
from toolz import valfilter, concatv
from functools import partial
@@ -40,9 +42,6 @@ from catalyst.exchange.exchange_algorithm import (
from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \
DataPortalExchangeBacktest
from catalyst.exchange.exchange_asset_finder import ExchangeAssetFinder
from catalyst.exchange.exchange_errors import (
ExchangeRequestError, ExchangeRequestErrorTooManyAttempts,
BaseCurrencyNotFoundError, NotEnoughCapitalError)
from catalyst.constants import LOG_LEVEL
@@ -57,6 +56,7 @@ class _RunAlgoError(click.ClickException, ValueError):
----------
pyfunc_msg : str
The message that will be shown when called as a python function.
cmdline_msg : str
The message that will be shown on the command line.
"""
@@ -70,7 +70,38 @@ class _RunAlgoError(click.ClickException, ValueError):
return self.pyfunc_msg
def _build_namespace(algotext, local_namespace, defines):
def _run(handle_data,
initialize,
before_trading_start,
analyze,
algofile,
algotext,
defines,
data_frequency,
capital_base,
data,
bundle,
bundle_timestamp,
start,
end,
output,
print_algo,
local_namespace,
environ,
live,
exchange,
algo_namespace,
base_currency,
live_graph,
analyze_live,
simulate_orders,
auth_aliases,
stats_output):
"""Run a backtest for the given algorithm.
This is shared between the cli and :func:`catalyst.run_algo`.
"""
# TODO: refactor for more granularity
if algotext is not None:
if local_namespace:
ip = get_ipython() # noqa
@@ -84,197 +115,146 @@ def _build_namespace(algotext, local_namespace, defines):
except ValueError:
raise ValueError(
'invalid define %r, should be of the form name=value' %
assign)
assign,
)
try:
# evaluate in the same namespace so names may refer to
# eachother
namespace[name] = eval(value, namespace)
except Exception as e:
raise ValueError(
'failed to execute definition for name %r: %s' % (name, e))
'failed to execute definition for name %r: %s' % (name, e),
)
elif defines:
raise _RunAlgoError(
'cannot pass define without `algotext`',
"cannot pass '-D' / '--define' without '-t' / '--algotext'")
"cannot pass '-D' / '--define' without '-t' / '--algotext'",
)
else:
namespace = {}
if algofile is not None:
algotext = algofile.read()
return namespace
if print_algo:
if PYGMENTS:
highlight(
algotext,
PythonLexer(),
TerminalFormatter(),
outfile=sys.stdout,
)
else:
click.echo(algotext)
log.warn(
'Catalyst is currently in ALPHA. It is going through rapid '
'development and it is subject to errors. Please use carefully. '
'We encourage you to report any issue on GitHub: '
'https://github.com/enigmampc/catalyst/issues'
)
log.info('Catalyst version {}'.format(catalyst.__version__))
sleep(3)
def _mode(simulate_orders, live):
if not live:
return 'backtest'
elif simulate_orders:
return 'paper-trading'
if live:
if simulate_orders:
mode = 'paper-trading'
else:
mode = 'live-trading'
else:
return 'live-trading'
mode = 'backtest'
log.info('running algo in {mode} mode'.format(mode=mode))
def _build_exchanges_dict(exchange, live, simulate_orders, base_currency):
exchange_name = exchange
if exchange_name is None:
raise ValueError('Please specify at least one exchange.')
if isinstance(auth_aliases, string_types):
aliases = auth_aliases.split(',')
if len(aliases) < 2 or len(aliases) % 2 != 0:
raise ValueError(
'the `auth_aliases` parameter must contain an even list '
'of comma-delimited values. For example, '
'"binance,auth2" or "binance,auth2,bittrex,auth2".'
)
auth_aliases = dict(zip(aliases[::2], aliases[1::2]))
exchange_list = [x.strip().lower() for x in exchange.split(',')]
exchanges = {exchange_name: get_exchange(
exchange_name=exchange_name,
base_currency=base_currency,
must_authenticate=(live and not simulate_orders))
for exchange_name in exchange_list}
return exchanges
def _pretty_print_code(algotext):
if PYGMENTS:
highlight(
algotext,
PythonLexer(),
TerminalFormatter(),
outfile=sys.stdout)
else:
click.echo(algotext)
def _choose_loader(data_frequency, column):
bound_cols = TradingPairPricing.columns
if column in bound_cols:
return ExchangePricingLoader(data_frequency)
raise ValueError(
"No PipelineLoader registered for column %s." % column)
def _get_live_time_range():
start = pd.Timestamp.utcnow()
# TODO: fix the end data.
end = start + timedelta(hours=8760)
return start, end
def _data_for_live_trading(sim_params, exchanges, env, open_calendar):
data = DataPortalExchangeLive(
exchanges=exchanges,
asset_finder=env.asset_finder,
trading_calendar=open_calendar,
first_trading_day=pd.to_datetime('today', utc=True))
return data
# TODO use proper retry here
def _fetch_capital_base(base_currency, exchange_name, exchange,
attempt_index=0):
"""
Fetch the base currency amount required to bootstrap
the algorithm against the exchange.
The algorithm cannot continue without this value.
:param exchange: the targeted exchange
:param attempt_index:
:return capital_base: the amount of base currency available for
trading
"""
try:
log.debug('retrieving capital base in {} to bootstrap '
'exchange {}'.format(base_currency, exchange_name))
balances = exchange.get_balances()
except ExchangeRequestError as e:
if attempt_index < 20:
log.warn(
'could not retrieve balances on {}: {}'.format(
exchange.name, e))
sleep(5)
return _fetch_capital_base(base_currency, exchange_name, exchange,
attempt_index + 1)
exchanges = dict()
for name in exchange_list:
if auth_aliases is not None and name in auth_aliases:
auth_alias = auth_aliases[name]
else:
raise ExchangeRequestErrorTooManyAttempts(
attempts=attempt_index,
error=e)
auth_alias = None
if base_currency in balances:
base_currency_available = balances[base_currency]['free']
log.info(
'base currency available in the account: {} {}'.format(
base_currency_available, base_currency))
return base_currency_available
else:
raise BaseCurrencyNotFoundError(
exchanges[name] = get_exchange(
exchange_name=name,
base_currency=base_currency,
exchange=exchange_name)
must_authenticate=(live and not simulate_orders),
skip_init=True,
auth_alias=auth_alias,
)
open_calendar = get_calendar('OPEN')
def _algorithm_class_for_live(algo_namespace, live_graph, stats_output,
analyze_live, base_currency, simulate_orders,
exchanges, capital_base):
if not simulate_orders:
for exchange_name in exchanges:
exchange = exchanges[exchange_name]
balance = _fetch_capital_base(base_currency, exchange_name,
exchange)
env = TradingEnvironment(
load=partial(
load_crypto_market_data,
environ=environ,
start_dt=start,
end_dt=end
),
environ=environ,
exchange_tz='UTC',
asset_db_path=None # We don't need an asset db, we have exchanges
)
env.asset_finder = ExchangeAssetFinder(exchanges=exchanges)
if balance < capital_base:
raise NotEnoughCapitalError(
exchange=exchange_name,
base_currency=base_currency,
balance=balance,
capital_base=capital_base)
algorithm_class = partial(
ExchangeTradingAlgorithmLive,
exchanges=exchanges,
algo_namespace=algo_namespace,
live_graph=live_graph,
simulate_orders=simulate_orders,
stats_output=stats_output,
analyze_live=analyze_live,)
return algorithm_class
def _bundle_trading_environment(bundle_data, environ):
prefix, connstr = re.split(
r'sqlite:///',
str(bundle_data.asset_finder.engine.url),
maxsplit=1)
if prefix:
def choose_loader(column):
bound_cols = TradingPairPricing.columns
if column in bound_cols:
return ExchangePricingLoader(data_frequency)
raise ValueError(
"invalid url %r, must begin with 'sqlite:///'" %
str(bundle_data.asset_finder.engine.url))
"No PipelineLoader registered for column %s." % column
)
return TradingEnvironment(asset_db_path=connstr, environ=environ)
if live:
start = pd.Timestamp.utcnow()
# TODO: fix the end data.
if end is None:
end = start + timedelta(hours=8760)
def _build_live_algo_and_data(sim_params, exchanges, env, open_calendar,
simulate_orders, algo_namespace, capital_base,
live_graph, stats_output, analyze_live,
base_currency, namespace, choose_loader,
algorithm_class_kwargs):
sim_params._arena = 'live' # TODO: use the constructor instead
data = DataPortalExchangeLive(
exchanges=exchanges,
asset_finder=env.asset_finder,
trading_calendar=open_calendar,
first_trading_day=pd.to_datetime('today', utc=True)
)
data = _data_for_live_trading(sim_params, exchanges, env, open_calendar)
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
emission_rate='minute',
data_frequency='minute'
)
algorithm_class = _algorithm_class_for_live(
algo_namespace, live_graph, stats_output, analyze_live,
base_currency, simulate_orders, exchanges, capital_base)
# TODO: use the constructor instead
sim_params._arena = 'live'
return data, algorithm_class(
namespace=namespace,
env=env,
get_pipeline_loader=choose_loader,
sim_params=sim_params,
**algorithm_class_kwargs)
def _build_backtest_algo_and_data(
exchanges, bundle, env, environ, bundle_timestamp, open_calendar,
start, end, namespace, choose_loader, sim_params,
algorithm_class_kwargs):
if exchanges:
algorithm_class = partial(
ExchangeTradingAlgorithmLive,
exchanges=exchanges,
algo_namespace=algo_namespace,
live_graph=live_graph,
simulate_orders=simulate_orders,
stats_output=stats_output,
analyze_live=analyze_live,
end=end,
)
elif exchanges:
# Removed the existing Poloniex fork to keep things simple
# We can add back the complexity if required.
@@ -283,24 +263,55 @@ def _build_backtest_algo_and_data(
# We still need to support bundles for other misc data, but we
# can handle this later.
if start != pd.Timestamp(start).normalize() or \
end != pd.Timestamp(end).normalize():
# todo: add to Sim_Params the option to start & end at specific times
log.warn(
"Catalyst currently starts and ends on the start and "
"end of the dates specified, respectively. We hope to "
"Modify this and support specific times in a future release."
)
data = DataPortalExchangeBacktest(
exchange_names=[exchange_name for exchange_name in exchanges],
asset_finder=None,
trading_calendar=open_calendar,
first_trading_day=start,
last_available_session=end)
last_available_session=end
)
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
data_frequency=data_frequency,
emission_rate=data_frequency,
)
algorithm_class = partial(
ExchangeTradingAlgorithmBacktest,
exchanges=exchanges)
exchanges=exchanges
)
elif bundle is not None:
# TODO This branch should probably be removed or fixed: it doesn't even
# build `algorithm_class`, so it will break when trying to instantiate
# it.
bundle_data = load(bundle, environ, bundle_timestamp)
bundle_data = load(
bundle,
environ,
bundle_timestamp,
)
env = _bundle_trading_environment(bundle_data, environ)
prefix, connstr = re.split(
r'sqlite:///',
str(bundle_data.asset_finder.engine.url),
maxsplit=1,
)
if prefix:
raise ValueError(
"invalid url %r, must begin with 'sqlite:///'" %
str(bundle_data.asset_finder.engine.url),
)
env = TradingEnvironment(asset_db_path=connstr, environ=environ)
first_trading_day = \
bundle_data.equity_minute_bar_reader.first_trading_day
@@ -309,103 +320,27 @@ def _build_backtest_algo_and_data(
first_trading_day=first_trading_day,
equity_minute_reader=bundle_data.equity_minute_bar_reader,
equity_daily_reader=bundle_data.equity_daily_bar_reader,
adjustment_reader=bundle_data.adjustment_reader)
adjustment_reader=bundle_data.adjustment_reader,
)
return data, algorithm_class(
perf = algorithm_class(
namespace=namespace,
env=env,
get_pipeline_loader=choose_loader,
sim_params=sim_params,
**algorithm_class_kwargs)
def _build_algo_and_data(handle_data, initialize, before_trading_start,
analyze, algofile, algotext, defines, data_frequency,
capital_base, data, bundle, bundle_timestamp, start,
end, output, print_algo, local_namespace, environ,
live, exchange, algo_namespace, base_currency,
live_graph, analyze_live, simulate_orders,
stats_output):
namespace = _build_namespace(algotext, local_namespace, defines)
if algotext is not None:
algotext = algofile.read()
if print_algo:
_pretty_print_code(algotext)
mode = _mode(simulate_orders, live)
log.info('running algo in {mode} mode'.format(mode=mode))
exchanges = _build_exchanges_dict(exchange, live, simulate_orders,
base_currency)
open_calendar = get_calendar('OPEN')
env = TradingEnvironment(
load=partial(load_crypto_market_data, environ=environ, start_dt=start,
end_dt=end),
environ=environ,
exchange_tz='UTC',
asset_db_path=None) # We don't need an asset db, we have exchanges
env.asset_finder = ExchangeAssetFinder(exchanges=exchanges)
choose_loader = partial(_choose_loader, data_frequency)
if live:
start, end = _get_live_time_range()
data_frequency = 'minute' # TODO double check if this is the desired behavior
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
emission_rate=data_frequency,
data_frequency=data_frequency)
if algotext is None:
algorithm_class_kwargs = {'initialize': initialize,
'handle_data': handle_data,
'before_trading_start': before_trading_start,
'analyze': analyze}
else:
algorithm_class_kwargs = {'algo_filename': getattr(algofile, 'name',
'<algorithm>'),
'script': algotext}
if live:
return _build_live_algo_and_data(
sim_params, exchanges, env, open_calendar, simulate_orders,
algo_namespace, capital_base, live_graph, stats_output,
analyze_live, base_currency, namespace, choose_loader,
algorithm_class_kwargs)
else:
return _build_backtest_algo_and_data(
exchanges, bundle, env, environ, bundle_timestamp, open_calendar,
start, end, namespace, choose_loader, sim_params,
algorithm_class_kwargs)
def _run(handle_data, initialize, before_trading_start, analyze, algofile,
algotext, defines, data_frequency, capital_base, data, bundle,
bundle_timestamp, start, end, output, print_algo, local_namespace,
environ, live, exchange, algo_namespace, base_currency, live_graph,
analyze_live, simulate_orders, stats_output):
"""Run an algorithm in backtest,
paper-trading or live-trading mode.
This is shared between the cli and :func:`catalyst.run_algo`.
"""
data, algorithm = _build_algo_and_data(
handle_data, initialize, before_trading_start, analyze, algofile,
algotext, defines, data_frequency, capital_base, data, bundle,
bundle_timestamp, start, end, output, print_algo, local_namespace,
environ, live, exchange, algo_namespace, base_currency, live_graph,
analyze_live, simulate_orders, stats_output)
perf = algorithm.run(
**{
'initialize': initialize,
'handle_data': handle_data,
'before_trading_start': before_trading_start,
'analyze': analyze,
} if algotext is None else {
'algo_filename': getattr(algofile, 'name', '<algorithm>'),
'script': algotext,
}
).run(
data,
overwrite_sim_params=False)
overwrite_sim_params=False,
)
if output == '-':
click.echo(str(perf))
@@ -462,7 +397,8 @@ def load_extensions(default, extensions, strict, environ, reload=False):
# without `strict` we should just log the failure
warnings.warn(
'Failed to load extension: %r\n%s' % (ext, e),
stacklevel=2)
stacklevel=2
)
else:
_loaded_extensions.add(ext)
@@ -489,9 +425,11 @@ def run_algorithm(initialize,
live_graph=False,
analyze_live=None,
simulate_orders=True,
auth_aliases=None,
stats_output=None,
output=os.devnull):
"""Run a trading algorithm.
"""
Run a trading algorithm.
Parameters
----------
@@ -533,7 +471,7 @@ def run_algorithm(initialize,
This argument is mutually exclusive with ``data``.
default_extension : bool, optional
Should the default catalyst extension be loaded. This is found at
``$ZIPLINE_ROOT/extension.py``
``$CATALYST_ROOT/extension.py``
extensions : iterable[str], optional
The names of any other extensions to load. Each element may either be
a dotted module path like ``a.b.c`` or a path to a python file ending
@@ -544,12 +482,8 @@ def run_algorithm(initialize,
environ : mapping[str -> str], optional
The os environment to use. Many extensions use this to get parameters.
This defaults to ``os.environ``.
live: execute live trading
exchange_conn: The exchange connection parameters
Supported Exchanges
-------------------
bitfinex
live : bool, optional
Execute algorithm in live trading mode.
Returns
-------
@@ -561,7 +495,8 @@ def run_algorithm(initialize,
catalyst.data.bundles.bundles : The available data bundles.
"""
load_extensions(
default_extension, extensions, strict_extensions, environ)
default_extension, extensions, strict_extensions, environ
)
if capital_base is None:
raise ValueError(
@@ -569,7 +504,8 @@ def run_algorithm(initialize,
'amount of base currency available for trading. For example, '
'if the `capital_base` is 5ETH, the '
'`order_target_percent(asset, 1)` command will order 5ETH worth '
'of the specified asset.')
'of the specified asset.'
)
# I'm not sure that we need this since the modified DataPortal
# does not require extensions to be explicitly loaded.
@@ -587,11 +523,13 @@ def run_algorithm(initialize,
elif len(non_none_data) != 1:
raise ValueError(
'must specify one of `data`, `data_portal`, or `bundle`,'
' got: %r' % non_none_data)
' got: %r' % non_none_data,
)
elif 'bundle' not in non_none_data and bundle_timestamp is not None:
raise ValueError(
'cannot specify `bundle_timestamp` without passing `bundle`')
'cannot specify `bundle_timestamp` without passing `bundle`',
)
return _run(
handle_data=handle_data,
initialize=initialize,
@@ -618,4 +556,6 @@ def run_algorithm(initialize,
live_graph=live_graph,
analyze_live=analyze_live,
simulate_orders=simulate_orders,
stats_output=stats_output)
auth_aliases=auth_aliases,
stats_output=stats_output
)
+5 -5
View File
@@ -23,7 +23,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " build to build the C and Cython extensions for zipline"
@echo " build to build the C and Cython extensions for catalyst"
@echo " html to make standalone HTML files"
@echo " livehtml to run a persistent process that rebuilds the docs"
@echo " dirhtml to make HTML files named index.html in directories"
@@ -96,9 +96,9 @@ qthelp: build
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/zipline.qhcp"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/catalyst.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zipline.qhc"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/catalyst.qhc"
applehelp: build
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@@ -113,8 +113,8 @@ devhelp: build
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/zipline"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/zipline"
@echo "# mkdir -p $$HOME/.local/share/devhelp/catalyst"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/catalyst"
@echo "# devhelp"
epub: build
+5 -5
View File
@@ -8,8 +8,8 @@ from shutil import move, rmtree
from subprocess import check_call
HERE = dirname(abspath(__file__))
ZIPLINE_ROOT = dirname(HERE)
TEMP_LOCATION = '/tmp/zipline-doc'
CATALYST_ROOT = dirname(HERE)
TEMP_LOCATION = '/tmp/catalyst-doc'
TEMP_LOCATION_GLOB = TEMP_LOCATION + '/*'
@@ -46,8 +46,8 @@ def main():
print("Copying built files to temp location.")
move('build/html', TEMP_LOCATION)
print("Moving to '%s'" % ZIPLINE_ROOT)
os.chdir(ZIPLINE_ROOT)
print("Moving to '%s'" % CATALYST_ROOT)
os.chdir(CATALYST_ROOT)
print("Checking out gh-pages branch.")
check_call(
@@ -70,7 +70,7 @@ def main():
os.chdir(old_dir)
print()
print("Updated documentation branch in directory %s" % ZIPLINE_ROOT)
print("Updated documentation branch in directory %s" % CATALYST_ROOT)
print("If you are happy with these changes, commit and push to gh-pages.")
if __name__ == '__main__':
+2 -2
View File
@@ -127,9 +127,9 @@ if "%1" == "qthelp" (
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\zipline.qhcp
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\catalyst.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\zipline.ghc
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\catalyst.ghc
goto end
)
+173 -179
View File
@@ -4,7 +4,7 @@ API Reference
Running a Backtest
~~~~~~~~~~~~~~~~~~
.. autofunction:: zipline.run_algorithm(...)
.. autofunction:: catalyst.run_algorithm(...)
Algorithm API
~~~~~~~~~~~~~
@@ -18,341 +18,335 @@ currently-executing :class:`~zipline.algorithm.TradingAlgorithm` instance.
Data Object
```````````
.. autoclass:: zipline.protocol.BarData
.. autoclass:: catalyst.protocol.BarData
:members:
Scheduling Functions
````````````````````
.. autofunction:: zipline.api.schedule_function
.. autofunction:: catalyst.api.schedule_function
.. autoclass:: zipline.api.date_rules
.. autoclass:: catalyst.api.date_rules
:members:
:undoc-members:
.. autoclass:: zipline.api.time_rules
.. autoclass:: catalyst.api.time_rules
:members:
Orders
``````
.. autofunction:: zipline.api.order
.. autofunction:: catalyst.api.order
.. autofunction:: zipline.api.order_value
.. autofunction:: catalyst.api.order_value
.. autofunction:: zipline.api.order_percent
.. autofunction:: catalyst.api.order_percent
.. autofunction:: zipline.api.order_target
.. autofunction:: catalyst.api.order_target
.. autofunction:: zipline.api.order_target_value
.. autofunction:: catalyst.api.order_target_value
.. autofunction:: zipline.api.order_target_percent
.. autofunction:: catalyst.api.order_target_percent
.. autoclass:: zipline.finance.execution.ExecutionStyle
.. autoclass:: catalyst.finance.execution.ExecutionStyle
:members:
.. autoclass:: zipline.finance.execution.MarketOrder
.. autoclass:: catalyst.finance.execution.MarketOrder
.. autoclass:: zipline.finance.execution.LimitOrder
.. autoclass:: catalyst.finance.execution.LimitOrder
.. autoclass:: zipline.finance.execution.StopOrder
.. autoclass:: catalyst.finance.execution.StopOrder
.. autoclass:: zipline.finance.execution.StopLimitOrder
.. autoclass:: catalyst.finance.execution.StopLimitOrder
.. autofunction:: zipline.api.get_order
.. autofunction:: catalyst.api.get_order
.. autofunction:: zipline.api.get_open_orders
.. autofunction:: catalyst.api.get_open_orders
.. autofunction:: zipline.api.cancel_order
.. autofunction:: catalyst.api.cancel_order
Order Cancellation Policies
'''''''''''''''''''''''''''
.. autofunction:: zipline.api.set_cancel_policy
.. autofunction:: catalyst.api.set_cancel_policy
.. autoclass:: zipline.finance.cancel_policy.CancelPolicy
.. autoclass:: catalyst.finance.cancel_policy.CancelPolicy
:members:
.. autofunction:: zipline.api.EODCancel
.. autofunction:: catalyst.api.EODCancel
.. autofunction:: zipline.api.NeverCancel
.. autofunction:: catalyst.api.NeverCancel
Assets
``````
.. autofunction:: zipline.api.symbol
.. autofunction:: catalyst.api.symbol
.. autofunction:: zipline.api.symbols
.. autofunction:: catalyst.api.symbols
.. autofunction:: zipline.api.future_symbol
.. autofunction:: catalyst.api.set_symbol_lookup_date
.. autofunction:: zipline.api.set_symbol_lookup_date
.. autofunction:: zipline.api.sid
.. autofunction:: catalyst.api.sid
Trading Controls
````````````````
Zipline provides trading controls to help ensure that the algorithm is
zipline provides trading controls to help ensure that the algorithm is
performing as expected. The functions help protect the algorithm from certian
bugs that could cause undesirable behavior when trading with real money.
.. autofunction:: zipline.api.set_do_not_order_list
.. autofunction:: catalyst.api.set_do_not_order_list
.. autofunction:: zipline.api.set_long_only
.. autofunction:: catalyst.api.set_long_only
.. autofunction:: zipline.api.set_max_leverage
.. autofunction:: catalyst.api.set_max_leverage
.. autofunction:: zipline.api.set_max_order_count
.. autofunction:: catalyst.api.set_max_order_count
.. autofunction:: zipline.api.set_max_order_size
.. autofunction:: catalyst.api.set_max_order_size
.. autofunction:: zipline.api.set_max_position_size
.. autofunction:: catalyst.api.set_max_position_size
Simulation Parameters
`````````````````````
.. autofunction:: zipline.api.set_benchmark
.. autofunction:: catalyst.api.set_benchmark
Commission Models
'''''''''''''''''
.. autofunction:: zipline.api.set_commission
.. autofunction:: catalyst.api.set_commission
.. autoclass:: zipline.finance.commission.CommissionModel
.. autoclass:: catalyst.finance.commission.CommissionModel
:members:
.. autoclass:: zipline.finance.commission.PerShare
.. autoclass:: catalyst.finance.commission.PerShare
.. autoclass:: zipline.finance.commission.PerTrade
.. autoclass:: catalyst.finance.commission.PerTrade
.. autoclass:: zipline.finance.commission.PerDollar
.. autoclass:: catalyst.finance.commission.PerDollar
Slippage Models
'''''''''''''''
.. autofunction:: zipline.api.set_slippage
.. autofunction:: catalyst.api.set_slippage
.. autoclass:: zipline.finance.slippage.SlippageModel
.. autoclass:: catalyst.finance.slippage.SlippageModel
:members:
.. autoclass:: zipline.finance.slippage.FixedSlippage
.. autoclass:: catalyst.finance.slippage.FixedSlippage
.. autoclass:: zipline.finance.slippage.VolumeShareSlippage
.. autoclass:: catalyst.finance.slippage.VolumeShareSlippage
Pipeline
````````
For more information, see :ref:`pipeline-api`
Not supported yet.
.. autofunction:: zipline.api.attach_pipeline
.. For more information, see :ref:`pipeline-api`
.. autofunction:: zipline.api.pipeline_output
.. .. autofunction:: catalyst.api.attach_pipeline
.. .. autofunction:: catalyst.api.pipeline_output
Miscellaneous
`````````````
.. autofunction:: zipline.api.record
.. autofunction:: catalyst.api.record
.. autofunction:: zipline.api.get_environment
.. autofunction:: catalyst.api.get_environment
.. autofunction:: zipline.api.fetch_csv
.. autofunction:: catalyst.api.fetch_csv
.. _pipeline-api:
Pipeline API
~~~~~~~~~~~~
.. Pipeline API
.. ~~~~~~~~~~~~
.. autoclass:: zipline.pipeline.Pipeline
:members:
:member-order: groupwise
.. .. autoclass:: zipline.pipeline.Pipeline
.. :members:
.. :member-order: groupwise
.. autoclass:: zipline.pipeline.CustomFactor
:members:
:member-order: groupwise
.. .. autoclass:: zipline.pipeline.CustomFactor
.. :members:
.. :member-order: groupwise
.. autoclass:: zipline.pipeline.filters.Filter
:members: __and__, __or__
:exclude-members: dtype
.. .. autoclass:: zipline.pipeline.filters.Filter
.. :members: __and__, __or__
.. :exclude-members: dtype
.. autoclass:: zipline.pipeline.factors.Factor
:members: bottom, deciles, demean, linear_regression, pearsonr,
percentile_between, quantiles, quartiles, quintiles, rank,
spearmanr, top, winsorize, zscore, isnan, notnan, isfinite, eq,
__add__, __sub__, __mul__, __div__, __mod__, __pow__, __lt__,
__le__, __ne__, __ge__, __gt__
:exclude-members: dtype
:member-order: bysource
.. .. autoclass:: zipline.pipeline.factors.Factor
.. :members: bottom, deciles, demean, linear_regression, pearsonr,
.. percentile_between, quantiles, quartiles, quintiles, rank,
.. spearmanr, top, winsorize, zscore, isnan, notnan, isfinite, eq,
.. \__add__, \__sub__, \__mul__, \__div__, \__mod__, \__pow__,
.. \__lt__, \__le__, \__ne__, \__ge__, \__gt__
.. :exclude-members: dtype
.. :member-order: bysource
.. autoclass:: zipline.pipeline.term.Term
:members:
:exclude-members: compute_extra_rows, dependencies, inputs, mask, windowed
.. .. autoclass:: zipline.pipeline.term.Term
.. :members:
.. :exclude-members: compute_extra_rows, dependencies, inputs, mask, windowed
.. autoclass:: zipline.pipeline.data.USEquityPricing
:members: open, high, low, close, volume
:undoc-members:
.. .. autoclass:: zipline.pipeline.data.USEquityPricing
.. :members: open, high, low, close, volume
.. :undoc-members:
Built-in Factors
````````````````
.. Built-in Factors
.. ````````````````
.. autoclass:: zipline.pipeline.factors.AverageDollarVolume
:members:
.. .. autoclass:: zipline.pipeline.factors.AverageDollarVolume
.. :members:
.. autoclass:: zipline.pipeline.factors.BollingerBands
:members:
.. .. autoclass:: zipline.pipeline.factors.BollingerBands
.. :members:
.. autoclass:: zipline.pipeline.factors.BusinessDaysSincePreviousEvent
:members:
.. .. autoclass:: zipline.pipeline.factors.BusinessDaysSincePreviousEvent
.. :members:
.. autoclass:: zipline.pipeline.factors.BusinessDaysUntilNextEvent
:members:
.. .. autoclass:: zipline.pipeline.factors.BusinessDaysUntilNextEvent
.. :members:
.. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingAverage
:members:
.. .. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingAverage
.. :members:
.. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingStdDev
:members:
.. .. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingStdDev
.. :members:
.. autoclass:: zipline.pipeline.factors.Latest
:members:
.. .. autoclass:: zipline.pipeline.factors.Latest
.. :members:
.. autoclass:: zipline.pipeline.factors.MaxDrawdown
:members:
.. .. autoclass:: zipline.pipeline.factors.MaxDrawdown
.. :members:
.. autoclass:: zipline.pipeline.factors.Returns
:members:
.. .. autoclass:: zipline.pipeline.factors.Returns
.. :members:
.. autoclass:: zipline.pipeline.factors.RollingLinearRegressionOfReturns
:members:
.. .. autoclass:: zipline.pipeline.factors.RollingLinearRegressionOfReturns
.. :members:
.. autoclass:: zipline.pipeline.factors.RollingPearsonOfReturns
:members:
.. .. autoclass:: zipline.pipeline.factors.RollingPearsonOfReturns
.. :members:
.. autoclass:: zipline.pipeline.factors.RollingSpearmanOfReturns
:members:
.. .. autoclass:: zipline.pipeline.factors.RollingSpearmanOfReturns
.. :members:
.. autoclass:: zipline.pipeline.factors.RSI
:members:
.. .. autoclass:: zipline.pipeline.factors.RSI
.. :members:
.. autoclass:: zipline.pipeline.factors.SimpleMovingAverage
:members:
.. .. autoclass:: zipline.pipeline.factors.SimpleMovingAverage
.. :members:
.. autoclass:: zipline.pipeline.factors.VWAP
:members:
.. .. autoclass:: zipline.pipeline.factors.VWAP
.. :members:
.. autoclass:: zipline.pipeline.factors.WeightedAverageValue
:members:
.. .. autoclass:: zipline.pipeline.factors.WeightedAverageValue
.. :members:
Pipeline Engine
```````````````
.. Pipeline Engine
.. ```````````````
.. autoclass:: zipline.pipeline.engine.PipelineEngine
:members: run_pipeline, run_chunked_pipeline
:member-order: bysource
.. .. autoclass:: zipline.pipeline.engine.PipelineEngine
.. :members: run_pipeline, run_chunked_pipeline
.. :member-order: bysource
.. autoclass:: zipline.pipeline.engine.SimplePipelineEngine
:members: __init__, run_pipeline, run_chunked_pipeline
:member-order: bysource
.. .. autoclass:: zipline.pipeline.engine.SimplePipelineEngine
.. :members: __init__, run_pipeline, run_chunked_pipeline
.. :member-order: bysource
.. autofunction:: zipline.pipeline.engine.default_populate_initial_workspace
.. .. autofunction:: zipline.pipeline.engine.default_populate_initial_workspace
Data Loaders
````````````
.. Data Loaders
.. ````````````
.. autoclass:: zipline.pipeline.loaders.equity_pricing_loader.USEquityPricingLoader
:members: __init__, from_files, load_adjusted_array
:member-order: bysource
.. .. autoclass:: zipline.pipeline.loaders.equity_pricing_loader.USEquityPricingLoader
.. :members: __init__, from_files, load_adjusted_array
.. :member-order: bysource
Asset Metadata
~~~~~~~~~~~~~~
.. autoclass:: zipline.assets.Asset
.. autoclass:: catalyst.assets.Asset
:members:
.. autoclass:: zipline.assets.Equity
:members:
.. autoclass:: zipline.assets.Future
:members:
.. autoclass:: zipline.assets.AssetConvertible
.. autoclass:: catalyst.assets.AssetConvertible
:members:
Trading Calendar API
~~~~~~~~~~~~~~~~~~~~
.. autofunction:: zipline.utils.calendars.get_calendar
.. autofunction:: catalyst.utils.calendars.get_calendar
.. autoclass:: zipline.utils.calendars.TradingCalendar
.. autoclass:: catalyst.utils.calendars.TradingCalendar
:members:
.. autofunction:: zipline.utils.calendars.register_calendar
.. autofunction:: catalyst.utils.calendars.register_calendar
.. autofunction:: zipline.utils.calendars.register_calendar_type
.. autofunction:: catalyst.utils.calendars.register_calendar_type
.. autofunction:: zipline.utils.calendars.deregister_calendar
.. autofunction:: catalyst.utils.calendars.deregister_calendar
.. autofunction:: zipline.utils.calendars.clear_calendars
.. autofunction:: catalyst.utils.calendars.clear_calendars
Data API
~~~~~~~~
Writers
```````
.. autoclass:: zipline.data.minute_bars.BcolzMinuteBarWriter
:members:
.. Writers
.. ```````
.. .. autoclass:: zipline.data.minute_bars.BcolzMinuteBarWriter
.. :members:
.. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarWriter
:members:
.. .. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarWriter
.. :members:
.. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentWriter
:members:
.. .. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentWriter
.. :members:
.. autoclass:: zipline.assets.AssetDBWriter
:members:
.. .. autoclass:: zipline.assets.AssetDBWriter
.. :members:
Readers
```````
.. autoclass:: zipline.data.minute_bars.BcolzMinuteBarReader
:members:
.. Readers
.. ```````
.. .. autoclass:: zipline.data.minute_bars.BcolzMinuteBarReader
.. :members:
.. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarReader
:members:
.. .. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarReader
.. :members:
.. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentReader
:members:
.. .. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentReader
.. :members:
.. autoclass:: zipline.assets.AssetFinder
:members:
.. .. autoclass:: zipline.assets.AssetFinder
.. :members:
.. autoclass:: zipline.data.data_portal.DataPortal
:members:
.. .. autoclass:: zipline.data.data_portal.DataPortal
.. :members:
Bundles
```````
.. autofunction:: zipline.data.bundles.register
.. Bundles
.. ```````
.. .. autofunction:: zipline.data.bundles.register
.. autofunction:: zipline.data.bundles.ingest(name, environ=os.environ, date=None, show_progress=True)
.. .. autofunction:: zipline.data.bundles.ingest(name, environ=os.environ, date=None, show_progress=True)
.. autofunction:: zipline.data.bundles.load(name, environ=os.environ, date=None)
.. .. autofunction:: zipline.data.bundles.load(name, environ=os.environ, date=None)
.. autofunction:: zipline.data.bundles.unregister
.. .. autofunction:: zipline.data.bundles.unregister
.. data:: zipline.data.bundles.bundles
.. .. data:: zipline.data.bundles.bundles
The bundles that have been registered as a mapping from bundle name to bundle
data. This mapping is immutable and should only be updated through
:func:`~zipline.data.bundles.register` or
:func:`~zipline.data.bundles.unregister`.
.. The bundles that have been registered as a mapping from bundle name to bundle
.. data. This mapping is immutable and should only be updated through
.. :func:`~zipline.data.bundles.register` or
.. :func:`~zipline.data.bundles.unregister`.
.. autofunction:: zipline.data.bundles.yahoo_equities
.. .. autofunction:: zipline.data.bundles.yahoo_equities
@@ -362,16 +356,16 @@ Utilities
Caching
```````
.. autoclass:: zipline.utils.cache.CachedObject
.. autoclass:: catalyst.utils.cache.CachedObject
.. autoclass:: zipline.utils.cache.ExpiringCache
.. autoclass:: catalyst.utils.cache.ExpiringCache
.. autoclass:: zipline.utils.cache.dataframe_cache
.. autoclass:: catalyst.utils.cache.dataframe_cache
.. autoclass:: zipline.utils.cache.working_file
.. autoclass:: catalyst.utils.cache.working_file
.. autoclass:: zipline.utils.cache.working_dir
.. autoclass:: catalyst.utils.cache.working_dir
Command Line
````````````
.. autofunction:: zipline.utils.cli.maybe_show_progress
.. autofunction:: catalyst.utils.cli.maybe_show_progress
+52 -161
View File
@@ -168,7 +168,7 @@ We'll start with the CLI, and introduce the ``run_algorithm()`` in the last
example of this tutorial. Some of the :doc:`example algorithms <example-algos>`
provide instructions on how to run them both from the CLI, and using the
:func:`~catalyst.run_algorithm` function. For the third method, refer to the
corresponding section on :doc:`Catalyst & Jupyter Notebook <jupyter>` after you
corresponding section on :ref:`Catalyst & Jupyter Notebook <jupyter>` after you
have assimilated the contents of this tutorial.
Command line interface
@@ -473,6 +473,7 @@ Which we execute by running:
</div>
|
There is a row for each trading day, starting on the first day of our
simulation Jan 1st, 2016. In the columns you can find various
information about the state of your algorithm. The column
@@ -483,7 +484,7 @@ bitcoin price.
Now we will run the simulation again, but this time we extend our original
algorithm with the addition of the ``analyze()`` function. Somewhat analogously
as how ``initialize()`` gets called once before the start of the algorith,
as how ``initialize()`` gets called once before the start of the algorithm,
``analyze()`` gets called once at the end of the algorithm, and receives two
variables: ``context``, which we discussed at the very beginning, and ``perf``,
which is the pandas dataframe containing the performance data for our algorithm
@@ -518,7 +519,7 @@ alongside enigma-catalyst (with the exception of the ``Conda`` install, where it
was included by default inside the conda environment we created). If for any
reason you don't have it installed, you can add it by running:
.. code-block:: python
.. code-block:: bash
(catalyst)$ pip install matplotlib
@@ -579,161 +580,8 @@ which you can skim through for now. A copy of this algorithm is available in
the ``examples`` directory:
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
.. code-block:: python
import numpy as np
import pandas as pd
from logbook import Logger
import matplotlib.pyplot as plt
from catalyst import run_algorithm
from catalyst.api import (order, record, symbol, order_target_percent,
get_open_orders)
from catalyst.exchange.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
base_currency = context.exchanges.values()[0].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),
)
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
:language: python
In order to run the code above, you have to ingest the needed data first:
@@ -805,6 +653,7 @@ the ``scikit-learn`` functions require ``numpy.ndarray``\ s rather than
``pandas.DataFrame``\ s, so you can simply pass the underlying
``ndarray`` of a ``DataFrame`` via ``.values``).
.. _jupyter:
Jupyter Notebook
~~~~~~~~~~~~~~~~
@@ -825,13 +674,13 @@ In order to use Jupyter Notebook, you first have to install it inside your
environment. It's available as ``pip`` package, so regardless of how you
installed Catalyst, go inside your catalyst environemnt and run:
.. code:: bash
.. code-block:: bash
(catalyst)$ pip install jupyter
Once you have Jupyter Notebook installed, every time you want to use it run:
.. code:: bash
.. code-block:: bash
(catalyst)$ jupyter notebook
@@ -845,7 +694,7 @@ Before running your algorithms inside the Jupyter Notebook, remember to ingest
the data from the command line interface (CLI). In the example below, you would
need to run first:
.. code:: bash
.. code-block:: bash
catalyst ingest-exchange -x bitfinex -i btc_usd
@@ -16606,7 +16455,49 @@ NaN
</div>
PyCharm IDE
~~~~~~~~~~~
PyCharm is an Integrated Development Environment (IDE) used in computer
programming, specifically for the Python language. It streamlines the continuos
development of Python code, and among other things includes a debugger that
comes in handy to see the inner workings of Catalyst, and your trading
algorithms.
Install
^^^^^^^
Install PyCharm from their `Website <https://www.jetbrains.com/pycharm/download/>`__.
There is a free and open-source **Community** version.
Setup
^^^^^
1. When creating a new project in PyCharm, right under you specify the Location,
click on **Project Interpreter** to display a drop down menu
2. Select **Existing interpreter**, click the gear box right next to it and
select 'add local'. Depending on your installation, select either
"*Virtual Environemnt*" or "*Conda Environment" and click the '...' button to
navigate to your catalyst env and select the Python binary file:
``bin/python`` for Linux/MacOS installations or 'python.exe' for Windows
installs (for example: 'C:\\Users\\user\\Anaconda2\\envs\\catalyst\\python.exe').
Select OK. You may want to click on *Make available to all projects* for your
future reference. Click OK again, and create your new environment using the
set up of your virtual environment.
Alternatively, if you already have your project created, in Windows do:
1. File -> Default Settings -> Project Interpreter. Click the gear box next to
the project interpreter and select add local, and follow the steps from the
second step above.
On MacOS:
1. PyCharm -> Preferences -> Settings -> Project:NAME_OF_PROJECT ->
Project Interpreter. Click the gear box next to the project interpreter
and select add local, and follow the steps from the second step above.
You should now be able to run your project/scripts in PyCharm.
Next steps
~~~~~~~~~~
+7 -4
View File
@@ -27,8 +27,8 @@ extlinks = {
# -- Docstrings ---------------------------------------------------------------
#extensions += ['numpydoc']
#numpydoc_show_class_members = False
extensions += ['numpydoc']
numpydoc_show_class_members = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']
@@ -41,11 +41,11 @@ master_doc = 'index'
# General information about the project.
project = u'Catalyst'
copyright = u'2017, Enigma MPC, Inc.'
copyright = u'2018, Enigma MPC, Inc.'
# The full version, including alpha/beta/rc tags, but excluding the commit hash
#release = version.split('+', 1)[0]
release = '0.3'
release = '0.4'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -97,3 +97,6 @@ intersphinx_mapping = {
doctest_global_setup = "import catalyst"
todo_include_todos = True
suppress_warnings = ['image.nonlocal_uri']
+26 -17
View File
@@ -36,25 +36,15 @@ Finally, you can build the C extensions by running:
$ python setup.py build_ext --inplace
.. To finish, make sure `tests`__ pass.
Development with Docker
-----------------------
.. __ #style-guide-running-tests
If you want to work with zipline using a `Docker`__ container, you'll need to
build the ``Dockerfile`` in the Zipline root directory, and then build
``Dockerfile-dev``. Instructions for building both containers can be found in
``Dockerfile`` and ``Dockerfile-dev``, respectively.
.. If you get an error running nosetests after setting up a fresh virtualenv, please try running
.. code-block
.. # where zipline is the name of your virtualenv
.. $ deactivate zipline
.. $ workon zipline
.. Development with Docker
.. -----------------------
..If you want to work with zipline using a `Docker`__ container, you'll need to build the ``Dockerfile`` in the Zipline root directory, and then build ``Dockerfile-dev``. Instructions for building both containers can be found in ``Dockerfile`` and ``Dockerfile-dev``, respectively.
.. __ https://docs.docker.com/get-started/
__ https://docs.docker.com/get-started/
Git Branching Structure
-----------------------
@@ -84,6 +74,25 @@ To build and view the docs locally, run:
$ {BROWSER} build/html/index.html
There is a `documented issue <https://github.com/sphinx-doc/sphinx/issues/3212>`_
with ``sphinx`` and ``docutils`` that causes the error below when trying to build
the docs.
.. code-block:: text
Exception occurred:
File "(...)/env-c/lib/python2.7/site-packages/docutils/writers/_html_base.py", line 671, in depart_document
assert not self.context, 'len(context) = %s' % len(self.context)
AssertionError: len(context) = 3
If you get this error, you need to downgrade your version of ``docutils`` as
follows, and build the docs again:
.. code-block:: bash
$ pip install docutils==0.12
Commit messages
---------------
+18 -881
View File
@@ -1,4 +1,5 @@
|
Example Algorithms
==================
@@ -51,35 +52,8 @@ Buy BTC Simple Algorithm
Source code: `examples/buy_btc_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_btc_simple.py>`_
.. code-block:: python
'''
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.api import order, record, symbol
def initialize(context):
context.asset = symbol('btc_usd')
def handle_data(context, data):
order(context.asset, 1)
record(btc = data.current(context.asset, 'price'))
.. literalinclude:: ../../catalyst/examples/buy_btc_simple.py
:language: python
This simple algorithm does not produce any output nor displays any chart.
@@ -89,8 +63,6 @@ This simple algorithm does not produce any output nor displays any chart.
Buy and Hodl Algorithm
~~~~~~~~~~~~~~~~~~~~~~
Source code: `examples/buy_and_hodl.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_and_hodl.py>`_
First ingest the historical pricing data needed to run this algorithm:
.. code-block:: bash
@@ -118,157 +90,10 @@ that 2015-3-1 is the earliest date that Catalyst supports (if you choose an
earlier date, you'll get an error), and the most recent date you can choose is
one day prior to the current date.
Source code: `examples/buy_and_hodl.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_and_hodl.py>`_
.. code-block:: python
#!/usr/bin/env python
#
# Copyright 2017 Enigma MPC, Inc.
# 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.
import pandas as pd
import matplotlib.pyplot as plt
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 = 'btc_usd'
context.TARGET_HODL_RATIO = 0.8
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
context.is_buying = True
context.asset = symbol(context.ASSET_NAME)
context.i = 0
def handle_data(context, data):
context.i += 1
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.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,
)
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):
# 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))
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.scatter(
buys.index.to_pydatetime(),
results.price[buys.index],
marker='^',
s=100,
c='g',
label=''
)
ax3 = plt.subplot(613, sharex=ax1)
results[['leverage', 'alpha', 'beta']].plot(ax=ax3)
ax3.set_ylabel('Leverage ')
ax4 = plt.subplot(614, sharex=ax1)
results[['starting_cash', '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 (mCoins/5min)')
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='bitfinex',
algo_namespace='buy_and_hodl',
base_currency='usd',
start=pd.to_datetime('2015-03-01', utc=True),
end=pd.to_datetime('2017-10-31', utc=True),
)
.. literalinclude:: ../../catalyst/examples/buy_and_hodl.py
:language: python
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_buy_and_hodl.png
@@ -277,166 +102,13 @@ one day prior to the current date.
Dual Moving Average Crossover
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Source Code: `examples/dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_
This strategy is covered in detail in the last part of
`this tutorial <beginner-tutorial.html#history>`_.
.. code-block:: python
Source Code: `examples/dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_
import numpy as np
import pandas as pd
from logbook import Logger
import matplotlib.pyplot as plt
from catalyst import run_algorithm
from catalyst.api import (order, record, symbol, order_target_percent,
get_open_orders)
from catalyst.exchange.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
base_currency = context.exchanges.values()[0].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),
)
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
:language: python
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/tutorial_dual_moving_average.png
@@ -446,8 +118,6 @@ This strategy is covered in detail in the last part of
Mean Reversion Algorithm
~~~~~~~~~~~~~~~~~~~~~~~~
Source code: `examples/mean_reversion_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/mean_reversion_simple.py>`_
This algorithm is based on a simple momentum strategy. When the cryptoasset goes
up quickly, we're going to buy; when it goes down quickly, we're going to sell.
Hopefully, we'll ride the waves.
@@ -468,284 +138,10 @@ lines 218-245, so in order to run the algorithm we just type:
python mean_reversion_simple.py
.. code-block:: python
Source code: `examples/mean_reversion_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/mean_reversion_simple.py>`_
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.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 USD.
context.neo_eth = symbol('neo_usd')
context.base_price = None
context.current_day = None
context.RSI_OVERSOLD = 30
context.RSI_OVERBOUGHT = 80
context.CANDLE_SIZE = '15T'
context.start_time = time.time()
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.neo_eth 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.neo_eth,
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.neo_eth, 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(
price=price,
volume=current['volume'],
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
# 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.neo_eth)
if len(orders) > 0:
return
# Exit if we cannot trade
if not data.can_trade(context.neo_eth):
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.neo_eth].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.neo_eth, 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.neo_eth, 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 = 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.neo_eth.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
MODE = 'backtest'
if MODE == 'backtest':
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=10000,
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-10-01', utc=True),
end=pd.to_datetime('2017-11-10', utc=True),
output=out
)
log.info('saved perf stats: {}'.format(out))
elif MODE == 'live':
run_algorithm(
capital_base=0.5,
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bittrex',
live=True,
algo_namespace=NAMESPACE,
base_currency='usd',
live_graph=False
)
.. literalinclude:: ../../catalyst/examples/mean_reversion_simple.py
:language: python
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_mean_reversion_simple.png
@@ -762,8 +158,6 @@ strategy.
Simple Universe
~~~~~~~~~~~~~~~
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/simple_universe.py>`_
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
@@ -790,142 +184,10 @@ of the file:
catalyst ingest-exchange -x bitfinex -f minute
.. code-block:: bash
python simple_universe.py
Credits: This code was originally submitted by `Abner Ayala-Acevedo
<https://github.com/abnera>`_. Thank you!
.. code-block:: python
from datetime import timedelta
import numpy as np
import pandas as pd
from catalyst import run_algorithm
from catalyst.exchange.exchange_utils import get_exchange_symbols
from catalyst.api import (symbols, )
def initialize(context):
context.i = -1 # minute counter
context.exchange = context.exchanges.values()[0].name.lower()
context.base_currency = 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 = 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 the 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='bitfinex',
data_frequency='minute',
base_currency='btc',
live=False,
live_graph=False,
algo_namespace='simple_universe')
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/simple_universe.py>`_
.. literalinclude:: ../../catalyst/examples/simple_universe.py
:language: python
.. _portfolio_optimization:
@@ -939,135 +201,10 @@ use 180 days of historical data and rebalance every 30 days. This code was used
in writting the following article:
`Markowitz Portfolio Optimization for Cryptocurrencies <https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556>`_.
.. code-block:: python
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/portfolio_optimization.py>`_
'''
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
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from datetime import datetime
from catalyst.api import record, symbol, 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')
# 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, )
.. literalinclude:: ../../catalyst/examples/portfolio_optimization.py
:language: python
.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ.
:align: center
+5 -5
View File
@@ -44,11 +44,11 @@ For additional details on the functionality added on recent releases, see the
Upcoming features
~~~~~~~~~~~~~~~~~
* Additional datasets beyond pricing data (Dec. 2017)
* API documentation (Jan. 2017)
* Support for decentralized exchanges (Jan. 2017)
* Support for data ingestion of community-contributed data sets (Jan. 2017)
* Pipeline support (Jan. 2018)
* Additional datasets beyond pricing data (Q1 2018)
* API documentation (Q1 2018)
* Support for decentralized exchanges (Q1 2018)
* Support for data ingestion of community-contributed data sets (Q1 2018)
* Pipeline support (Q1 2018)
* Web UI (Q2 2018)
+2
View File
@@ -1,6 +1,8 @@
.. include:: ../../README.rst
|
|
Table of Contents
-----------------
+126 -42
View File
@@ -47,8 +47,10 @@ you can install MiniConda, which is a smaller footprint (fewer packages and
smaller size) than its big brother Anaconda, but it still contains all the
main packages needed. To install MiniConda, you can follow these steps:
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select Python 2.7
for your Operating System.
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select either
Python 3.6 (recommended) or Python 2.7 for your Operating System. The
`Enigma Data Marketplace <https://enigmampc.github.io/marketplace/>`_ will
require Python3, that's why we are recommending to opt for the newer version.
2. Install MiniConda. See the `Installation Instructions
<https://conda.io/docs/user-guide/install/index.html>`_ if you need help.
3. Ensure the correct installation by running ``conda list`` in a Terminal
@@ -64,18 +66,27 @@ main packages needed. To install MiniConda, you can follow these steps:
Once either Conda or MiniConda has been set up you can install Catalyst:
1. Download the file `python2.7-environment.yml
<https://github.com/enigmampc/catalyst/blob/master/etc/python2.7-environment.yml>`_.
1. Download the file `python3.6-environment.yml
<https://github.com/enigmampc/catalyst/blob/master/etc/python3.6-environment.yml>`_
(recommended) or `python2.7-environment.yml
<https://github.com/enigmampc/catalyst/blob/master/etc/python2.7-environment.yml>`_
matching your Conda installation from step #1 above.
To download, simply click on the 'Raw' button and save the file locally
to a folder you can remember. Make sure that the file gets saved with the
``.yml`` extension, and nothing like a ``.txt`` file or anything else.
2. Open a Terminal window and enter [``cd/dir``] into the directory where you
saved the above ``python2.7-environment.yml`` file.
saved the above ``.yml`` file.
3. Install using this file. This step can take about 5-10 minutes to install.
.. code-block:: bash
conda env create -f python3.6-environment.yml
or
.. code-block:: bash
conda env create -f python2.7-environment.yml
@@ -122,10 +133,18 @@ with the following steps:
2. Create the environment:
for python 2.7:
.. code-block:: bash
conda create --name catalyst python=2.7 scipy zlib
or for python 3.6:
.. code-block:: bash
conda create --name catalyst python=3.6 scipy zlib
3. Activate the environment:
**Linux or MacOS:**
@@ -180,20 +199,6 @@ use a single tool to install Python and non-Python dependencies, or if you're
already using `Anaconda <http://continuum.io/downloads>`_ as your Python
distribution, refer to the :ref:`Installing with Conda <conda>` section.
Once you've installed the necessary additional dependencies for your system
(see below for your particular platform: :ref:`Linux`, :ref:`MacOS` or
:ref:`Windows`), you should be able to simply run
.. code-block:: bash
$ pip install enigma-catalyst matplotlib
Note that in the command above we install two different packages. The second
one, ``matplotlib`` is a visualization library. While it's not strictly
required to run catalyst simulations or live trading, it comes in very handy
to visualize the performance of your algorithms, and for this reason we
recommend you install it, as well.
If you use Python for anything other than Catalyst, we **strongly** recommend
that you install in a `virtualenv
<https://virtualenv.readthedocs.org/en/latest>`_. The `Hitchhiker's Guide to
@@ -206,8 +211,21 @@ summarized version:
$ pip install virtualenv
$ virtualenv catalyst-venv
$ source ./catalyst-venv/bin/activate
Once you've installed the necessary additional dependencies for your system
(:ref:`Linux`, :ref:`MacOS` or :ref:`Windows`) **and have activated your virtualenv**, you should be able to simply run
.. code-block:: bash
$ pip install enigma-catalyst matplotlib
Note that in the command above we install two different packages. The second
one, ``matplotlib`` is a visualization library. While it's not strictly
required to run catalyst simulations or live trading, it comes in very handy
to visualize the performance of your algorithms, and for this reason we
recommend you install it, as well.
Troubleshooting ``pip`` Install
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -219,13 +237,13 @@ Troubleshooting ``pip`` Install
.. code-block:: bash
pip install --upgrade pip
$ pip install --upgrade pip
On Windows, the recommended command is:
.. code-block:: bash
python -m pip install --upgrade pip
$ python -m pip install --upgrade pip
----
@@ -251,7 +269,7 @@ Troubleshooting ``pip`` Install
.. code-block:: bash
pip install --pre enigma-catalyst
$ pip install --pre enigma-catalyst
----
@@ -263,7 +281,7 @@ Troubleshooting ``pip`` Install
.. code-block:: bash
pip install --upgrade pip setuptools
$ pip install --upgrade pip setuptools
----
@@ -278,7 +296,7 @@ Troubleshooting ``pip`` Install
.. code-block:: bash
pip install -r requirements.txt
$ pip install -r requirements.txt
----
@@ -294,12 +312,22 @@ Troubleshooting ``pip`` Install
.. code-block:: bash
sudo apt-get install python-dev
$ sudo apt-get install python-dev
----
**Issue**:
Missing TA_Lib
**Solution**:
Follow `these instructions
<https://mrjbq7.github.io/ta-lib/install.html>`_ to install the TA_Lib Python wrapper
(and if needed, its underlying C library as well).
.. _pipenv:
Installing with ``pipenv``
-------------------------
--------------------------
Installing Catalyst via ``pipenv`` is perhaps easier that installing it via
``pip`` itself but you need to install ``pipenv`` first via ``pip``.
@@ -376,14 +404,14 @@ outdated. Thus, you first need to run:
.. code-block:: bash
pip install --upgrade pip setuptools
$ pip install --upgrade pip setuptools
The default installation is also missing the C and C++ compilers, which you
install by:
.. code-block:: bash
sudo yum install gcc gcc-c++
$ sudo yum install gcc gcc-c++
Then you should follow the regular installation instructions outlined at the
beginning of this page.
@@ -408,20 +436,34 @@ following brew packages:
$ brew install freetype pkg-config gcc openssl
MacOS + virtualenv + matplotlib
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MacOS + virtualenv/conda + matplotlib
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A note about using matplotlib in virtual enviroments on MacOS: it may be
necessary to run
The first time that you try to run an algorithm that loads the ``matplotlib``
library, you may get the following error:
.. code-block:: text
RuntimeError: Python is not installed as a framework. The Mac OS X backend
will not be able to function correctly if Python is not installed as a
framework. See the Python documentation for more information on installing
Python as a framework on Mac OS X. Please either reinstall Python as a
framework, or try one of the other backends. If you are using (Ana)Conda
please install python.app and replace the use of 'python' with 'pythonw'.
See 'Working with Matplotlib on OSX' in the Matplotlib FAQ for more
information.
This is a ``matplotlib``-specific error, that will go away once you run the
following command:
.. code-block:: bash
echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc
$ echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc
in order to override the default ``MacOS`` backend for your system, which
may not be accessible from inside the virtual environment. This will allow
Catalyst to open matplotlib charts from within a virtual environment, which
is useful for displaying the performance of your backtests. To learn more
may not be accessible from inside the virtual or conda environment. This will
allow Catalyst to open matplotlib charts from within a virtual environment,
which is useful for displaying the performance of your backtests. To learn more
about matplotlib backends, please refer to the
`matplotlib backend documentation <https://matplotlib.org/faq/usage_faq.html#what-is-a-backend>`_.
@@ -430,12 +472,22 @@ about matplotlib backends, please refer to the
Windows Requirements
--------------------
In Windows, you will first need to install the `Microsoft Visual C++ Compiler
for Python 2.7
<https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_. This
package contains the compiler and the set of system headers necessary for
producing binary wheels for Python 2.7 packages. If it's not already in your
system, download it and install it before proceeding to the next step.
In Windows, you will first need to install the Microsoft Visual C++ Compiler,
which is different depending on the version of Python that you plan to use:
* Python 3.5, 3.6: `Visual C++ 2015 Build Tools
<http://landinghub.visualstudio.com/visual-cpp-build-tools>`_,
which installs Visual C++ version 14.0. **This is the recommended version**
* Python 2.7: `Microsoft Visual C++ Compiler for Python 2.7
<https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_, which
installs version Visual C++ version 9.0
This package contains the compiler and the set of system headers necessary for
producing binary wheels for Python packages. If it's not already in your
system, download it and install it before proceeding to the next step. If you
need additional help, or are looking for other versions of Visual C++ for
Windows (only advanced users), follow `this link <https://wiki.python.org/moin/WindowsCompilers>`_.
Once you have the above compiler installed, the easiest and best supported way
to install Catalyst in Windows is to use :ref:`Conda <conda>`. If you didn't
@@ -463,6 +515,7 @@ mentioned above are as follows:
default you get 0 as the Value Data)
|
- **The installer has encountered an unexpected error installing this package.
This may indicate a problem with this package. The error code is 2503.**
@@ -475,6 +528,33 @@ mentioned above are as follows:
- ``cd`` into the folder where you downloaded ``VCForPython27.msi``
- Run ``msiexec /i VCForPython27.msi``
Updating Catalyst
-----------------
Catalyst is currently in alpha and in under very active development. We release
new minor versions every few days in response to the thorough battle testing
that our user community puts Catalyst in. As a result, you should expect to
update Catalyst frequently. Once installed, Catalyst can easily be updated as a
``pip`` package regardless of the environemnt used for installation. Make sure
you activate your environment first as you did in your first install, and then
execute:
.. code-block:: bash
$ pip uninstall enigma-catalyst
$ pip install enigma-catalyst
Alternatively, you could update Catalyst issuing the following command:
.. code-block:: bash
$ pip install -U enigma-catalyst
but this command will also upgrade all the Catalyst dependencies to the latest
versions available, and may have unexpected side effects if a newer version of a
dependency inadvertently breaks some functionality that Catalyst relies on.
Thus, the first method is the recommended one.
Getting Help
------------
@@ -482,6 +562,10 @@ If after following the instructions above, and going through the
*Troubleshooting* sections, you still experience problems installing Catalyst,
you can seek additional help through the following channels:
- Join our `Catalyst Forum <https://catalyst.enigma.co/>`_, and browse a variety
of topics and conversations around common issues that others face when using
Catalyst, and how to resolve them. And join the conversation!
- Join our `Discord community <https://discord.gg/SJK32GY>`_, and head over
the #catalyst_dev channel where many other users (as well as the project
developers) hang out, and can assist you with your particular issue. The
+83 -10
View File
@@ -4,11 +4,65 @@ This document explains how to get started with live trading.
Supported Exchanges
^^^^^^^^^^^^^^^^^^^
Catalyst can trade against these exchanges:
- Bitfinex, id= ``bitfinex``
- Bittrex, id= ``bittrex``
- Poloniex, id= ``poloniex``
Since version 0.4, Catalyst integrated with `CCXT <https://github.com/ccxt/ccxt>`_,
a cryptocurrency trading library with support for more than 90 exchanges. The
range of CCXT and Catalyst support for each of those exchanges varies greatly.
The most supported exchanges are as follows:
The exchanges available for backtesting are fully supported in live mode:
- Bitfinex, id = ``bitfinex``
- Bittrex, id = ``bittrex``
- Poloniex, id = ``poloniex``
Additionally, we have successfully tested the following exchanges:
- Binance, id = ``binance``
- Bitmex, id = ``bitmex``
- GDAX, id = ``gdax``
As Catalyst is currently in Alpha and in under active development, you are
encouraged to throughly test any exchange in *paper trading* mode before trading
*live* with it.
Paper Trading vs Live Trading modes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Catalyst currently supports three different modes in which you can execute your
trading algorithm. The first is **backtesting**, which is covered extensively in
the tutorial, and uses historical data to run your algorithm. There is no
interaction with the exchange in backtesting mode, and this is the first mode
that you should test any new algorithm.
Once you are confident with the simulations that you have obtained with your
algorithm in backtesting, you may switch to live trading, where you have two
different modes:
* **Paper Trading**: The simulated algorithm runs in real time, and fetches
pricing data in real time from the exchange, but the orders never reach the
exchange, and are instead kept within Catalyst and simulated. No real currency
is bought or sold. Think of it as a `backtesting happening in real time`.
* **Live Trading**: This is the proper live trading mode in which an algorithm
runs in real time, fetching pricing data from live exchanges and placing
orders against the exchange. Real currency is transacted on the exchange
driven by the algorithm.
These three modes are controlled by the following variables:
+---------------+-------------------------+
| Mode | Parameters |
+ +-------+-----------------+
| | live | simulate_orders |
+---------------+-------+-----------------+
| backtesting | False | True (default) |
+---------------+-------+-----------------+
| paper trading | True | True |
+---------------+-------+-----------------+
| live trading | True | False |
+---------------+-------+-----------------+
Authentication
^^^^^^^^^^^^^^
@@ -61,7 +115,7 @@ Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention.
Here are some examples:
.. code-block:: json
.. code:: python
# With Bitfinex
bitcoin_usd_asset = symbol('btc_usd')
@@ -75,7 +129,8 @@ Note that the trading pairs are always referenced in the same manner.
However, not all trading pairs are available on all exchanges. An
error will occur if the specified trading pair is not trading
on the exchange. To check which currency pairs are available on each
of the supported exchanges, see `Catalyst Market Coverage <https://www.enigma.co/catalyst/status`_.
of the supported exchanges, see
`Catalyst Market Coverage <https://www.enigma.co/catalyst/status>`_.
Trading an Algorithm
^^^^^^^^^^^^^^^^^^^^
@@ -105,20 +160,38 @@ What differs are the arguments provided to the catalyst client or
Here is the breakdown of the new arguments:
- ``live``: Boolean flag which enables live trading.
- ``live``: Boolean flag which enables live trading. It defaults to ``False``.
- ``capital_base``: The amount of base_currency assigned to the strategy.
It has to be lower or equal to the amount of base currency available for
trading on the exchange. For illustration, order_target_percent(asset, 1)
will order the capital_base amount specified here of the specified asset.
- ``exchange_name``: The name of the targeted exchange
(supported values: *bitfinex*, *bittrex*).
- ``exchange_name``: The name of the targeted exchange. See the
`CCXT Supported Exchanges <https://github.com/ccxt/ccxt/wiki/Exchange-Markets>`_
for the full list.
- ``algo_namespace``: A arbitrary label assigned to your algorithm for
data storage purposes.
- ``base_currency``: The base currency used to calculate the
statistics of your algorithm. Currently, the base currency of all
trading pairs of your algorithm must match this value.
- ``simulate_orders``: Enables the paper trading mode, in which orders are
simulated in Catalyst instead of processed on the exchange.
simulated in Catalyst instead of processed on the exchange. It defaults to
``True``.
- ``end_date``: When setting the end_date to a time in the **future**,
it will schedule the live algo to finish gracefully at the specified date.
- ``start_date``: (**Will be implemented in the future**)
The live algo starts by default in the present, as mentioned above.
by setting the start_date to a time in the future, the algorithm would
essentially sleep and when the predefined time comes, it would start executing.
The `catalyst live` command offers additional parameters.
You can learn more by running the following from the command line:
.. code-block:: bash
catalyst live --help
Here is a complete algorithm for reference:
`Buy Low and Sell High <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_low_sell_high_live.py>`_
+184 -3
View File
@@ -2,9 +2,190 @@
Release Notes
=============
Version 0.4.1
Version 0.5.8
^^^^^^^^^^^^^
**Release Date**: 2017-01-03
**Release Date**: 2018-03-29
Bug Fixes
~~~~~~~~~
- Fix proper release of Data Marketplace on mainnet.
Version 0.5.7
^^^^^^^^^^^^^
**Release Date**: 2018-03-29
Build
~~~~~
- Data Marketplace deployed on mainnet.
- Added progress indicators for publishing data, and made the data publishing
synchronous to provide feedback to the publisher.
Bug Fixes
~~~~~~~~~
- Added arguments to the ``reduce`` function in tha Asset class :issue:`214`,
:issue:`287`
Version 0.5.6
^^^^^^^^^^^^^
**Release Date**: 2018-03-22
Build
~~~~~
- Data Marketplace: ensures compatibility across wallets, now fully supporting
``ledger``, ``trezor``, ``keystore``, ``private key``. Partial support for
``metamask`` (includes sign_msg, but not sign_tx). Current support for
``Digital Bitbox`` is unknown, but believed to be supported.
- Data Marketplace: Switched online provider from MyEtherWallet to MyCrypto.
- Data Marketplace: Added progress indicator for data ingestion.
Bug Fixes
~~~~~~~~~
- Changed benchmark to be constant, so it doesn't ingest data at all. Temporary
fix for :issue:`271`, :issue:`285`
Version 0.5.5
^^^^^^^^^^^^^
**Release Date**: 2018-03-19
Bug Fixes
~~~~~~~~~
- Fixed an issue with the data history in daily frequency :issue:`274`
- Fix hourly frequency issues :issue:`227` and :issue:`114`
Version 0.5.4
^^^^^^^^^^^^^
**Release Date**: 2018-03-14
Build
~~~~~
- Switched Data Marketplace from Ropstein testnet to Rinkeby testnet after
incorporating changes resulting from the marketplace contract audit
- Several usability improvements of the Data Marketplace that make the
`--dataset` parameter optional. If it is not included in the command line,
will list available datasets, and let you choose interactively.
Bug Fixes
~~~~~~~~~
- Fix Binance requirement of symbol to be included in the cancelled order
:issue:`204`
- Fix `notenoughcasherror` when an open order is filled minutes later
:issue:`237`
- Properly handle of empty candles received from exchanges :issue:`236`
- Added a function to reduce open orders amount from calculated target/amount
for target orders :issue:`243`
- Fix missing file in live trading mode on date change :issue:`252`,
:issue:`253`
- Upgraded Data Marketplace to Web3==4.0.0b11, which was breaking some
functionality from prior version 4.0.0b7 :issue:`257`
- Always request more data to avoid empty bars and always give the exact bar
number :issue:`260`
Documentation
~~~~~~~~~~~~~
- PyCharm documentation :issue:`195`
- Added TA-Lib troubleshooting instructions
- Added instructions on how to create a Conda environment for Python 3.6, and
updated Visual C++ instructions for Windows and Python 3
- Linking example algorithms in the documentation to their sources
Version 0.5.3
^^^^^^^^^^^^^
**Release Date**: 2018-02-09
Bug Fixes
~~~~~~~~~
- Fixed an issue with last candle in backtesting :issue:`219`
Version 0.5.2
^^^^^^^^^^^^^
**Release Date**: 2018-02-08
Bug Fixes
~~~~~~~~~
- Fixed an issue with live candle values :issue:`216` and :issue:`199`
Version 0.5.1
^^^^^^^^^^^^^
**Release Date**: 2018-02-07
Bug Fixes
~~~~~~~~~
- Fixed an issue with orders that stay open :issue:`211`
- Fixed Jupyter issues :issue:`179`
- Fetching multiple tickers in one call to minimize rate limit risks :issue:`174`
- Improved live state presentation :issue:`171`
Build
~~~~~
- Introducing the Enigma Marketplace
Version 0.4.7
^^^^^^^^^^^^^
**Release Date**: 2018-01-19
Bug Fixes
~~~~~~~~~
- Fixing issue :issue:`137` impacting the CLI
Build
~~~~~
- Implemented authentication aliases (:issue:`60`)
Version 0.4.6
^^^^^^^^^^^^^
**Release Date**: 2018-01-18
Bug Fixes
~~~~~~~~~
- Fixed some Python3 issues
- Reading the trade log to get executed order prices on exchanges like Binance (:issue:`151`)
- Fixed issue with market order executing price (:issue:`150` and :issue:`111`)
- Implemented standardized symbol mapping (:issue:`157`)
- Improved error handling for unsupported timeframes (:issue:`159`)
- Using Bitfinex instead of Poloniex to fetch btc_usdt benchmark (:issue:`161`)
Build
~~~~~
- Added a `context.state` dict to keep arbitrary state values between runs
- Added ability to stop live algo at specified end date
Version 0.4.5
^^^^^^^^^^^^^
**Release Date**: 2018-01-12
Bug Fixes
~~~~~~~~~
- Improved order execution for exchanges supporting trade lists (:issue:`151`)
- Fixed an issue where requesting history of multiple assets repeats values
- Raising an error for order amounts smaller than exchange lots
- Handling multiple req errors with tickers more gracefully (:issue:`160`)
Version 0.4.4
^^^^^^^^^^^^^
**Release Date**: 2018-01-09
Bug Fixes
~~~~~~~~~
- Removed redundant capital_base validation (:issue:`142`)
- Fixed portfolio update issue with restored state (:issue:`111`)
- Skipping cash validation where there are open orders (:issue:`144`)
Version 0.4.3
^^^^^^^^^^^^^
**Release Date**: 2018-01-05
Bug Fixes
~~~~~~~~~
- Fixed CLI issue (:issue:`137`)
- Upgraded CCXT
Version 0.4.2
^^^^^^^^^^^^^
**Release Date**: 2018-01-03
Bug Fixes
~~~~~~~~~
@@ -39,7 +220,7 @@ Build
- Added market orders in live mode (:issue:`81`)
Version 0.3.10
^^^^^^^^^^^^^
~~~~~~~~~~~~~~
**Release Date**: 2017-11-28
Bug Fixes
+6 -1
View File
@@ -11,6 +11,7 @@ Installation: MacOS
|
|
Installation: Windows
---------------------
@@ -21,6 +22,7 @@ Where things go smoothly:
<iframe width="560" height="315" src="https://www.youtube.com/embed/H8HqcEbZmkk" frameborder="0" allowfullscreen></iframe>
|
Where things don't:
.. raw:: html
@@ -29,6 +31,7 @@ Where things don't:
|
|
Backtesting a Strategy
----------------------
@@ -44,6 +47,7 @@ sell. Hopefully, well ride the waves.
|
|
Live Trading a Strategy
-----------------------
@@ -54,5 +58,6 @@ in the previous video, we now take it to trade live against the Bittrex exchange
.. raw:: html
<iframe width="560" height="315" src="https://www.youtube.com/embed/NupiE-Xuglw" frameborder="0" allowfullscreen></iframe>
|
|
|
+1 -1
View File
@@ -16,4 +16,4 @@ fi
jupyter notebook -y --no-browser --notebook-dir=${PROJECT_DIR} \
--certfile=${SSL_CERT_PEM} --keyfile=${SSL_CERT_KEY} --ip='*' \
--config=${CONFIG_PATH}
--config=${CONFIG_PATH} --allow-root
+9 -4
View File
@@ -1,6 +1,7 @@
name: catalyst
channels:
- defaults
- conda-forge
dependencies:
- certifi=2016.2.28=py27_0
- mkl=2017.0.3
@@ -20,7 +21,11 @@ dependencies:
- bcolz==0.12.1
- bottleneck==1.2.1
- chardet==3.0.4
- ccxt==1.10.283
- ccxt==1.10.1094
# The Enigma Data Marketplace requires Python3 because it depends on
# web3, which requires Python3, as building its dependencies breaks in Python2
# - web3==4.0.0b7
- requests-toolbelt==0.8.0
- click==6.7
- contextlib2==0.5.5
- cycler==0.10.0
@@ -34,11 +39,11 @@ dependencies:
- lru-dict==1.1.6
- mako==1.0.7
- markupsafe==1.0
- matplotlib==2.1.0
- matplotlib==2.1.2
- multipledispatch==0.4.9
- networkx==2.0
- numexpr==2.6.4
- pandas==0.19.2
- pandas==0.22.0
- pandas-datareader==0.5.0
- patsy==0.4.1
- pyparsing==2.2.0
@@ -57,4 +62,4 @@ dependencies:
- tables==3.4.2
- toolz==0.8.2
- urllib3==1.22
- enigma-catalyst>=0.3
- enigma-catalyst>=0.5
+90
View File
@@ -0,0 +1,90 @@
name: catalyst
channels:
- defaults
- conda-forge
dependencies:
- ca-certificates=2017.08.26
- certifi=2018.1.18
- intel-openmp=2018.0.0
- mkl=2018.0.1
- numpy=1.14.0
- openssl=1.0.2n
- matplotlib=2.1.2=py36_0
- pip=9.0.1
- python=3.6.4
- scipy=1.0.0
- setuptools=38.4.0=py36_0
- sqlite=3.22.0
- tk=8.6.7
- wheel=0.30.0
- xz=5.2.3
- zlib=1.2.11
- pip:
- aiodns==1.1.1
- aiohttp==3.0.1
- alembic==0.9.7
- async-timeout==2.0.0
- attrdict==2.0.0
- attrs==17.4.0
- bcolz==0.12.1
- boto3==1.5.27
- botocore==1.8.41
- bottleneck==1.2.1
- cchardet==2.1.1
- ccxt==1.10.1102
- chardet==3.0.4
- click==6.7
- contextlib2==0.5.5
- cyordereddict==1.0.0
- cython==0.27.3
- cytoolz==0.9.0
- decorator==4.2.1
- docutils==0.14
- empyrical==0.2.1
- enigma-catalyst>=0.5.3
- eth-abi==1.0.0b0
- eth-account==0.1.0a2
- eth-keyfile==0.5.1
- eth-keys==0.2.0b1
- eth-rlp==0.1.0a2
- eth-utils==1.0.0b1
- hexbytes==0.1.0b0
- idna==2.6
- idna-ssl==1.0.0
- intervaltree==2.1.0
- jmespath==0.9.3
- logbook==1.2.1
- lru-dict==1.1.6
- lxml==4.1.1
- mako==1.0.7
- markupsafe==1.0
- multidict==4.1.0
- multipledispatch==0.4.9
- networkx==2.1
- numexpr==2.6.4
- pandas==0.22.0
- pandas-datareader==0.6.0
- patsy==0.5.0
- pycares==2.3.0
- pycryptodome==3.4.11
- pysha3==1.0.2
- python-dateutil==2.6.1
- python-editor==1.0.3
- pytz==2018.3
- redo==1.6
- requests==2.18.4
- requests-file==1.4.3
- requests-ftp==0.3.1
- requests-toolbelt==0.8.0
- rlp==0.6.0
- s3transfer==0.1.12
- six==1.11.0
- sortedcontainers==1.5.9
- sqlalchemy==1.2.2
- statsmodels==0.8.0
- tables==3.4.2
- toolz==0.9.0
- urllib3==1.22
- web3==4.0.0b9
- wrapt==1.10.11
- yarl==1.1.0
+4 -2
View File
@@ -19,7 +19,7 @@ requests-file==1.4.1
# scipy and pandas are required for statsmodels,
# statsmodels in turn is required for some pandas packages
scipy==0.17.1
pandas==0.19.2
pandas==0.22.0
pandas-datareader==0.2.1
# Needed for parts of pandas.stats
patsy==0.4.0
@@ -81,6 +81,8 @@ empyrical==0.2.1
tables==3.3.0
#Catalyst dependencies
ccxt==1.10.283
ccxt==1.10.1094
boto3==1.4.8
redo==1.6
web3==4.0.0b11; python_version > '3.4'
requests-toolbelt==0.8.0
+1 -1
View File
@@ -16,7 +16,7 @@ babel==1.3
docutils==0.12
snowballstemmer==1.2.0
sphinx-rtd-theme==0.1.8
sphinx==1.3.4
sphinx==1.6.7
pbr==1.10.0
mock==2.0.0
+2 -1
View File
@@ -1,3 +1,4 @@
Sphinx>=1.3.2
Sphinx==1.6.7
numpydoc>=0.5.0
sphinx-autobuild==0.6.0
docutils==0.12
+1 -1
View File
@@ -165,7 +165,7 @@ def _filter_requirements(lines_iter, filter_names=None,
REQ_UPPER_BOUNDS = {
'bcolz': '<1',
'pandas': '<0.20',
'pandas': '>=0.22',
'empyrical': '<0.2.2',
}

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