mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c85e698ee2 | ||
|
|
423e30da1e | ||
|
|
c29b1ef3c1 | ||
|
|
17f9906df4 | ||
|
|
41b5135ed4 | ||
|
|
af85ee31c9 | ||
|
|
132dffd239 | ||
|
|
887a7cc825 | ||
|
|
22249506e6 | ||
|
|
b979ffd123 | ||
|
|
0db9950347 | ||
|
|
7c4467d800 | ||
|
|
30dbeaa5fb | ||
|
|
4d6837b5d6 | ||
|
|
098a4c4fc6 | ||
|
|
f5cb6e38d6 | ||
|
|
1bd65397b6 | ||
|
|
c768b207bc | ||
|
|
e18686d5c5 | ||
|
|
b7779cf363 | ||
|
|
28819b8a32 | ||
|
|
a56d7f34c7 | ||
|
|
9f0b3303f1 | ||
|
|
9d7a35658b | ||
|
|
c58cebd1eb | ||
|
|
027cdba474 | ||
|
|
d223529100 | ||
|
|
1e02506ab4 | ||
|
|
2a97ade68e | ||
|
|
9648767e9a | ||
|
|
98449b2088 | ||
|
|
91d16aba3b | ||
|
|
9eb649371b | ||
|
|
7f2ded65bc | ||
|
|
b76b4458cb | ||
|
|
decbdbf6ea | ||
|
|
685ce25b85 | ||
|
|
1cafcc1417 | ||
|
|
0d77854782 | ||
|
|
4cb8d54d97 | ||
|
|
7b796a4276 | ||
|
|
41a4c7072f | ||
|
|
11302b3af9 | ||
|
|
5bb7eed072 | ||
|
|
dbf3b6e6b2 | ||
|
|
3e69449a6b | ||
|
|
69731b653d | ||
|
|
127d779eb1 | ||
|
|
8d86a5548f | ||
|
|
0a37cdec5b | ||
|
|
8f7d678170 | ||
|
|
1de084a08f | ||
|
|
93ca4e990d | ||
|
|
4694372496 | ||
|
|
f0606b5ea4 | ||
|
|
b0dce13672 | ||
|
|
fc9837b678 | ||
|
|
5de89549ee | ||
|
|
586d7f2954 | ||
|
|
218dc0bafd | ||
|
|
89c080fce7 | ||
|
|
4a794aa035 | ||
|
|
cb4668f093 | ||
|
|
6092471180 | ||
|
|
09068a4c37 | ||
|
|
ed406a30ff | ||
|
|
aa520d5a8b | ||
|
|
e59f46dfd6 | ||
|
|
5197ab6cc2 | ||
|
|
d7b6cb8490 | ||
|
|
fa0e9332bf | ||
|
|
d9d6a4e52d | ||
|
|
b4e5b699bd | ||
|
|
f990ecf14d | ||
|
|
5d3a1c2f8b | ||
|
|
3159ec7dc2 | ||
|
|
8be0626fc9 | ||
|
|
868f17fd9d | ||
|
|
b95cf465fc | ||
|
|
c4b10bae39 | ||
|
|
6c4f7afaea | ||
|
|
b85219d5b4 | ||
|
|
082b342d02 | ||
|
|
37c1057ab6 | ||
|
|
46e1a87a3d | ||
|
|
cfafafb8fc | ||
|
|
497212383a | ||
|
|
e5870ea60a | ||
|
|
8587fee0ce | ||
|
|
388535b09c | ||
|
|
25e9f0f58f | ||
|
|
b4bd557273 | ||
|
|
2577b53518 | ||
|
|
8fe3ab344e | ||
|
|
bfd7e4b2dd | ||
|
|
50310576f9 | ||
|
|
fea2ed104e | ||
|
|
127878413e | ||
|
|
6a6ccf5595 | ||
|
|
d40585f56e | ||
|
|
4337abd60a | ||
|
|
92e0a7bb88 | ||
|
|
20f8a75f4a | ||
|
|
634d22fb06 | ||
|
|
7ee875ee70 | ||
|
|
9936b38e09 | ||
|
|
7e22674d40 | ||
|
|
ab644bd732 | ||
|
|
2becc4157a | ||
|
|
ec5fdecf91 | ||
|
|
bd88ba2277 | ||
|
|
a61857b37d | ||
|
|
866b0a215c | ||
|
|
2ba825db9b | ||
|
|
041665dee1 | ||
|
|
8072ded532 | ||
|
|
9956b5462d | ||
|
|
92d3b98448 | ||
|
|
46f34d64a0 | ||
|
|
d494de7551 | ||
|
|
129778945f | ||
|
|
f3183b267a | ||
|
|
6812aa0c5b | ||
|
|
89bf6742e3 | ||
|
|
f45d673d94 | ||
|
|
67400f048b | ||
|
|
d64d6f191f | ||
|
|
5345bfc43d | ||
|
|
e9745be0e8 | ||
|
|
bc8bf6941d | ||
|
|
f35444fabc | ||
|
|
2c1f810163 | ||
|
|
77570f03eb | ||
|
|
a8cfcead70 | ||
|
|
00f579c729 | ||
|
|
49b6792399 | ||
|
|
6b1982274e | ||
|
|
bc58640a3a | ||
|
|
511af7946e | ||
|
|
dcdb3d9d7a | ||
|
|
262dffd4bf | ||
|
|
2d5d2b21ee | ||
|
|
5c77cfc68d | ||
|
|
11f8e36ff0 | ||
|
|
44e4e66f5c | ||
|
|
3c4c6c3dfd | ||
|
|
bebebc46dc | ||
|
|
1f29f7fdf1 | ||
|
|
cbae2465d2 | ||
|
|
4a4e32846b | ||
|
|
426fde40b1 | ||
|
|
e9a3bcf3e9 | ||
|
|
5b77f5eeba | ||
|
|
5417881a72 | ||
|
|
1cadaf1ed0 | ||
|
|
929e0a9a01 | ||
|
|
15aadbb101 |
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
#
|
#
|
||||||
# Dockerfile for an image with the currently checked out version of catalyst installed. To build:
|
# 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:
|
# 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):
|
# To access Jupyter when running docker locally (you may need to add NAT rules):
|
||||||
#
|
#
|
||||||
|
|||||||
+5
-5
@@ -1,15 +1,15 @@
|
|||||||
#
|
#
|
||||||
# Dockerfile for an image with the currently checked out version of catalyst installed. To build:
|
# 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:
|
# 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):
|
# 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
|
# 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
|
WORKDIR /catalyst
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
|version tag|
|
|version tag|
|
||||||
|version status|
|
|version status|
|
||||||
|
|forum|
|
||||||
|discord|
|
|discord|
|
||||||
|twitter|
|
|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,
|
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 Poloniex) with more being added over time. Catalyst empowers users to share
|
||||||
and curate data and build profitable, data-driven investment strategies. Please
|
and curate data and build profitable, data-driven investment strategies. Please
|
||||||
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst, or
|
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst.
|
||||||
refer to the `whitepaper <https://www.enigma.co/enigma_catalyst.pdf>`_ for
|
|
||||||
further technical details.
|
|
||||||
|
|
||||||
Catalyst builds on top of the well-established
|
Catalyst builds on top of the well-established
|
||||||
`Zipline <https://github.com/quantopian/zipline>`_ project. We did our best to
|
`Zipline <https://github.com/quantopian/zipline>`_ project. We did our best to
|
||||||
minimize structural changes to the general API to maximize compatibility with
|
minimize structural changes to the general API to maximize compatibility with
|
||||||
existing trading algorithms, developer knowledge, and tutorials. Join us on
|
existing trading algorithms, developer knowledge, and tutorials. Join us on the
|
||||||
`Discord <https://discord.gg/SJK32GY>`_ where we have a *#catalyst_dev* channel
|
`Catalyst Forum <https://catalyst.enigma.co/>`_ for questions around Catalyst,
|
||||||
for questions around Catalyst, algorithmic trading and technical support.
|
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
|
Overview
|
||||||
========
|
========
|
||||||
@@ -63,6 +64,9 @@ Go to our `Documentation Website <https://enigmampc.github.io/catalyst/>`_.
|
|||||||
.. |version status| image:: https://img.shields.io/pypi/pyversions/enigma-catalyst.svg
|
.. |version status| image:: https://img.shields.io/pypi/pyversions/enigma-catalyst.svg
|
||||||
:target: https://pypi.python.org/pypi/enigma-catalyst
|
: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
|
.. |discord| image:: https://img.shields.io/badge/discord-join%20chat-green.svg
|
||||||
:target: https://discordapp.com/invite/SJK32GY
|
:target: https://discordapp.com/invite/SJK32GY
|
||||||
|
|
||||||
|
|||||||
+17
-16
@@ -580,7 +580,7 @@ def ingest_exchange(ctx, exchange_name, data_frequency, start, end,
|
|||||||
|
|
||||||
exchange_bundle = ExchangeBundle(exchange_name)
|
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)
|
sys.stdout)
|
||||||
exchange_bundle.ingest(
|
exchange_bundle.ingest(
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
@@ -767,12 +767,18 @@ def bundles():
|
|||||||
@main.group()
|
@main.group()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def marketplace(ctx):
|
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
|
pass
|
||||||
|
|
||||||
|
|
||||||
@marketplace.command()
|
@marketplace.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def ls(ctx):
|
def ls(ctx):
|
||||||
|
"""List all available datasets.
|
||||||
|
"""
|
||||||
click.echo('Listing of available data sources on the marketplace:',
|
click.echo('Listing of available data sources on the marketplace:',
|
||||||
sys.stdout)
|
sys.stdout)
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
@@ -787,10 +793,8 @@ def ls(ctx):
|
|||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def subscribe(ctx, dataset):
|
def subscribe(ctx, dataset):
|
||||||
if dataset is None:
|
"""Subscribe to an existing dataset.
|
||||||
ctx.fail("must specify a dataset to subscribe to with '--dataset'\n"
|
"""
|
||||||
"List available dataset on the marketplace with "
|
|
||||||
"'catalyst marketplace ls'")
|
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
marketplace.subscribe(dataset)
|
marketplace.subscribe(dataset)
|
||||||
|
|
||||||
@@ -825,11 +829,8 @@ def subscribe(ctx, dataset):
|
|||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def ingest(ctx, dataset, data_frequency, start, end):
|
def ingest(ctx, dataset, data_frequency, start, end):
|
||||||
if dataset is None:
|
"""Ingest a dataset (requires subscription).
|
||||||
ctx.fail("must specify a dataset to clean with '--dataset'\n"
|
"""
|
||||||
"List available dataset on the marketplace with "
|
|
||||||
"'catalyst marketplace ls'")
|
|
||||||
click.echo('Ingesting data: {}'.format(dataset), sys.stdout)
|
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
marketplace.ingest(dataset, data_frequency, start, end)
|
marketplace.ingest(dataset, data_frequency, start, end)
|
||||||
|
|
||||||
@@ -842,19 +843,17 @@ def ingest(ctx, dataset, data_frequency, start, end):
|
|||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def clean(ctx, dataset):
|
def clean(ctx, dataset):
|
||||||
if dataset is None:
|
"""Clean/Remove local data for a given dataset.
|
||||||
ctx.fail("must specify a dataset to ingest with '--dataset'\n"
|
"""
|
||||||
"List available dataset on the marketplace with "
|
|
||||||
"'catalyst marketplace ls'")
|
|
||||||
click.echo('Cleaning data source: {}'.format(dataset), sys.stdout)
|
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
marketplace.clean(dataset)
|
marketplace.clean(dataset)
|
||||||
click.echo('Done', sys.stdout)
|
|
||||||
|
|
||||||
|
|
||||||
@marketplace.command()
|
@marketplace.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def register(ctx):
|
def register(ctx):
|
||||||
|
"""Register a new dataset.
|
||||||
|
"""
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
marketplace.register()
|
marketplace.register()
|
||||||
|
|
||||||
@@ -878,6 +877,8 @@ def register(ctx):
|
|||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def publish(ctx, dataset, datadir, watch):
|
def publish(ctx, dataset, datadir, watch):
|
||||||
|
"""Publish data for a registered dataset.
|
||||||
|
"""
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
if dataset is None:
|
if dataset is None:
|
||||||
ctx.fail("must specify a dataset to publish data for "
|
ctx.fail("must specify a dataset to publish data for "
|
||||||
|
|||||||
@@ -939,7 +939,7 @@ class TradingAlgorithm(object):
|
|||||||
The field to query. The options have the following meanings:
|
The field to query. The options have the following meanings:
|
||||||
arena : str
|
arena : str
|
||||||
The arena from the simulation parameters. This will normally
|
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.
|
live trading from backtesting.
|
||||||
data_frequency : {'daily', 'minute'}
|
data_frequency : {'daily', 'minute'}
|
||||||
data_frequency tells the algorithm if it is running with
|
data_frequency tells the algorithm if it is running with
|
||||||
@@ -954,7 +954,7 @@ class TradingAlgorithm(object):
|
|||||||
The platform that the code is running on. By default this
|
The platform that the code is running on. By default this
|
||||||
will be the string 'catalyst'. This can allow algorithms to
|
will be the string 'catalyst'. This can allow algorithms to
|
||||||
know if they are running on the Quantopian platform instead.
|
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 all of the fields in a dictionary.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -1032,7 +1032,7 @@ class TradingAlgorithm(object):
|
|||||||
argument is the name of the column in the preprocessed dataframe
|
argument is the name of the column in the preprocessed dataframe
|
||||||
containing the symbols. This will be used along with the date
|
containing the symbols. This will be used along with the date
|
||||||
information to map the sids in the asset finder.
|
information to map the sids in the asset finder.
|
||||||
**kwargs
|
\*\*kwargs
|
||||||
Forwarded to :func:`pandas.read_csv`.
|
Forwarded to :func:`pandas.read_csv`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -1156,7 +1156,7 @@ class TradingAlgorithm(object):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
**kwargs
|
\*\*kwargs
|
||||||
The names and values to record.
|
The names and values to record.
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
@@ -1273,7 +1273,7 @@ class TradingAlgorithm(object):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
*args : iterable[str]
|
\*args : iterable[str]
|
||||||
The ticker symbols to lookup.
|
The ticker symbols to lookup.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|||||||
@@ -630,23 +630,28 @@ cdef class TradingPair(Asset):
|
|||||||
and whose second element is a tuple of all the attributes that should
|
and whose second element is a tuple of all the attributes that should
|
||||||
be serialized/deserialized during pickling.
|
be serialized/deserialized during pickling.
|
||||||
"""
|
"""
|
||||||
#TODO: make sure that all fields set there
|
# added arguments for catalyst
|
||||||
return (self.__class__, (self.symbol,
|
return (self.__class__, (self.symbol,
|
||||||
self.exchange,
|
self.exchange,
|
||||||
self.start_date,
|
self.start_date,
|
||||||
self.asset_name,
|
self.asset_name,
|
||||||
self.sid,
|
self.sid,
|
||||||
self.leverage,
|
self.leverage,
|
||||||
|
self.end_daily,
|
||||||
|
self.end_minute,
|
||||||
self.end_date,
|
self.end_date,
|
||||||
|
self.exchange_symbol,
|
||||||
self.first_traded,
|
self.first_traded,
|
||||||
self.auto_close_date,
|
self.auto_close_date,
|
||||||
self.exchange_full,
|
self.exchange_full,
|
||||||
self.min_trade_size,
|
self.min_trade_size,
|
||||||
self.max_trade_size,
|
self.max_trade_size,
|
||||||
|
self.maker,
|
||||||
|
self.taker,
|
||||||
self.lot,
|
self.lot,
|
||||||
self.decimals,
|
self.decimals,
|
||||||
self.taker,
|
self.trading_state,
|
||||||
self.maker))
|
self.data_source))
|
||||||
|
|
||||||
def make_asset_array(int size, Asset asset):
|
def make_asset_array(int size, Asset asset):
|
||||||
cdef np.ndarray out = np.empty([size], dtype=object)
|
cdef np.ndarray out = np.empty([size], dtype=object)
|
||||||
|
|||||||
@@ -24,23 +24,23 @@ AUTO_INGEST = False
|
|||||||
|
|
||||||
AUTH_SERVER = 'https://data.enigma.co'
|
AUTH_SERVER = 'https://data.enigma.co'
|
||||||
|
|
||||||
# TODO: switch to mainnet
|
ETH_REMOTE_NODE = 'https://mainnet.infura.io'
|
||||||
ETH_REMOTE_NODE = 'https://ropsten.infura.io/'
|
|
||||||
|
|
||||||
# TODO: move to MASTER branch on github
|
|
||||||
MARKETPLACE_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/' \
|
MARKETPLACE_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/' \
|
||||||
'catalyst/develop/catalyst/marketplace/' \
|
'catalyst/master/catalyst/marketplace/' \
|
||||||
'contract_marketplace_address.txt'
|
'contract_marketplace_address.txt'
|
||||||
|
|
||||||
MARKETPLACE_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
|
MARKETPLACE_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
|
||||||
'catalyst/develop/catalyst/marketplace/' \
|
'catalyst/master/catalyst/marketplace/' \
|
||||||
'contract_marketplace_abi.json'
|
'contract_marketplace_abi.json'
|
||||||
|
|
||||||
# TODO: switch to mainnet
|
ENIGMA_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/' \
|
||||||
ENIGMA_CONTRACT = 'https://raw.githubusercontent.com/enigmampc/catalyst/' \
|
'catalyst/master/catalyst/marketplace/' \
|
||||||
'develop/catalyst/marketplace/' \
|
|
||||||
'contract_enigma_address.txt'
|
'contract_enigma_address.txt'
|
||||||
|
|
||||||
ENIGMA_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
|
ENIGMA_CONTRACT_ABI = 'https://raw.githubusercontent.com/enigmampc/' \
|
||||||
'catalyst/develop/catalyst/marketplace/' \
|
'catalyst/master/catalyst/marketplace/' \
|
||||||
'contract_enigma_abi.json'
|
'contract_enigma_abi.json'
|
||||||
|
|
||||||
|
SUPPORTED_WALLETS = ['metamask', 'ledger', 'trezor', 'bitbox', 'keystore',
|
||||||
|
'key']
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from catalyst.api import (
|
|||||||
order_target_percent,
|
order_target_percent,
|
||||||
symbol,
|
symbol,
|
||||||
record,
|
record,
|
||||||
get_open_orders,
|
|
||||||
)
|
)
|
||||||
from catalyst.exchange.utils.stats_utils import get_pretty_stats
|
from catalyst.exchange.utils.stats_utils import get_pretty_stats
|
||||||
from catalyst.utils.run_algo import run_algorithm
|
from catalyst.utils.run_algo import run_algorithm
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import pandas as pd
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import (record, symbol, order_target_percent,
|
from catalyst.api import (record, symbol, order_target_percent,)
|
||||||
get_open_orders)
|
|
||||||
from catalyst.exchange.utils.stats_utils import extract_transactions
|
from catalyst.exchange.utils.stats_utils import extract_transactions
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
NAMESPACE = 'dual_moving_average'
|
||||||
@@ -20,8 +19,8 @@ def initialize(context):
|
|||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
# define the windows for the moving averages
|
# define the windows for the moving averages
|
||||||
short_window = 2
|
short_window = 50
|
||||||
long_window = 2
|
long_window = 200
|
||||||
|
|
||||||
# Skip as many bars as long_window to properly compute the average
|
# Skip as many bars as long_window to properly compute the average
|
||||||
context.i += 1
|
context.i += 1
|
||||||
@@ -63,7 +62,7 @@ def handle_data(context, data):
|
|||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
# Since we are using limit orders, some orders may not execute immediately
|
||||||
# we wait until all orders are executed before considering more trades.
|
# 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:
|
if len(orders) > 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -150,27 +149,16 @@ def analyze(context, perf):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=1000,
|
capital_base=1000,
|
||||||
data_frequency='minute',
|
data_frequency='minute',
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='bitfinex',
|
exchange_name='bitfinex',
|
||||||
algo_namespace=NAMESPACE,
|
algo_namespace=NAMESPACE,
|
||||||
base_currency='usd',
|
base_currency='usd',
|
||||||
simulate_orders=True,
|
start=pd.to_datetime('2017-9-22', utc=True),
|
||||||
live=True,
|
end=pd.to_datetime('2017-9-23', utc=True),
|
||||||
)
|
)
|
||||||
# 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),
|
|
||||||
# )
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
)
|
||||||
@@ -37,8 +37,8 @@ def initialize(context):
|
|||||||
context.base_price = None
|
context.base_price = None
|
||||||
context.current_day = None
|
context.current_day = None
|
||||||
|
|
||||||
context.RSI_OVERSOLD = 40
|
context.RSI_OVERSOLD = 60
|
||||||
context.RSI_OVERBOUGHT = 60
|
context.RSI_OVERBOUGHT = 70
|
||||||
context.CANDLE_SIZE = '15T'
|
context.CANDLE_SIZE = '15T'
|
||||||
|
|
||||||
context.start_time = time.time()
|
context.start_time = time.time()
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def handle_data(context, data):
|
|||||||
# Define portfolio optimization parameters
|
# Define portfolio optimization parameters
|
||||||
n_portfolios = 50000
|
n_portfolios = 50000
|
||||||
results_array = np.zeros((3 + context.nassets, n_portfolios))
|
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.random.random(context.nassets)
|
||||||
weights /= np.sum(weights)
|
weights /= np.sum(weights)
|
||||||
w = np.asmatrix(weights)
|
w = np.asmatrix(weights)
|
||||||
@@ -146,4 +146,5 @@ if __name__ == '__main__':
|
|||||||
start=start,
|
start=start,
|
||||||
end=end,
|
end=end,
|
||||||
exchange_name='poloniex',
|
exchange_name='poloniex',
|
||||||
capital_base=100000, )
|
capital_base=100000,
|
||||||
|
base_currency='usdt', )
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ def analyze(context, perf):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
mode = 'backtest'
|
mode = 'live'
|
||||||
|
|
||||||
if mode == 'backtest':
|
if mode == 'backtest':
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ SUPPORTED_EXCHANGES = dict(
|
|||||||
|
|
||||||
|
|
||||||
class CCXT(Exchange):
|
class CCXT(Exchange):
|
||||||
def __init__(self, exchange_name, key, secret, base_currency):
|
def __init__(self, exchange_name, key,
|
||||||
|
secret, password, base_currency):
|
||||||
log.debug(
|
log.debug(
|
||||||
'finding {} in CCXT exchanges:\n{}'.format(
|
'finding {} in CCXT exchanges:\n{}'.format(
|
||||||
exchange_name, ccxt.exchanges
|
exchange_name, ccxt.exchanges
|
||||||
@@ -60,6 +61,7 @@ class CCXT(Exchange):
|
|||||||
self.api = exchange_attr({
|
self.api = exchange_attr({
|
||||||
'apiKey': key,
|
'apiKey': key,
|
||||||
'secret': secret,
|
'secret': secret,
|
||||||
|
'password': password,
|
||||||
})
|
})
|
||||||
self.api.enableRateLimit = True
|
self.api.enableRateLimit = True
|
||||||
|
|
||||||
@@ -188,6 +190,9 @@ class CCXT(Exchange):
|
|||||||
if data_frequency == 'minute' and not freq.endswith('T'):
|
if data_frequency == 'minute' and not freq.endswith('T'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
elif data_frequency == 'hourly' and not freq.endswith('D'):
|
||||||
|
continue
|
||||||
|
|
||||||
elif data_frequency == 'daily' and not freq.endswith('D'):
|
elif data_frequency == 'daily' and not freq.endswith('D'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -426,25 +431,18 @@ class CCXT(Exchange):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if start_dt is None:
|
if start_dt is None:
|
||||||
# TODO: determine why binance is failing
|
if end_dt is None:
|
||||||
if end_dt is None and self.name not in ['binance']:
|
|
||||||
end_dt = pd.Timestamp.utcnow()
|
end_dt = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
if end_dt is not None:
|
dt_range = get_periods_range(
|
||||||
dt_range = get_periods_range(
|
end_dt=end_dt,
|
||||||
end_dt=end_dt,
|
periods=bar_count,
|
||||||
periods=bar_count,
|
freq=freq,
|
||||||
freq=freq,
|
)
|
||||||
)
|
start_dt = dt_range[0]
|
||||||
start_dt = dt_range[0]
|
|
||||||
|
|
||||||
if start_dt is not None:
|
delta = start_dt - get_epoch()
|
||||||
# Convert out start date to a UNIX timestamp, then translate to
|
since = int(delta.total_seconds()) * 1000
|
||||||
# milliseconds
|
|
||||||
delta = start_dt - get_epoch()
|
|
||||||
since = int(delta.total_seconds()) * 1000
|
|
||||||
else:
|
|
||||||
since = None
|
|
||||||
|
|
||||||
candles = dict()
|
candles = dict()
|
||||||
for index, asset in enumerate(assets):
|
for index, asset in enumerate(assets):
|
||||||
@@ -985,7 +983,8 @@ class CCXT(Exchange):
|
|||||||
)
|
)
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
|
|
||||||
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 \
|
order_id = order_param.id \
|
||||||
if isinstance(order_param, Order) else order_param
|
if isinstance(order_param, Order) else order_param
|
||||||
|
|
||||||
@@ -997,7 +996,8 @@ class CCXT(Exchange):
|
|||||||
try:
|
try:
|
||||||
symbol = self.get_symbol(asset_or_symbol) \
|
symbol = self.get_symbol(asset_or_symbol) \
|
||||||
if asset_or_symbol is not None else None
|
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 (ExchangeError, NetworkError) as e:
|
except (ExchangeError, NetworkError) as e:
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
|||||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||||
SymbolNotFoundOnExchange, \
|
SymbolNotFoundOnExchange, \
|
||||||
PricingDataNotLoadedError, \
|
PricingDataNotLoadedError, \
|
||||||
NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError, \
|
NoDataAvailableOnExchange, NoValueForField, \
|
||||||
|
NoCandlesReceivedFromExchange, \
|
||||||
TickerNotFoundError, NotEnoughCashError
|
TickerNotFoundError, NotEnoughCashError
|
||||||
from catalyst.exchange.utils.datetime_utils import get_delta, \
|
from catalyst.exchange.utils.datetime_utils import get_delta, \
|
||||||
get_periods_range, \
|
get_periods_range, \
|
||||||
get_periods, get_start_dt, get_frequency
|
get_periods, get_start_dt, get_frequency, \
|
||||||
|
get_candles_number_from_minutes
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols, \
|
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols, \
|
||||||
resample_history_df, has_bundle
|
resample_history_df, has_bundle, get_candles_df
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('Exchange', level=LOG_LEVEL)
|
log = Logger('Exchange', level=LOG_LEVEL)
|
||||||
@@ -197,12 +199,8 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
assets.append(asset)
|
assets.append(asset)
|
||||||
|
|
||||||
except SymbolNotFoundOnExchange:
|
except SymbolNotFoundOnExchange as e:
|
||||||
log.debug(
|
log.warn(e)
|
||||||
'skipping non-existent market {} {}'.format(
|
|
||||||
self.name, symbol
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False,
|
def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False,
|
||||||
@@ -255,7 +253,8 @@ class Exchange:
|
|||||||
elif data_frequency is not None:
|
elif data_frequency is not None:
|
||||||
applies = (
|
applies = (
|
||||||
(
|
(
|
||||||
data_frequency == 'minute' and a.end_minute is not None)
|
data_frequency == 'minute' and
|
||||||
|
a.end_minute is not None)
|
||||||
or (
|
or (
|
||||||
data_frequency == 'daily' and a.end_daily is not None)
|
data_frequency == 'daily' and a.end_daily is not None)
|
||||||
)
|
)
|
||||||
@@ -502,45 +501,62 @@ class Exchange:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
freq, candle_size, unit, data_frequency = get_frequency(
|
freq, candle_size, unit, data_frequency = get_frequency(
|
||||||
frequency, data_frequency
|
frequency, data_frequency, supported_freqs=['T', 'D', 'H']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 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
|
# The get_history method supports multiple asset
|
||||||
candles = self.get_candles(
|
candles = self.get_candles(
|
||||||
freq=freq,
|
freq=freq,
|
||||||
assets=assets,
|
assets=assets,
|
||||||
bar_count=bar_count,
|
bar_count=requested_bar_count,
|
||||||
end_dt=end_dt if not is_current else None,
|
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:
|
for asset in candles:
|
||||||
first_candle = candles[asset][0]
|
if not candles[asset]:
|
||||||
asset_series = self.get_series_from_candles(
|
raise NoCandlesReceivedFromExchange(
|
||||||
candles=candles[asset],
|
bar_count=requested_bar_count,
|
||||||
start_dt=first_candle['last_traded'],
|
end_dt=end_dt,
|
||||||
end_dt=end_dt,
|
asset=asset,
|
||||||
data_frequency=frequency,
|
exchange=self.name)
|
||||||
field=field,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Checking to make sure that the dates match
|
# for avoiding unnecessary forward fill end_dt is taken back one second
|
||||||
delta = get_delta(candle_size, data_frequency)
|
forward_fill_till_dt = end_dt - timedelta(seconds=1)
|
||||||
adj_end_dt = end_dt - delta
|
|
||||||
last_traded = asset_series.index[-1]
|
|
||||||
|
|
||||||
if last_traded < adj_end_dt:
|
series = get_candles_df(candles=candles,
|
||||||
raise LastCandleTooEarlyError(
|
field=field,
|
||||||
last_traded=last_traded,
|
freq=frequency,
|
||||||
end_dt=adj_end_dt,
|
bar_count=requested_bar_count,
|
||||||
exchange=self.name,
|
end_dt=forward_fill_till_dt)
|
||||||
)
|
|
||||||
|
|
||||||
series[asset] = asset_series
|
# 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 = pd.DataFrame(series)
|
||||||
df.dropna(inplace=True)
|
df.dropna(inplace=True)
|
||||||
|
|
||||||
return df
|
return df.tail(bar_count)
|
||||||
|
|
||||||
def get_history_window_with_bundle(self,
|
def get_history_window_with_bundle(self,
|
||||||
assets,
|
assets,
|
||||||
@@ -588,9 +604,10 @@ class Exchange:
|
|||||||
A dataframe containing the requested data.
|
A dataframe containing the requested data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: this function needs some work, we're currently using it just for benchmark data
|
# TODO: this function needs some work,
|
||||||
|
# we're currently using it just for benchmark data
|
||||||
freq, candle_size, unit, data_frequency = get_frequency(
|
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
|
adj_bar_count = candle_size * bar_count
|
||||||
try:
|
try:
|
||||||
@@ -614,7 +631,7 @@ class Exchange:
|
|||||||
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
||||||
trailing_dt = \
|
trailing_dt = \
|
||||||
series[asset].index[-1] + get_delta(1, data_frequency) \
|
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
|
# The get_history method supports multiple asset
|
||||||
# Use the original frequency to let each api optimize
|
# Use the original frequency to let each api optimize
|
||||||
@@ -664,7 +681,8 @@ class Exchange:
|
|||||||
else:
|
else:
|
||||||
return free, False
|
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
|
Update the portfolio cash and position balances based on the
|
||||||
latest ticker prices.
|
latest ticker prices.
|
||||||
@@ -920,7 +938,8 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cancel_order(self, order_param, symbol_or_asset=None):
|
def cancel_order(self, order_param,
|
||||||
|
symbol_or_asset=None, params={}):
|
||||||
"""Cancel an open order.
|
"""Cancel an open order.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -929,6 +948,7 @@ class Exchange:
|
|||||||
The order_id or order object to cancel.
|
The order_id or order object to cancel.
|
||||||
symbol_or_asset: str|TradingPair
|
symbol_or_asset: str|TradingPair
|
||||||
The catalyst symbol, some exchanges need this
|
The catalyst symbol, some exchanges need this
|
||||||
|
params:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import signal
|
|||||||
import sys
|
import sys
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import isfile, join
|
from os.path import isfile, join, exists
|
||||||
|
|
||||||
import catalyst.protocol as zp
|
import catalyst.protocol as zp
|
||||||
import logbook
|
import logbook
|
||||||
@@ -36,9 +36,11 @@ from catalyst.exchange.utils.exchange_utils import (
|
|||||||
get_algo_folder,
|
get_algo_folder,
|
||||||
get_algo_df,
|
get_algo_df,
|
||||||
save_algo_df,
|
save_algo_df,
|
||||||
|
clear_frame_stats_directory,
|
||||||
|
remove_old_files,
|
||||||
group_assets_by_exchange, )
|
group_assets_by_exchange, )
|
||||||
from catalyst.exchange.utils.stats_utils import get_pretty_stats, stats_to_s3, \
|
from catalyst.exchange.utils.stats_utils import \
|
||||||
stats_to_algo_folder
|
get_pretty_stats, stats_to_s3, stats_to_algo_folder
|
||||||
from catalyst.finance.execution import MarketOrder
|
from catalyst.finance.execution import MarketOrder
|
||||||
from catalyst.finance.performance import PerformanceTracker
|
from catalyst.finance.performance import PerformanceTracker
|
||||||
from catalyst.finance.performance.period import calc_period_stats
|
from catalyst.finance.performance.period import calc_period_stats
|
||||||
@@ -67,8 +69,8 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
|||||||
|
|
||||||
self.current_day = None
|
self.current_day = None
|
||||||
|
|
||||||
if self.simulate_orders is None \
|
if self.simulate_orders is None and \
|
||||||
and self.sim_params.arena == 'backtest':
|
self.sim_params.arena == 'backtest':
|
||||||
self.simulate_orders = True
|
self.simulate_orders = True
|
||||||
|
|
||||||
# Operations with retry features
|
# Operations with retry features
|
||||||
@@ -118,7 +120,7 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
|||||||
# be in-line with CXXT and many exchanges. We'll consider
|
# be in-line with CXXT and many exchanges. We'll consider
|
||||||
# adding more order types in the future.
|
# adding more order types in the future.
|
||||||
if not isinstance(style, ExchangeLimitOrder) or \
|
if not isinstance(style, ExchangeLimitOrder) or \
|
||||||
not isinstance(style, MarketOrder):
|
not isinstance(style, MarketOrder):
|
||||||
raise OrderTypeNotSupported(
|
raise OrderTypeNotSupported(
|
||||||
order_type=style.__class__.__name__
|
order_type=style.__class__.__name__
|
||||||
)
|
)
|
||||||
@@ -161,6 +163,25 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
|||||||
style)
|
style)
|
||||||
return amount, 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):
|
def round_order(self, amount, asset):
|
||||||
"""
|
"""
|
||||||
We need fractions with cryptocurrencies
|
We need fractions with cryptocurrencies
|
||||||
@@ -368,19 +389,35 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self._clock = None
|
self._clock = None
|
||||||
self.frame_stats = list()
|
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 = \
|
# in order to save paper & live files separately
|
||||||
get_algo_df(self.algo_namespace, 'custom_signals_stats')
|
self.mode_name = 'paper' if kwargs['simulate_orders'] else 'live'
|
||||||
|
|
||||||
self.exposure_stats = \
|
self.pnl_stats = get_algo_df(
|
||||||
get_algo_df(self.algo_namespace, 'exposure_stats')
|
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.is_running = True
|
||||||
|
|
||||||
self.stats_minutes = 1
|
self.stats_minutes = 1
|
||||||
|
|
||||||
self._last_orders = []
|
self._last_orders = []
|
||||||
|
self._last_open_orders = []
|
||||||
self.trading_client = None
|
self.trading_client = None
|
||||||
|
|
||||||
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
||||||
@@ -392,6 +429,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
"Exit should be handled by the user.")
|
"Exit should be handled by the user.")
|
||||||
|
|
||||||
def interrupt_algorithm(self):
|
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
|
self.is_running = False
|
||||||
|
|
||||||
if self._analyze is None:
|
if self._analyze is None:
|
||||||
@@ -401,21 +451,31 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
log.info('Exiting the algorithm. Calling `analyze()` '
|
log.info('Exiting the algorithm. Calling `analyze()` '
|
||||||
'before exiting the algorithm.')
|
'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)
|
algo_folder = get_algo_folder(self.algo_namespace)
|
||||||
folder = join(algo_folder, 'daily_performance')
|
folder = join(algo_folder, 'frame_stats')
|
||||||
files = [f for f in listdir(folder) if isfile(join(folder, f))]
|
|
||||||
|
|
||||||
daily_perf_list = []
|
if exists(folder):
|
||||||
for item in files:
|
files = [f for f in listdir(folder) if isfile(join(folder, f))]
|
||||||
filename = join(folder, item)
|
|
||||||
|
|
||||||
with open(filename, 'rb') as handle:
|
period_stats_list = []
|
||||||
perf_period = pickle.load(handle)
|
for item in files:
|
||||||
perf_period_dict = perf_period.to_dict()
|
filename = join(folder, item)
|
||||||
daily_perf_list.append(perf_period_dict)
|
|
||||||
|
|
||||||
stats = pd.DataFrame(daily_perf_list)
|
with open(filename, 'rb') as handle:
|
||||||
stats.set_index('period_close', drop=False, inplace=True)
|
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)
|
self.analyze(stats)
|
||||||
|
|
||||||
@@ -484,7 +544,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
"""
|
"""
|
||||||
self.state = get_algo_object(
|
self.state = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='context.state',
|
key='context.state_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if self.state is None:
|
if self.state is None:
|
||||||
self.state = {}
|
self.state = {}
|
||||||
@@ -507,7 +567,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
# Unpacking the perf_tracker and positions if available
|
# Unpacking the perf_tracker and positions if available
|
||||||
cum_perf = get_algo_object(
|
cum_perf = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='cumulative_performance',
|
key='cumulative_performance_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if cum_perf is not None:
|
if cum_perf is not None:
|
||||||
tracker.cumulative_performance = cum_perf
|
tracker.cumulative_performance = cum_perf
|
||||||
@@ -518,7 +578,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
todays_perf = get_algo_object(
|
todays_perf = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key=today.strftime('%Y-%m-%d'),
|
key=today.strftime('%Y-%m-%d'),
|
||||||
rel_path='daily_performance',
|
rel_path='daily_performance_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if todays_perf is not None:
|
if todays_perf is not None:
|
||||||
# Ensure single common position tracker
|
# Ensure single common position tracker
|
||||||
@@ -601,8 +661,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
if base_currency is None:
|
if base_currency is None:
|
||||||
base_currency = exchange.base_currency
|
base_currency = exchange.base_currency
|
||||||
|
|
||||||
# Don't check the cash if there are open orders. This could
|
|
||||||
# results in false positives.
|
|
||||||
orders = []
|
orders = []
|
||||||
for asset in self.blotter.open_orders:
|
for asset in self.blotter.open_orders:
|
||||||
asset_orders = self.blotter.open_orders[asset]
|
asset_orders = self.blotter.open_orders[asset]
|
||||||
@@ -657,7 +715,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
self.pnl_stats = pd.concat([self.pnl_stats, df])
|
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):
|
def add_custom_signals_stats(self, period_stats):
|
||||||
"""
|
"""
|
||||||
@@ -678,8 +740,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
|
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
|
||||||
|
|
||||||
save_algo_df(self.algo_namespace, 'custom_signals_stats',
|
save_algo_df(
|
||||||
self.custom_signals_stats)
|
self.algo_namespace,
|
||||||
|
'custom_signals_stats_{}'.format(self.mode_name),
|
||||||
|
self.custom_signals_stats,
|
||||||
|
)
|
||||||
|
|
||||||
def add_exposure_stats(self, period_stats):
|
def add_exposure_stats(self, period_stats):
|
||||||
"""
|
"""
|
||||||
@@ -706,9 +771,43 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.exposure_stats = pd.concat([self.exposure_stats, df])
|
self.exposure_stats = pd.concat([self.exposure_stats, df])
|
||||||
|
|
||||||
save_algo_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):
|
def handle_data(self, data):
|
||||||
"""
|
"""
|
||||||
Wrapper around the handle_data method of each algo.
|
Wrapper around the handle_data method of each algo.
|
||||||
@@ -728,15 +827,20 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
# Resetting the frame stats every day to minimize memory footprint
|
# Resetting the frame stats every day to minimize memory footprint
|
||||||
today = data.current_dt.floor('1D')
|
today = data.current_dt.floor('1D')
|
||||||
if self.current_day is not None and today > self.current_day:
|
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
|
self.performance_needs_update = False
|
||||||
orders = list(self.perf_tracker.todays_performance.orders_by_id.keys())
|
last_orders_list = list(self.blotter.orders.keys())
|
||||||
if orders != self._last_orders:
|
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.performance_needs_update = True
|
||||||
|
|
||||||
# Saving current orders to detect changes in the next frame
|
# Saving current order positions
|
||||||
self._last_orders = copy.deepcopy(orders)
|
# 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:
|
if self.performance_needs_update:
|
||||||
self.perf_tracker.update_performance()
|
self.perf_tracker.update_performance()
|
||||||
@@ -778,7 +882,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
log.debug('saving cumulative performance object')
|
log.debug('saving cumulative performance object')
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='cumulative_performance',
|
key='cumulative_performance_{}'.format(self.mode_name),
|
||||||
obj=self.perf_tracker.cumulative_performance,
|
obj=self.perf_tracker.cumulative_performance,
|
||||||
)
|
)
|
||||||
log.debug('saving todays performance object')
|
log.debug('saving todays performance object')
|
||||||
@@ -786,12 +890,12 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key=today.strftime('%Y-%m-%d'),
|
key=today.strftime('%Y-%m-%d'),
|
||||||
obj=self.perf_tracker.todays_performance,
|
obj=self.perf_tracker.todays_performance,
|
||||||
rel_path='daily_performance'
|
rel_path='daily_performance_{}'.format(self.mode_name)
|
||||||
)
|
)
|
||||||
log.debug('saving context.state object')
|
log.debug('saving context.state object')
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='context.state',
|
key='context.state_{}'.format(self.mode_name),
|
||||||
obj=self.state)
|
obj=self.state)
|
||||||
|
|
||||||
def _process_stats(self, data):
|
def _process_stats(self, data):
|
||||||
@@ -808,6 +912,8 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
# Saving the last hour in memory
|
# Saving the last hour in memory
|
||||||
self.frame_stats.append(frame_stats)
|
self.frame_stats.append(frame_stats)
|
||||||
|
|
||||||
|
# creating and saving the pnl_stats into the local
|
||||||
|
# directory
|
||||||
self.add_pnl_stats(frame_stats)
|
self.add_pnl_stats(frame_stats)
|
||||||
if self.recorded_vars:
|
if self.recorded_vars:
|
||||||
self.add_custom_signals_stats(frame_stats)
|
self.add_custom_signals_stats(frame_stats)
|
||||||
@@ -845,6 +951,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
csv_bytes = stats_to_algo_folder(
|
csv_bytes = stats_to_algo_folder(
|
||||||
stats=self.frame_stats,
|
stats=self.frame_stats,
|
||||||
algo_namespace=self.algo_namespace,
|
algo_namespace=self.algo_namespace,
|
||||||
|
folder_name='stats_{}'.format(self.mode_name),
|
||||||
recorded_cols=recorded_cols,
|
recorded_cols=recorded_cols,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -949,13 +1056,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
args=(order_id,))
|
args=(order_id,))
|
||||||
|
|
||||||
@api_method
|
@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.
|
"""Cancel an open order.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
order_param : str or Order
|
order_param : str or Order
|
||||||
The order_id or order object to cancel.
|
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]
|
exchange = self.exchanges[exchange_name]
|
||||||
|
|
||||||
@@ -969,4 +1082,4 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
sleeptime=self.attempts['retry_sleeptime'],
|
sleeptime=self.attempts['retry_sleeptime'],
|
||||||
retry_exceptions=(ExchangeRequestError,),
|
retry_exceptions=(ExchangeRequestError,),
|
||||||
cleanup=lambda: log.warn('cancelling order again.'),
|
cleanup=lambda: log.warn('cancelling order again.'),
|
||||||
args=(order_id,))
|
args=(order_id, symbol, params))
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class TradingPairFeeSchedule(CommissionModel):
|
|||||||
multiplier = maker \
|
multiplier = maker \
|
||||||
if ((order.amount > 0 and order.limit < transaction.price)
|
if ((order.amount > 0 and order.limit < transaction.price)
|
||||||
or (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
|
fee = cost * multiplier
|
||||||
return fee
|
return fee
|
||||||
@@ -238,9 +238,12 @@ class ExchangeBlotter(Blotter):
|
|||||||
else:
|
else:
|
||||||
delta = pd.Timestamp.utcnow() - order.dt
|
delta = pd.Timestamp.utcnow() - order.dt
|
||||||
log.info(
|
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,
|
order_id=order.id,
|
||||||
delta=delta
|
delta=delta,
|
||||||
|
symbol=order.asset.symbol,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
|||||||
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
|
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
|
||||||
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
|
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
|
||||||
get_bcolz_chunk, get_df_from_arrays, get_assets
|
get_bcolz_chunk, get_df_from_arrays, get_assets
|
||||||
from catalyst.exchange.utils.datetime_utils import get_delta, get_start_dt, \
|
from catalyst.exchange.utils.datetime_utils import get_start_dt, \
|
||||||
get_period_label, get_month_start_end, get_year_start_end
|
get_period_label, get_month_start_end, get_year_start_end
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder, \
|
from catalyst.exchange.utils.exchange_utils import get_exchange_folder, \
|
||||||
save_exchange_symbols, mixin_market_params, get_catalyst_symbol
|
save_exchange_symbols, mixin_market_params, get_catalyst_symbol
|
||||||
@@ -232,12 +232,12 @@ class ExchangeBundle:
|
|||||||
|
|
||||||
problem = '{name} ({start_dt} to {end_dt}) has empty ' \
|
problem = '{name} ({start_dt} to {end_dt}) has empty ' \
|
||||||
'periods: {dates}'.format(
|
'periods: {dates}'.format(
|
||||||
name=asset.symbol,
|
name=asset.symbol,
|
||||||
start_dt=asset.start_date.strftime(
|
start_dt=asset.start_date.strftime(
|
||||||
DATE_TIME_FORMAT),
|
DATE_TIME_FORMAT),
|
||||||
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
|
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
|
||||||
dates=[date.strftime(
|
dates=[date.strftime(
|
||||||
DATE_TIME_FORMAT) for date in dates])
|
DATE_TIME_FORMAT) for date in dates])
|
||||||
|
|
||||||
if empty_rows_behavior == 'warn':
|
if empty_rows_behavior == 'warn':
|
||||||
log.warn(problem)
|
log.warn(problem)
|
||||||
@@ -286,12 +286,12 @@ class ExchangeBundle:
|
|||||||
|
|
||||||
problem = '{name} ({start_dt} to {end_dt}) has {threshold} ' \
|
problem = '{name} ({start_dt} to {end_dt}) has {threshold} ' \
|
||||||
'identical close values on: {dates}'.format(
|
'identical close values on: {dates}'.format(
|
||||||
name=asset.symbol,
|
name=asset.symbol,
|
||||||
start_dt=asset.start_date.strftime(DATE_TIME_FORMAT),
|
start_dt=asset.start_date.strftime(DATE_TIME_FORMAT),
|
||||||
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
|
end_dt=end_dt.strftime(DATE_TIME_FORMAT),
|
||||||
threshold=threshold,
|
threshold=threshold,
|
||||||
dates=[pd.to_datetime(date).strftime(DATE_TIME_FORMAT)
|
dates=[pd.to_datetime(date).strftime(DATE_TIME_FORMAT)
|
||||||
for date in dates])
|
for date in dates])
|
||||||
|
|
||||||
problems.append(problem)
|
problems.append(problem)
|
||||||
|
|
||||||
@@ -598,8 +598,9 @@ class ExchangeBundle:
|
|||||||
# we want to give an end_date far in time
|
# we want to give an end_date far in time
|
||||||
writer = self.get_writer(start_dt, end_dt, data_frequency)
|
writer = self.get_writer(start_dt, end_dt, data_frequency)
|
||||||
if show_breakdown:
|
if show_breakdown:
|
||||||
for asset in chunks:
|
if chunks:
|
||||||
with maybe_show_progress(
|
for asset in chunks:
|
||||||
|
with maybe_show_progress(
|
||||||
chunks[asset],
|
chunks[asset],
|
||||||
show_progress,
|
show_progress,
|
||||||
label='Ingesting {frequency} price data for '
|
label='Ingesting {frequency} price data for '
|
||||||
@@ -607,6 +608,30 @@ class ExchangeBundle:
|
|||||||
exchange=self.exchange_name,
|
exchange=self.exchange_name,
|
||||||
frequency=data_frequency,
|
frequency=data_frequency,
|
||||||
symbol=asset.symbol
|
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:
|
)) as it:
|
||||||
for chunk in it:
|
for chunk in it:
|
||||||
problems += self.ingest_ctable(
|
problems += self.ingest_ctable(
|
||||||
@@ -617,30 +642,6 @@ class ExchangeBundle:
|
|||||||
empty_rows_behavior='strip',
|
empty_rows_behavior='strip',
|
||||||
cleanup=True
|
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:
|
if show_report and len(problems) > 0:
|
||||||
log.info('problems during ingestion:{}\n'.format(
|
log.info('problems during ingestion:{}\n'.format(
|
||||||
@@ -830,7 +831,6 @@ class ExchangeBundle:
|
|||||||
field,
|
field,
|
||||||
data_frequency,
|
data_frequency,
|
||||||
algo_end_dt=None,
|
algo_end_dt=None,
|
||||||
trailing_bar_count=None,
|
|
||||||
force_auto_ingest=False
|
force_auto_ingest=False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -858,7 +858,6 @@ class ExchangeBundle:
|
|||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
trailing_bar_count=trailing_bar_count,
|
|
||||||
)
|
)
|
||||||
return pd.DataFrame(series)
|
return pd.DataFrame(series)
|
||||||
|
|
||||||
@@ -887,7 +886,6 @@ class ExchangeBundle:
|
|||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
reset_reader=True,
|
reset_reader=True,
|
||||||
trailing_bar_count=trailing_bar_count,
|
|
||||||
)
|
)
|
||||||
return series
|
return series
|
||||||
|
|
||||||
@@ -898,7 +896,6 @@ class ExchangeBundle:
|
|||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
trailing_bar_count=trailing_bar_count,
|
|
||||||
)
|
)
|
||||||
return pd.DataFrame(series)
|
return pd.DataFrame(series)
|
||||||
|
|
||||||
@@ -962,12 +959,7 @@ class ExchangeBundle:
|
|||||||
bar_count,
|
bar_count,
|
||||||
field,
|
field,
|
||||||
data_frequency,
|
data_frequency,
|
||||||
trailing_bar_count=None,
|
|
||||||
reset_reader=False):
|
reset_reader=False):
|
||||||
if trailing_bar_count:
|
|
||||||
delta = get_delta(trailing_bar_count, data_frequency)
|
|
||||||
end_dt += delta
|
|
||||||
|
|
||||||
start_dt = get_start_dt(end_dt, bar_count, data_frequency, False)
|
start_dt = get_start_dt(end_dt, bar_count, data_frequency, False)
|
||||||
start_dt, _ = self.get_adj_dates(
|
start_dt, _ = self.get_adj_dates(
|
||||||
start_dt, end_dt, assets, data_frequency
|
start_dt, end_dt, assets, data_frequency
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
|||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
PricingDataNotLoadedError)
|
PricingDataNotLoadedError)
|
||||||
from catalyst.exchange.utils.exchange_utils import resample_history_df, group_assets_by_exchange
|
from catalyst.exchange.utils.exchange_utils import resample_history_df, \
|
||||||
from catalyst.exchange.utils.datetime_utils import get_frequency
|
group_assets_by_exchange
|
||||||
|
from catalyst.exchange.utils.datetime_utils import get_frequency, get_start_dt
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from redo import retry
|
from redo import retry
|
||||||
|
|
||||||
@@ -295,10 +296,9 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
|
|||||||
bundle = self.exchange_bundles[exchange_name] # type: ExchangeBundle
|
bundle = self.exchange_bundles[exchange_name] # type: ExchangeBundle
|
||||||
|
|
||||||
freq, candle_size, unit, adj_data_frequency = get_frequency(
|
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
|
adj_bar_count = candle_size * bar_count
|
||||||
trailing_bar_count = candle_size - 1
|
|
||||||
|
|
||||||
if data_frequency == 'minute' and adj_data_frequency == 'daily':
|
if data_frequency == 'minute' and adj_data_frequency == 'daily':
|
||||||
end_dt = end_dt.floor('1D')
|
end_dt = end_dt.floor('1D')
|
||||||
@@ -310,10 +310,10 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
|
|||||||
field=field,
|
field=field,
|
||||||
data_frequency=adj_data_frequency,
|
data_frequency=adj_data_frequency,
|
||||||
algo_end_dt=self._last_available_session,
|
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
|
return df
|
||||||
|
|
||||||
def get_exchange_spot_value(self,
|
def get_exchange_spot_value(self,
|
||||||
|
|||||||
@@ -322,3 +322,10 @@ class BalanceTooLowError(ZiplineError):
|
|||||||
'add positions to hold a free amount greater than {amount}, or clean '
|
'add positions to hold a free amount greater than {amount}, or clean '
|
||||||
'the state of this algo and restart.'
|
'the state of this algo and restart.'
|
||||||
).strip()
|
).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,4 +1,5 @@
|
|||||||
import calendar
|
import calendar
|
||||||
|
import math
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ def get_periods_range(freq, start_dt=None, end_dt=None, periods=None):
|
|||||||
adj_periods = periods * unit_periods
|
adj_periods = periods * unit_periods
|
||||||
|
|
||||||
# TODO: standardize time aliases to avoid any mapping
|
# TODO: standardize time aliases to avoid any mapping
|
||||||
unit = 'd' if unit == 'D' else 'm'
|
unit = 'd' if unit == 'D' else 'h' if unit == 'H' else 'm'
|
||||||
delta = pd.Timedelta(adj_periods, unit)
|
delta = pd.Timedelta(adj_periods, unit)
|
||||||
|
|
||||||
if start_dt is not None:
|
if start_dt is not None:
|
||||||
@@ -248,9 +249,12 @@ def get_year_start_end(dt, first_day=None, last_day=None):
|
|||||||
return year_start, year_end
|
return year_start, year_end
|
||||||
|
|
||||||
|
|
||||||
def get_frequency(freq, data_frequency=None):
|
def get_frequency(freq, data_frequency=None, supported_freqs=['D', 'H', 'T']):
|
||||||
"""
|
"""
|
||||||
Get the frequency parameters.
|
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
|
Notes
|
||||||
-----
|
-----
|
||||||
@@ -302,16 +306,17 @@ def get_frequency(freq, data_frequency=None):
|
|||||||
elif unit.lower() == 'm' or unit == 'T':
|
elif unit.lower() == 'm' or unit == 'T':
|
||||||
unit = 'T'
|
unit = 'T'
|
||||||
alias = '{}T'.format(candle_size)
|
alias = '{}T'.format(candle_size)
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
if data_frequency == 'daily':
|
elif unit.lower() == 'h':
|
||||||
data_frequency = 'minute'
|
data_frequency = 'minute'
|
||||||
|
|
||||||
# elif unit.lower() == 'h':
|
if 'H' in supported_freqs:
|
||||||
# candle_size = candle_size * 60
|
unit = 'H'
|
||||||
#
|
alias = '{}H'.format(candle_size)
|
||||||
# alias = '{}T'.format(candle_size)
|
else:
|
||||||
# if data_frequency == 'daily':
|
candle_size = candle_size * 60
|
||||||
# data_frequency = 'minute'
|
alias = '{}T'.format(candle_size)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise InvalidHistoryFrequencyAlias(freq=freq)
|
raise InvalidHistoryFrequencyAlias(freq=freq)
|
||||||
@@ -325,3 +330,33 @@ def from_ms_timestamp(ms):
|
|||||||
|
|
||||||
def get_epoch():
|
def get_epoch():
|
||||||
return pd.to_datetime('1970-1-1', utc=True)
|
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))
|
||||||
|
|||||||
@@ -126,11 +126,11 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None):
|
|||||||
filename = get_exchange_symbols_filename(exchange_name, is_local)
|
filename = get_exchange_symbols_filename(exchange_name, is_local)
|
||||||
|
|
||||||
if not is_local and (not os.path.isfile(filename) or pd.Timedelta(
|
if not is_local and (not os.path.isfile(filename) or pd.Timedelta(
|
||||||
pd.Timestamp('now', tz='UTC') - last_modified_time(
|
pd.Timestamp('now', tz='UTC') - last_modified_time(
|
||||||
filename)).days > 1):
|
filename)).days > 1):
|
||||||
try:
|
try:
|
||||||
download_exchange_symbols(exchange_name, environ)
|
download_exchange_symbols(exchange_name, environ)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
@@ -273,6 +273,7 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None, how='pickle'):
|
|||||||
key: str
|
key: str
|
||||||
environ:
|
environ:
|
||||||
rel_path: str
|
rel_path: str
|
||||||
|
how: str
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -316,6 +317,7 @@ def save_algo_object(algo_name, key, obj, environ=None, rel_path=None,
|
|||||||
obj: Object
|
obj: Object
|
||||||
environ:
|
environ:
|
||||||
rel_path: str
|
rel_path: str
|
||||||
|
how: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
folder = get_algo_folder(algo_name, environ)
|
folder = get_algo_folder(algo_name, environ)
|
||||||
@@ -392,6 +394,71 @@ def save_algo_df(algo_name, key, df, environ=None, rel_path=None):
|
|||||||
df.to_csv(handle, encoding='UTF_8')
|
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):
|
def get_exchange_minute_writer_root(exchange_name, environ=None):
|
||||||
"""
|
"""
|
||||||
The minute writer folder for the exchange.
|
The minute writer folder for the exchange.
|
||||||
@@ -512,7 +579,7 @@ def get_common_assets(exchanges):
|
|||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
|
||||||
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.
|
Resample the OHCLV DataFrame using the specified frequency.
|
||||||
|
|
||||||
@@ -540,7 +607,16 @@ def resample_history_df(df, freq, field):
|
|||||||
else:
|
else:
|
||||||
raise ValueError('Invalid field.')
|
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
|
return resampled_df
|
||||||
|
|
||||||
|
|
||||||
@@ -566,8 +642,9 @@ def mixin_market_params(exchange_name, params, market):
|
|||||||
params['maker'] = 0.001
|
params['maker'] = 0.001
|
||||||
params['taker'] = 0.002
|
params['taker'] = 0.002
|
||||||
|
|
||||||
elif 'maker' in market and 'taker' in market \
|
elif 'maker' in market and 'taker' in market and \
|
||||||
and market['maker'] is not None and market['taker'] is not None:
|
market['maker'] is not None and market['taker'] is not None:
|
||||||
|
|
||||||
params['maker'] = market['maker']
|
params['maker'] = market['maker']
|
||||||
params['taker'] = market['taker']
|
params['taker'] = market['taker']
|
||||||
|
|
||||||
@@ -639,23 +716,36 @@ def save_asset_data(folder, df, decimals=8):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_candles_df(candles, field, freq, bar_count, end_dt,
|
def forward_fill_df_if_needed(df, periods):
|
||||||
previous_value=None):
|
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()
|
all_series = dict()
|
||||||
|
|
||||||
for asset in candles:
|
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]]
|
all_series[asset] = pd.Series(asset_df[field])
|
||||||
values = [candle[field] for candle in candles[asset]]
|
|
||||||
series = pd.Series(values, index=dates)
|
|
||||||
|
|
||||||
series = series.reindex(
|
|
||||||
periods,
|
|
||||||
method='ffill',
|
|
||||||
fill_value=previous_value,
|
|
||||||
)
|
|
||||||
series.sort_index(inplace=True)
|
|
||||||
all_series[asset] = series
|
|
||||||
|
|
||||||
df = pd.DataFrame(all_series)
|
df = pd.DataFrame(all_series)
|
||||||
df.dropna(inplace=True)
|
df.dropna(inplace=True)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
|||||||
exchange_name=exchange_name,
|
exchange_name=exchange_name,
|
||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
secret=exchange_auth['secret'],
|
secret=exchange_auth['secret'],
|
||||||
|
password=exchange_auth['password'] if 'password'
|
||||||
|
in exchange_auth.keys() else '',
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
)
|
)
|
||||||
exchange_cache[key] = exchange
|
exchange_cache[key] = exchange
|
||||||
|
|||||||
@@ -396,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.
|
Saves the performance stats to the algo local folder.
|
||||||
|
|
||||||
@@ -404,6 +405,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
|||||||
----------
|
----------
|
||||||
stats: list[Object]
|
stats: list[Object]
|
||||||
algo_namespace: str
|
algo_namespace: str
|
||||||
|
folder_name: str
|
||||||
recorded_cols: list[str]
|
recorded_cols: list[str]
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -416,7 +418,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
|||||||
timestr = time.strftime('%Y%m%d')
|
timestr = time.strftime('%Y%m%d')
|
||||||
folder = get_algo_folder(algo_namespace)
|
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)
|
ensure_directory(stats_folder)
|
||||||
|
|
||||||
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
|
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
|
||||||
|
|||||||
@@ -95,11 +95,24 @@ class TradingEnvironment(object):
|
|||||||
if not trading_calendar:
|
if not trading_calendar:
|
||||||
trading_calendar = get_calendar("NYSE")
|
trading_calendar = get_calendar("NYSE")
|
||||||
|
|
||||||
self.benchmark_returns, self.treasury_curves = load(
|
# todo: uncomment and add a well defined benchmark
|
||||||
trading_calendar.day,
|
# self.benchmark_returns, self.treasury_curves = load(
|
||||||
trading_calendar.schedule.index,
|
# trading_calendar.day,
|
||||||
self.bm_symbol,
|
# 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
|
self.exchange_tz = exchange_tz
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0x7fAec9aaE31BE428DeAAE1be8195dF609079Fd10
|
0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
0x3985f5de8fddf2e8f7705cd360b498bf35ebfbc4
|
0xa64927358a82254be92eb1f1cb01de68d1787004
|
||||||
@@ -7,6 +7,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
import bcolz
|
import bcolz
|
||||||
import logbook
|
import logbook
|
||||||
@@ -23,7 +24,7 @@ from catalyst.exchange.utils.stats_utils import set_print_settings
|
|||||||
from catalyst.marketplace.marketplace_errors import (
|
from catalyst.marketplace.marketplace_errors import (
|
||||||
MarketplacePubAddressEmpty, MarketplaceDatasetNotFound,
|
MarketplacePubAddressEmpty, MarketplaceDatasetNotFound,
|
||||||
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
|
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
|
||||||
MarketplaceNoCSVFiles)
|
MarketplaceNoCSVFiles, MarketplaceRequiresPython3)
|
||||||
from catalyst.marketplace.utils.auth_utils import get_key_secret, \
|
from catalyst.marketplace.utils.auth_utils import get_key_secret, \
|
||||||
get_signed_headers
|
get_signed_headers
|
||||||
from catalyst.marketplace.utils.bundle_utils import merge_bundles
|
from catalyst.marketplace.utils.bundle_utils import merge_bundles
|
||||||
@@ -32,6 +33,7 @@ from catalyst.marketplace.utils.eth_utils import bin_hex, from_grains, \
|
|||||||
from catalyst.marketplace.utils.path_utils import get_bundle_folder, \
|
from catalyst.marketplace.utils.path_utils import get_bundle_folder, \
|
||||||
get_data_source_folder, get_marketplace_folder, \
|
get_data_source_folder, get_marketplace_folder, \
|
||||||
get_user_pubaddr, get_temp_bundles_folder, extract_bundle
|
get_user_pubaddr, get_temp_bundles_folder, extract_bundle
|
||||||
|
from catalyst.utils.paths import ensure_directory
|
||||||
|
|
||||||
if sys.version_info.major < 3:
|
if sys.version_info.major < 3:
|
||||||
import urllib
|
import urllib
|
||||||
@@ -44,7 +46,10 @@ log = logbook.Logger('Marketplace', level=LOG_LEVEL)
|
|||||||
class Marketplace:
|
class Marketplace:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
global Web3
|
global Web3
|
||||||
from web3 import Web3, HTTPProvider
|
try:
|
||||||
|
from web3 import Web3, HTTPProvider
|
||||||
|
except ImportError:
|
||||||
|
raise MarketplaceRequiresPython3()
|
||||||
|
|
||||||
self.addresses = get_user_pubaddr()
|
self.addresses = get_user_pubaddr()
|
||||||
|
|
||||||
@@ -60,10 +65,14 @@ class Marketplace:
|
|||||||
contract_url = urllib.urlopen(MARKETPLACE_CONTRACT)
|
contract_url = urllib.urlopen(MARKETPLACE_CONTRACT)
|
||||||
|
|
||||||
self.mkt_contract_address = Web3.toChecksumAddress(
|
self.mkt_contract_address = Web3.toChecksumAddress(
|
||||||
contract_url.readline().strip())
|
contract_url.readline().decode(
|
||||||
|
contract_url.info().get_content_charset()).strip())
|
||||||
|
|
||||||
abi_url = urllib.urlopen(MARKETPLACE_CONTRACT_ABI)
|
abi_url = urllib.urlopen(MARKETPLACE_CONTRACT_ABI)
|
||||||
abi = json.load(abi_url)
|
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 = self.web3.eth.contract(
|
||||||
self.mkt_contract_address,
|
self.mkt_contract_address,
|
||||||
@@ -73,10 +82,14 @@ class Marketplace:
|
|||||||
contract_url = urllib.urlopen(ENIGMA_CONTRACT)
|
contract_url = urllib.urlopen(ENIGMA_CONTRACT)
|
||||||
|
|
||||||
self.eng_contract_address = Web3.toChecksumAddress(
|
self.eng_contract_address = Web3.toChecksumAddress(
|
||||||
contract_url.readline().strip())
|
contract_url.readline().decode(
|
||||||
|
contract_url.info().get_content_charset()).strip())
|
||||||
|
|
||||||
abi_url = urllib.urlopen(ENIGMA_CONTRACT_ABI)
|
abi_url = urllib.urlopen(ENIGMA_CONTRACT_ABI)
|
||||||
abi = json.load(abi_url)
|
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 = self.web3.eth.contract(
|
||||||
self.eng_contract_address,
|
self.eng_contract_address,
|
||||||
@@ -119,9 +132,10 @@ class Marketplace:
|
|||||||
else:
|
else:
|
||||||
while True:
|
while True:
|
||||||
for i in range(0, len(self.addresses)):
|
for i in range(0, len(self.addresses)):
|
||||||
print('{}\t{}\t{}'.format(
|
print('{}\t{}\t{}\t{}'.format(
|
||||||
i,
|
i,
|
||||||
self.addresses[i]['pubAddr'],
|
self.addresses[i]['pubAddr'],
|
||||||
|
self.addresses[i]['wallet'].ljust(10),
|
||||||
self.addresses[i]['desc'])
|
self.addresses[i]['desc'])
|
||||||
)
|
)
|
||||||
address_i = int(input('Choose your address associated with '
|
address_i = int(input('Choose your address associated with '
|
||||||
@@ -136,10 +150,10 @@ class Marketplace:
|
|||||||
|
|
||||||
return address, address_i
|
return address, address_i
|
||||||
|
|
||||||
def sign_transaction(self, from_address, tx):
|
def sign_transaction(self, tx):
|
||||||
|
|
||||||
print('\nVisit https://www.myetherwallet.com/#offline-transaction and '
|
url = 'https://www.mycrypto.com/#offline-transaction'
|
||||||
'enter the following parameters:\n\n'
|
print('\nVisit {url} and enter the following parameters:\n\n'
|
||||||
'From Address:\t\t{_from}\n'
|
'From Address:\t\t{_from}\n'
|
||||||
'\n\tClick the "Generate Information" button\n\n'
|
'\n\tClick the "Generate Information" button\n\n'
|
||||||
'To Address:\t\t{to}\n'
|
'To Address:\t\t{to}\n'
|
||||||
@@ -148,13 +162,16 @@ class Marketplace:
|
|||||||
'Gas Price:\t\t[Accept the default value]\n'
|
'Gas Price:\t\t[Accept the default value]\n'
|
||||||
'Nonce:\t\t\t{nonce}\n'
|
'Nonce:\t\t\t{nonce}\n'
|
||||||
'Data:\t\t\t{data}\n'.format(
|
'Data:\t\t\t{data}\n'.format(
|
||||||
_from=from_address,
|
url=url,
|
||||||
to=tx['to'],
|
_from=tx['from'],
|
||||||
value=tx['value'],
|
to=tx['to'],
|
||||||
gas=tx['gas'],
|
value=tx['value'],
|
||||||
nonce=tx['nonce'],
|
gas=tx['gas'],
|
||||||
data=tx['data'], )
|
nonce=tx['nonce'],
|
||||||
)
|
data=tx['data'], )
|
||||||
|
)
|
||||||
|
|
||||||
|
webbrowser.open_new(url)
|
||||||
|
|
||||||
signed_tx = input('Copy and Paste the "Signed Transaction" '
|
signed_tx = input('Copy and Paste the "Signed Transaction" '
|
||||||
'field here:\n')
|
'field here:\n')
|
||||||
@@ -167,16 +184,17 @@ class Marketplace:
|
|||||||
def check_transaction(self, tx_hash):
|
def check_transaction(self, tx_hash):
|
||||||
|
|
||||||
if 'ropsten' in ETH_REMOTE_NODE:
|
if 'ropsten' in ETH_REMOTE_NODE:
|
||||||
etherscan = 'https://ropsten.etherscan.io/tx/{}'.format(
|
etherscan = 'https://ropsten.etherscan.io/tx/'
|
||||||
tx_hash)
|
elif 'rinkeby' in ETH_REMOTE_NODE:
|
||||||
|
etherscan = 'https://rinkeby.etherscan.io/tx/'
|
||||||
else:
|
else:
|
||||||
etherscan = 'https://etherscan.io/tx/{}'.format(tx_hash)
|
etherscan = 'https://etherscan.io/tx/'
|
||||||
|
etherscan = '{}{}'.format(etherscan, tx_hash)
|
||||||
|
|
||||||
print('\nYou can check the outcome of your transaction here:\n'
|
print('\nYou can check the outcome of your transaction here:\n'
|
||||||
'{}\n\n'.format(etherscan))
|
'{}\n\n'.format(etherscan))
|
||||||
|
|
||||||
def list(self):
|
def _list(self):
|
||||||
|
|
||||||
data_sources = self.mkt_contract.functions.getAllProviders().call()
|
data_sources = self.mkt_contract.functions.getAllProviders().call()
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@@ -188,15 +206,44 @@ class Marketplace:
|
|||||||
dataset=self.to_text(data_source)
|
dataset=self.to_text(data_source)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
return pd.DataFrame(data)
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
df = self._list()
|
||||||
|
|
||||||
df = pd.DataFrame(data)
|
|
||||||
set_print_settings()
|
set_print_settings()
|
||||||
if df.empty:
|
if df.empty:
|
||||||
print('There are no datasets available yet.')
|
print('There are no datasets available yet.')
|
||||||
else:
|
else:
|
||||||
print(df)
|
print(df)
|
||||||
|
|
||||||
def subscribe(self, dataset):
|
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()
|
dataset = dataset.lower()
|
||||||
|
|
||||||
@@ -259,14 +306,14 @@ class Marketplace:
|
|||||||
'buy: {} ENG. Get enough ENG to cover the costs of the '
|
'buy: {} ENG. Get enough ENG to cover the costs of the '
|
||||||
'monthly\nsubscription for what you are trying to buy, '
|
'monthly\nsubscription for what you are trying to buy, '
|
||||||
'and try again.'.format(
|
'and try again.'.format(
|
||||||
address, from_grains(balance), price))
|
address, from_grains(balance), price))
|
||||||
return
|
return
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
agree_pay = input('Please confirm that you agree to pay {} ENG '
|
agree_pay = input('Please confirm that you agree to pay {} ENG '
|
||||||
'for a monthly subscription to the dataset "{}" '
|
'for a monthly subscription to the dataset "{}" '
|
||||||
'starting today. [default: Y] '.format(
|
'starting today. [default: Y] '.format(
|
||||||
price, dataset)) or 'y'
|
price, dataset)) or 'y'
|
||||||
if agree_pay.lower() not in ('y', 'n'):
|
if agree_pay.lower() not in ('y', 'n'):
|
||||||
print("Please answer Y or N.")
|
print("Please answer Y or N.")
|
||||||
else:
|
else:
|
||||||
@@ -287,13 +334,11 @@ class Marketplace:
|
|||||||
self.mkt_contract_address,
|
self.mkt_contract_address,
|
||||||
grains,
|
grains,
|
||||||
).buildTransaction(
|
).buildTransaction(
|
||||||
{'nonce': self.web3.eth.getTransactionCount(address)}
|
{'from': address,
|
||||||
|
'nonce': self.web3.eth.getTransactionCount(address)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'ropsten' in ETH_REMOTE_NODE:
|
signed_tx = self.sign_transaction(tx)
|
||||||
tx['gas'] = min(int(tx['gas'] * 1.5), 4700000)
|
|
||||||
|
|
||||||
signed_tx = self.sign_transaction(address, tx)
|
|
||||||
try:
|
try:
|
||||||
tx_hash = '0x{}'.format(
|
tx_hash = '0x{}'.format(
|
||||||
bin_hex(self.web3.eth.sendRawTransaction(signed_tx))
|
bin_hex(self.web3.eth.sendRawTransaction(signed_tx))
|
||||||
@@ -328,13 +373,11 @@ class Marketplace:
|
|||||||
|
|
||||||
tx = self.mkt_contract.functions.subscribe(
|
tx = self.mkt_contract.functions.subscribe(
|
||||||
Web3.toHex(dataset),
|
Web3.toHex(dataset),
|
||||||
).buildTransaction(
|
).buildTransaction({
|
||||||
{'nonce': self.web3.eth.getTransactionCount(address)})
|
'from': address,
|
||||||
|
'nonce': self.web3.eth.getTransactionCount(address)})
|
||||||
|
|
||||||
if 'ropsten' in ETH_REMOTE_NODE:
|
signed_tx = self.sign_transaction(tx)
|
||||||
tx['gas'] = min(int(tx['gas'] * 1.5), 4700000)
|
|
||||||
|
|
||||||
signed_tx = self.sign_transaction(address, tx)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx_hash = '0x{}'.format(bin_hex(
|
tx_hash = '0x{}'.format(bin_hex(
|
||||||
@@ -369,7 +412,7 @@ class Marketplace:
|
|||||||
'You can now ingest this dataset anytime during the '
|
'You can now ingest this dataset anytime during the '
|
||||||
'next month by running the following command:\n'
|
'next month by running the following command:\n'
|
||||||
'catalyst marketplace ingest --dataset={}'.format(
|
'catalyst marketplace ingest --dataset={}'.format(
|
||||||
dataset, address, dataset))
|
dataset, address, dataset))
|
||||||
|
|
||||||
def process_temp_bundle(self, ds_name, path):
|
def process_temp_bundle(self, ds_name, path):
|
||||||
"""
|
"""
|
||||||
@@ -387,17 +430,43 @@ class Marketplace:
|
|||||||
"""
|
"""
|
||||||
tmp_bundle = extract_bundle(path)
|
tmp_bundle = extract_bundle(path)
|
||||||
bundle_folder = get_data_source_folder(ds_name)
|
bundle_folder = get_data_source_folder(ds_name)
|
||||||
|
ensure_directory(bundle_folder)
|
||||||
if os.listdir(bundle_folder):
|
if os.listdir(bundle_folder):
|
||||||
zsource = bcolz.ctable(rootdir=tmp_bundle, mode='r')
|
zsource = bcolz.ctable(rootdir=tmp_bundle, mode='r')
|
||||||
ztarget = bcolz.ctable(rootdir=bundle_folder, mode='r')
|
ztarget = bcolz.ctable(rootdir=bundle_folder, mode='r')
|
||||||
merge_bundles(zsource, ztarget)
|
merge_bundles(zsource, ztarget)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
shutil.rmtree(bundle_folder, ignore_errors=True)
|
||||||
os.rename(tmp_bundle, bundle_folder)
|
os.rename(tmp_bundle, bundle_folder)
|
||||||
|
|
||||||
pass
|
def ingest(self, ds_name=None, start=None, end=None, force_download=False):
|
||||||
|
|
||||||
def ingest(self, ds_name, 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()
|
# ds_name = ds_name.lower()
|
||||||
|
|
||||||
@@ -426,29 +495,38 @@ class Marketplace:
|
|||||||
print('Your subscription to dataset "{}" expired on {} UTC.'
|
print('Your subscription to dataset "{}" expired on {} UTC.'
|
||||||
'Please renew your subscription by running:\n'
|
'Please renew your subscription by running:\n'
|
||||||
'catalyst marketplace subscribe --dataset={}'.format(
|
'catalyst marketplace subscribe --dataset={}'.format(
|
||||||
ds_name,
|
ds_name,
|
||||||
pd.to_datetime(check_sub[4], unit='s', utc=True),
|
pd.to_datetime(check_sub[4], unit='s', utc=True),
|
||||||
ds_name)
|
ds_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'key' in self.addresses[address_i]:
|
if 'key' in self.addresses[address_i]:
|
||||||
key = self.addresses[address_i]['key']
|
key = self.addresses[address_i]['key']
|
||||||
secret = self.addresses[address_i]['secret']
|
secret = self.addresses[address_i]['secret']
|
||||||
else:
|
else:
|
||||||
key, secret = get_key_secret(address)
|
key, secret = get_key_secret(address,
|
||||||
|
self.addresses[address_i]['wallet'])
|
||||||
|
|
||||||
headers = get_signed_headers(ds_name, key, secret)
|
headers = get_signed_headers(ds_name, key, secret)
|
||||||
log.debug('Starting download of dataset for ingestion...')
|
log.info('Starting download of dataset for ingestion...')
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
'{}/marketplace/ingest'.format(AUTH_SERVER),
|
'{}/marketplace/ingest'.format(AUTH_SERVER),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
|
log.info('Dataset downloaded successfully. Processing dataset...')
|
||||||
target_path = get_temp_bundles_folder()
|
target_path = get_temp_bundles_folder()
|
||||||
try:
|
try:
|
||||||
decoder = MultipartDecoder.from_response(r)
|
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:
|
for part in decoder.parts:
|
||||||
|
log.info("Processing file {} of {}".format(
|
||||||
|
counter, len(decoder.parts)))
|
||||||
h = part.headers[b'Content-Disposition'].decode('utf-8')
|
h = part.headers[b'Content-Disposition'].decode('utf-8')
|
||||||
# Extracting the filename from the header
|
# Extracting the filename from the header
|
||||||
name = re.search(r'filename="(.*)"', h).group(1)
|
name = re.search(r'filename="(.*)"', h).group(1)
|
||||||
@@ -462,6 +540,7 @@ class Marketplace:
|
|||||||
f.write(part.content)
|
f.write(part.content)
|
||||||
|
|
||||||
self.process_temp_bundle(ds_name, filename)
|
self.process_temp_bundle(ds_name, filename)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
except NonMultipartContentTypeException:
|
except NonMultipartContentTypeException:
|
||||||
response = r.json()
|
response = r.json()
|
||||||
@@ -493,17 +572,42 @@ class Marketplace:
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def clean(self, data_source_name, data_frequency=None):
|
def clean(self, ds_name=None, data_frequency=None):
|
||||||
data_source_name = data_source_name.lower()
|
|
||||||
|
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:
|
if data_frequency is None:
|
||||||
folder = get_data_source_folder(data_source_name)
|
folder = get_data_source_folder(ds_name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
folder = get_bundle_folder(data_source_name, data_frequency)
|
folder = get_bundle_folder(ds_name, data_frequency)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
pass
|
|
||||||
|
|
||||||
def create_metadata(self, key, secret, ds_name, data_frequency, desc,
|
def create_metadata(self, key, secret, ds_name, data_frequency, desc,
|
||||||
has_history=True, has_live=True):
|
has_history=True, has_live=True):
|
||||||
@@ -539,7 +643,7 @@ class Marketplace:
|
|||||||
def register(self):
|
def register(self):
|
||||||
while True:
|
while True:
|
||||||
desc = input('Enter the name of the dataset to register: ')
|
desc = input('Enter the name of the dataset to register: ')
|
||||||
dataset = desc.lower()
|
dataset = desc.lower().strip()
|
||||||
provider_info = self.mkt_contract.functions.getDataProviderInfo(
|
provider_info = self.mkt_contract.functions.getDataProviderInfo(
|
||||||
Web3.toHex(dataset)
|
Web3.toHex(dataset)
|
||||||
).call()
|
).call()
|
||||||
@@ -595,7 +699,8 @@ class Marketplace:
|
|||||||
key = self.addresses[address_i]['key']
|
key = self.addresses[address_i]['key']
|
||||||
secret = self.addresses[address_i]['secret']
|
secret = self.addresses[address_i]['secret']
|
||||||
else:
|
else:
|
||||||
key, secret = get_key_secret(address)
|
key, secret = get_key_secret(address,
|
||||||
|
self.addresses[address_i]['wallet'])
|
||||||
|
|
||||||
grains = to_grains(price)
|
grains = to_grains(price)
|
||||||
|
|
||||||
@@ -604,13 +709,11 @@ class Marketplace:
|
|||||||
grains,
|
grains,
|
||||||
address,
|
address,
|
||||||
).buildTransaction(
|
).buildTransaction(
|
||||||
{'nonce': self.web3.eth.getTransactionCount(address)}
|
{'from': address,
|
||||||
|
'nonce': self.web3.eth.getTransactionCount(address)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'ropsten' in ETH_REMOTE_NODE:
|
signed_tx = self.sign_transaction(tx)
|
||||||
tx['gas'] = min(int(tx['gas'] * 1.5), 4700000)
|
|
||||||
|
|
||||||
signed_tx = self.sign_transaction(address, tx)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx_hash = '0x{}'.format(
|
tx_hash = '0x{}'.format(
|
||||||
@@ -621,7 +724,7 @@ class Marketplace:
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Unable to subscribe to data source: {}'.format(e))
|
print('Unable to register the requested dataset: {}'.format(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.check_transaction(tx_hash)
|
self.check_transaction(tx_hash)
|
||||||
@@ -678,28 +781,34 @@ class Marketplace:
|
|||||||
key = match['key']
|
key = match['key']
|
||||||
secret = match['secret']
|
secret = match['secret']
|
||||||
else:
|
else:
|
||||||
key, secret = get_key_secret(provider_info[0])
|
key, secret = get_key_secret(provider_info[0], match['wallet'])
|
||||||
|
|
||||||
headers = get_signed_headers(dataset, key, secret)
|
|
||||||
filenames = glob.glob(os.path.join(datadir, '*.csv'))
|
filenames = glob.glob(os.path.join(datadir, '*.csv'))
|
||||||
|
|
||||||
if not filenames:
|
if not filenames:
|
||||||
raise MarketplaceNoCSVFiles(datadir=datadir)
|
raise MarketplaceNoCSVFiles(datadir=datadir)
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
for file in filenames:
|
for idx, file in enumerate(filenames):
|
||||||
|
log.info('Uploading file {} of {}: {}'.format(
|
||||||
|
idx+1, len(filenames), file))
|
||||||
|
files = []
|
||||||
files.append(('file', open(file, 'rb')))
|
files.append(('file', open(file, 'rb')))
|
||||||
|
|
||||||
r = requests.post('{}/marketplace/publish'.format(AUTH_SERVER),
|
headers = get_signed_headers(dataset, key, secret)
|
||||||
files=files,
|
r = requests.post('{}/marketplace/publish'.format(AUTH_SERVER),
|
||||||
headers=headers)
|
files=files,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise MarketplaceHTTPRequest(request='upload file',
|
raise MarketplaceHTTPRequest(request='upload file',
|
||||||
error=r.status_code)
|
error=r.status_code)
|
||||||
|
|
||||||
if 'error' in r.json():
|
if 'error' in r.json():
|
||||||
raise MarketplaceHTTPRequest(request='upload file',
|
raise MarketplaceHTTPRequest(request='upload file',
|
||||||
error=r.json()['error'])
|
error=r.json()['error'])
|
||||||
|
|
||||||
print('Dataset {} uploaded successfully.'.format(dataset))
|
log.info('File processed successfully.')
|
||||||
|
|
||||||
|
print('\nDataset {} uploaded and processed successfully.'.format(
|
||||||
|
dataset))
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ def silent_except_hook(exctype, excvalue, exctraceback):
|
|||||||
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
|
MarketplaceNoAddressMatch, MarketplaceHTTPRequest,
|
||||||
MarketplaceNoCSVFiles, MarketplaceContractDataNoMatch,
|
MarketplaceNoCSVFiles, MarketplaceContractDataNoMatch,
|
||||||
MarketplaceSubscriptionExpired, MarketplaceJSONError,
|
MarketplaceSubscriptionExpired, MarketplaceJSONError,
|
||||||
MarketplaceWalletNotSupported, MarketplaceEmptySignature]:
|
MarketplaceWalletNotSupported, MarketplaceEmptySignature,
|
||||||
|
MarketplaceRequiresPython3]:
|
||||||
fn = traceback.extract_tb(exctraceback)[-1][0]
|
fn = traceback.extract_tb(exctraceback)[-1][0]
|
||||||
ln = traceback.extract_tb(exctraceback)[-1][1]
|
ln = traceback.extract_tb(exctraceback)[-1][1]
|
||||||
print("Error traceback: {1} (line {2})\n"
|
print("Error traceback: {1} (line {2})\n"
|
||||||
@@ -86,3 +87,11 @@ class MarketplaceJSONError(ZiplineError):
|
|||||||
'The configuration file {file} is malformed. Please correct '
|
'The configuration file {file} is malformed. Please correct '
|
||||||
'the following error:\n{error}'
|
'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.')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
@@ -9,10 +10,10 @@ from catalyst.marketplace.marketplace_errors import (
|
|||||||
MarketplaceEmptySignature)
|
MarketplaceEmptySignature)
|
||||||
from catalyst.marketplace.utils.path_utils import (
|
from catalyst.marketplace.utils.path_utils import (
|
||||||
get_user_pubaddr, save_user_pubaddr)
|
get_user_pubaddr, save_user_pubaddr)
|
||||||
from catalyst.constants import AUTH_SERVER
|
from catalyst.constants import AUTH_SERVER, SUPPORTED_WALLETS
|
||||||
|
|
||||||
|
|
||||||
def get_key_secret(pubAddr, wallet='mew'):
|
def get_key_secret(pubAddr, wallet):
|
||||||
"""
|
"""
|
||||||
Obtain a new key/secret pair from authentication server
|
Obtain a new key/secret pair from authentication server
|
||||||
|
|
||||||
@@ -42,16 +43,24 @@ def get_key_secret(pubAddr, wallet='mew'):
|
|||||||
auth_type, auth_info = header.split(None, 1)
|
auth_type, auth_info = header.split(None, 1)
|
||||||
d = requests.utils.parse_dict_header(auth_info)
|
d = requests.utils.parse_dict_header(auth_info)
|
||||||
|
|
||||||
nonce = '0x{}'.format(d['nonce'])
|
nonce = 'Catalyst nonce: 0x{}'.format(d['nonce'])
|
||||||
|
|
||||||
|
if wallet in SUPPORTED_WALLETS:
|
||||||
|
url = 'https://www.mycrypto.com/signmsg.html'
|
||||||
|
|
||||||
if wallet == 'mew':
|
|
||||||
print('\nObtaining a key/secret pair to streamline all future '
|
print('\nObtaining a key/secret pair to streamline all future '
|
||||||
'requests with the authentication server.\n'
|
'requests with the authentication server.\n'
|
||||||
'Visit https://www.myetherwallet.com/signmsg.html and sign the'
|
'Visit {url} and sign the '
|
||||||
'following message:\n{}'.format(nonce))
|
'following message (copy the entire line, without the '
|
||||||
signature = input('Copy and Paste the "sig" field from '
|
'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, '
|
'the signature here (without the double quotes, '
|
||||||
'only the HEX value:\n')
|
'only the HEX value):\n')
|
||||||
else:
|
else:
|
||||||
raise MarketplaceWalletNotSupported(wallet=wallet)
|
raise MarketplaceWalletNotSupported(wallet=wallet)
|
||||||
|
|
||||||
@@ -83,7 +92,8 @@ def get_key_secret(pubAddr, wallet='mew'):
|
|||||||
addresses = get_user_pubaddr()
|
addresses = get_user_pubaddr()
|
||||||
|
|
||||||
match = next((l for l in addresses if
|
match = next((l for l in addresses if
|
||||||
l['pubAddr'] == pubAddr), None)
|
l['pubAddr'].lower() == pubAddr.lower()), None)
|
||||||
|
|
||||||
match['key'] = response.json()['key']
|
match['key'] = response.json()['key']
|
||||||
match['secret'] = response.json()['secret']
|
match['secret'] = response.json()['secret']
|
||||||
|
|
||||||
@@ -113,7 +123,7 @@ def get_signed_headers(ds_name, key, secret):
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
"""
|
"""
|
||||||
nonce = str(int(time.time()))
|
nonce = str(int(time.time() * 1000))
|
||||||
|
|
||||||
signature = hmac.new(
|
signature = hmac.new(
|
||||||
secret.encode('utf-8'),
|
secret.encode('utf-8'),
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import bcolz
|
import bcolz
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
def merge_bundles(zsource, ztarget):
|
def merge_bundles(zsource, ztarget):
|
||||||
@@ -18,19 +23,72 @@ def merge_bundles(zsource, ztarget):
|
|||||||
"""
|
"""
|
||||||
# TODO: find a way to do this iteratively instead of in-memory
|
# TODO: find a way to do this iteratively instead of in-memory
|
||||||
df_source = zsource.todataframe()
|
df_source = zsource.todataframe()
|
||||||
df_source.set_index('date', drop=False, inplace=True)
|
|
||||||
df_target = ztarget.todataframe()
|
df_target = ztarget.todataframe()
|
||||||
df_target.set_index('date', drop=False, inplace=True)
|
|
||||||
|
|
||||||
df = df_target.merge(
|
df = pd.concat(
|
||||||
right=df_source,
|
[df_source, df_target], ignore_index=True
|
||||||
how='right',
|
|
||||||
) # type: pd.DataFrame
|
) # 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)
|
dirname = os.path.basename(ztarget.rootdir)
|
||||||
bak_dir = ztarget.rootdir.replace(dirname, '.{}'.format(dirname))
|
bak_dir = ztarget.rootdir.replace(dirname, '.{}'.format(dirname))
|
||||||
os.rename(ztarget.rootdir, bak_dir)
|
shutil.move(ztarget.rootdir, bak_dir)
|
||||||
|
|
||||||
z = bcolz.ctable.fromdataframe(df=df, rootdir=ztarget.rootdir)
|
z = bcolz.ctable.fromdataframe(df=df, rootdir=ztarget.rootdir)
|
||||||
shutil.rmtree(bak_dir)
|
shutil.rmtree(bak_dir)
|
||||||
return z
|
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)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
|
from catalyst.constants import SUPPORTED_WALLETS
|
||||||
from catalyst.utils.deprecate import deprecated
|
from catalyst.utils.deprecate import deprecated
|
||||||
from catalyst.utils.paths import data_root, ensure_directory
|
from catalyst.utils.paths import data_root, ensure_directory
|
||||||
from catalyst.marketplace.marketplace_errors import MarketplaceJSONError
|
from catalyst.marketplace.marketplace_errors import MarketplaceJSONError
|
||||||
@@ -131,17 +132,63 @@ def get_user_pubaddr(environ=None):
|
|||||||
try:
|
try:
|
||||||
d = data[0]['pubAddr']
|
d = data[0]['pubAddr']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return [data, ]
|
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
|
return data
|
||||||
|
|
||||||
else:
|
else:
|
||||||
data = []
|
data = []
|
||||||
data.append(dict(pubAddr='', desc=''))
|
data.append(dict(pubAddr='', desc='', wallet=''))
|
||||||
with open(filename, 'w') as f:
|
with open(filename, 'w') as f:
|
||||||
json.dump(data, f, sort_keys=False, indent=2,
|
json.dump(data, f, sort_keys=False, indent=2,
|
||||||
separators=(',', ':'))
|
separators=(',', ':'))
|
||||||
return data
|
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):
|
def save_user_pubaddr(data, environ=None):
|
||||||
"""
|
"""
|
||||||
Saves the user's public addresses and their related metadata in
|
Saves the user's public addresses and their related metadata in
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
@@ -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)
|
||||||
@@ -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))
|
||||||
@@ -10,6 +10,7 @@ import click
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
|
import catalyst
|
||||||
from catalyst.data.bundles import load
|
from catalyst.data.bundles import load
|
||||||
from catalyst.data.data_portal import DataPortal
|
from catalyst.data.data_portal import DataPortal
|
||||||
from catalyst.exchange.exchange_pricing_loader import ExchangePricingLoader, \
|
from catalyst.exchange.exchange_pricing_loader import ExchangePricingLoader, \
|
||||||
@@ -23,7 +24,7 @@ try:
|
|||||||
from pygments.formatters import TerminalFormatter
|
from pygments.formatters import TerminalFormatter
|
||||||
|
|
||||||
PYGMENTS = True
|
PYGMENTS = True
|
||||||
except:
|
except ImportError:
|
||||||
PYGMENTS = False
|
PYGMENTS = False
|
||||||
from toolz import valfilter, concatv
|
from toolz import valfilter, concatv
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -55,6 +56,7 @@ class _RunAlgoError(click.ClickException, ValueError):
|
|||||||
----------
|
----------
|
||||||
pyfunc_msg : str
|
pyfunc_msg : str
|
||||||
The message that will be shown when called as a python function.
|
The message that will be shown when called as a python function.
|
||||||
|
|
||||||
cmdline_msg : str
|
cmdline_msg : str
|
||||||
The message that will be shown on the command line.
|
The message that will be shown on the command line.
|
||||||
"""
|
"""
|
||||||
@@ -150,6 +152,7 @@ def _run(handle_data,
|
|||||||
'We encourage you to report any issue on GitHub: '
|
'We encourage you to report any issue on GitHub: '
|
||||||
'https://github.com/enigmampc/catalyst/issues'
|
'https://github.com/enigmampc/catalyst/issues'
|
||||||
)
|
)
|
||||||
|
log.info('Catalyst version {}'.format(catalyst.__version__))
|
||||||
sleep(3)
|
sleep(3)
|
||||||
|
|
||||||
if live:
|
if live:
|
||||||
@@ -260,6 +263,15 @@ def _run(handle_data,
|
|||||||
# We still need to support bundles for other misc data, but we
|
# We still need to support bundles for other misc data, but we
|
||||||
# can handle this later.
|
# can handle this later.
|
||||||
|
|
||||||
|
if start != pd.tslib.normalize_date(start) or \
|
||||||
|
end != pd.tslib.normalize_date(end):
|
||||||
|
# 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(
|
data = DataPortalExchangeBacktest(
|
||||||
exchange_names=[exchange_name for exchange_name in exchanges],
|
exchange_names=[exchange_name for exchange_name in exchanges],
|
||||||
asset_finder=None,
|
asset_finder=None,
|
||||||
@@ -416,7 +428,8 @@ def run_algorithm(initialize,
|
|||||||
auth_aliases=None,
|
auth_aliases=None,
|
||||||
stats_output=None,
|
stats_output=None,
|
||||||
output=os.devnull):
|
output=os.devnull):
|
||||||
"""Run a trading algorithm.
|
"""
|
||||||
|
Run a trading algorithm.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -458,7 +471,7 @@ def run_algorithm(initialize,
|
|||||||
This argument is mutually exclusive with ``data``.
|
This argument is mutually exclusive with ``data``.
|
||||||
default_extension : bool, optional
|
default_extension : bool, optional
|
||||||
Should the default catalyst extension be loaded. This is found at
|
Should the default catalyst extension be loaded. This is found at
|
||||||
``$ZIPLINE_ROOT/extension.py``
|
``$CATALYST_ROOT/extension.py``
|
||||||
extensions : iterable[str], optional
|
extensions : iterable[str], optional
|
||||||
The names of any other extensions to load. Each element may either be
|
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
|
a dotted module path like ``a.b.c`` or a path to a python file ending
|
||||||
@@ -469,12 +482,8 @@ def run_algorithm(initialize,
|
|||||||
environ : mapping[str -> str], optional
|
environ : mapping[str -> str], optional
|
||||||
The os environment to use. Many extensions use this to get parameters.
|
The os environment to use. Many extensions use this to get parameters.
|
||||||
This defaults to ``os.environ``.
|
This defaults to ``os.environ``.
|
||||||
live: execute live trading
|
live : bool, optional
|
||||||
exchange_conn: The exchange connection parameters
|
Execute algorithm in live trading mode.
|
||||||
|
|
||||||
Supported Exchanges
|
|
||||||
-------------------
|
|
||||||
bitfinex
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|||||||
+173
-179
@@ -4,7 +4,7 @@ API Reference
|
|||||||
Running a Backtest
|
Running a Backtest
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autofunction:: zipline.run_algorithm(...)
|
.. autofunction:: catalyst.run_algorithm(...)
|
||||||
|
|
||||||
Algorithm API
|
Algorithm API
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
@@ -18,341 +18,335 @@ currently-executing :class:`~zipline.algorithm.TradingAlgorithm` instance.
|
|||||||
Data Object
|
Data Object
|
||||||
```````````
|
```````````
|
||||||
|
|
||||||
.. autoclass:: zipline.protocol.BarData
|
.. autoclass:: catalyst.protocol.BarData
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Scheduling Functions
|
Scheduling Functions
|
||||||
````````````````````
|
````````````````````
|
||||||
|
|
||||||
.. autofunction:: zipline.api.schedule_function
|
.. autofunction:: catalyst.api.schedule_function
|
||||||
|
|
||||||
.. autoclass:: zipline.api.date_rules
|
.. autoclass:: catalyst.api.date_rules
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. autoclass:: zipline.api.time_rules
|
.. autoclass:: catalyst.api.time_rules
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Orders
|
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:
|
: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
|
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:
|
:members:
|
||||||
|
|
||||||
.. autofunction:: zipline.api.EODCancel
|
.. autofunction:: catalyst.api.EODCancel
|
||||||
|
|
||||||
.. autofunction:: zipline.api.NeverCancel
|
.. autofunction:: catalyst.api.NeverCancel
|
||||||
|
|
||||||
|
|
||||||
Assets
|
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:: catalyst.api.sid
|
||||||
|
|
||||||
.. autofunction:: zipline.api.sid
|
|
||||||
|
|
||||||
|
|
||||||
Trading Controls
|
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
|
performing as expected. The functions help protect the algorithm from certian
|
||||||
bugs that could cause undesirable behavior when trading with real money.
|
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
|
Simulation Parameters
|
||||||
`````````````````````
|
`````````````````````
|
||||||
|
|
||||||
.. autofunction:: zipline.api.set_benchmark
|
.. autofunction:: catalyst.api.set_benchmark
|
||||||
|
|
||||||
Commission Models
|
Commission Models
|
||||||
'''''''''''''''''
|
'''''''''''''''''
|
||||||
|
|
||||||
.. autofunction:: zipline.api.set_commission
|
.. autofunction:: catalyst.api.set_commission
|
||||||
|
|
||||||
.. autoclass:: zipline.finance.commission.CommissionModel
|
.. autoclass:: catalyst.finance.commission.CommissionModel
|
||||||
:members:
|
: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
|
Slippage Models
|
||||||
'''''''''''''''
|
'''''''''''''''
|
||||||
|
|
||||||
.. autofunction:: zipline.api.set_slippage
|
.. autofunction:: catalyst.api.set_slippage
|
||||||
|
|
||||||
.. autoclass:: zipline.finance.slippage.SlippageModel
|
.. autoclass:: catalyst.finance.slippage.SlippageModel
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: zipline.finance.slippage.FixedSlippage
|
.. autoclass:: catalyst.finance.slippage.FixedSlippage
|
||||||
|
|
||||||
.. autoclass:: zipline.finance.slippage.VolumeShareSlippage
|
.. autoclass:: catalyst.finance.slippage.VolumeShareSlippage
|
||||||
|
|
||||||
Pipeline
|
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
|
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
|
.. Pipeline API
|
||||||
~~~~~~~~~~~~
|
.. ~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.Pipeline
|
.. .. autoclass:: zipline.pipeline.Pipeline
|
||||||
:members:
|
.. :members:
|
||||||
:member-order: groupwise
|
.. :member-order: groupwise
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.CustomFactor
|
.. .. autoclass:: zipline.pipeline.CustomFactor
|
||||||
:members:
|
.. :members:
|
||||||
:member-order: groupwise
|
.. :member-order: groupwise
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.filters.Filter
|
.. .. autoclass:: zipline.pipeline.filters.Filter
|
||||||
:members: __and__, __or__
|
.. :members: __and__, __or__
|
||||||
:exclude-members: dtype
|
.. :exclude-members: dtype
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.Factor
|
.. .. autoclass:: zipline.pipeline.factors.Factor
|
||||||
:members: bottom, deciles, demean, linear_regression, pearsonr,
|
.. :members: bottom, deciles, demean, linear_regression, pearsonr,
|
||||||
percentile_between, quantiles, quartiles, quintiles, rank,
|
.. percentile_between, quantiles, quartiles, quintiles, rank,
|
||||||
spearmanr, top, winsorize, zscore, isnan, notnan, isfinite, eq,
|
.. spearmanr, top, winsorize, zscore, isnan, notnan, isfinite, eq,
|
||||||
__add__, __sub__, __mul__, __div__, __mod__, __pow__, __lt__,
|
.. \__add__, \__sub__, \__mul__, \__div__, \__mod__, \__pow__,
|
||||||
__le__, __ne__, __ge__, __gt__
|
.. \__lt__, \__le__, \__ne__, \__ge__, \__gt__
|
||||||
:exclude-members: dtype
|
.. :exclude-members: dtype
|
||||||
:member-order: bysource
|
.. :member-order: bysource
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.term.Term
|
.. .. autoclass:: zipline.pipeline.term.Term
|
||||||
:members:
|
.. :members:
|
||||||
:exclude-members: compute_extra_rows, dependencies, inputs, mask, windowed
|
.. :exclude-members: compute_extra_rows, dependencies, inputs, mask, windowed
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.data.USEquityPricing
|
.. .. autoclass:: zipline.pipeline.data.USEquityPricing
|
||||||
:members: open, high, low, close, volume
|
.. :members: open, high, low, close, volume
|
||||||
:undoc-members:
|
.. :undoc-members:
|
||||||
|
|
||||||
Built-in Factors
|
.. Built-in Factors
|
||||||
````````````````
|
.. ````````````````
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.AverageDollarVolume
|
.. .. autoclass:: zipline.pipeline.factors.AverageDollarVolume
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.BollingerBands
|
.. .. autoclass:: zipline.pipeline.factors.BollingerBands
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.BusinessDaysSincePreviousEvent
|
.. .. autoclass:: zipline.pipeline.factors.BusinessDaysSincePreviousEvent
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.BusinessDaysUntilNextEvent
|
.. .. autoclass:: zipline.pipeline.factors.BusinessDaysUntilNextEvent
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingAverage
|
.. .. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingAverage
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingStdDev
|
.. .. autoclass:: zipline.pipeline.factors.ExponentialWeightedMovingStdDev
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.Latest
|
.. .. autoclass:: zipline.pipeline.factors.Latest
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.MaxDrawdown
|
.. .. autoclass:: zipline.pipeline.factors.MaxDrawdown
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.Returns
|
.. .. autoclass:: zipline.pipeline.factors.Returns
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.RollingLinearRegressionOfReturns
|
.. .. autoclass:: zipline.pipeline.factors.RollingLinearRegressionOfReturns
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.RollingPearsonOfReturns
|
.. .. autoclass:: zipline.pipeline.factors.RollingPearsonOfReturns
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.RollingSpearmanOfReturns
|
.. .. autoclass:: zipline.pipeline.factors.RollingSpearmanOfReturns
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.RSI
|
.. .. autoclass:: zipline.pipeline.factors.RSI
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.SimpleMovingAverage
|
.. .. autoclass:: zipline.pipeline.factors.SimpleMovingAverage
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.VWAP
|
.. .. autoclass:: zipline.pipeline.factors.VWAP
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.factors.WeightedAverageValue
|
.. .. autoclass:: zipline.pipeline.factors.WeightedAverageValue
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
Pipeline Engine
|
.. Pipeline Engine
|
||||||
```````````````
|
.. ```````````````
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.engine.PipelineEngine
|
.. .. autoclass:: zipline.pipeline.engine.PipelineEngine
|
||||||
:members: run_pipeline, run_chunked_pipeline
|
.. :members: run_pipeline, run_chunked_pipeline
|
||||||
:member-order: bysource
|
.. :member-order: bysource
|
||||||
|
|
||||||
.. autoclass:: zipline.pipeline.engine.SimplePipelineEngine
|
.. .. autoclass:: zipline.pipeline.engine.SimplePipelineEngine
|
||||||
:members: __init__, run_pipeline, run_chunked_pipeline
|
.. :members: __init__, run_pipeline, run_chunked_pipeline
|
||||||
:member-order: bysource
|
.. :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
|
.. .. autoclass:: zipline.pipeline.loaders.equity_pricing_loader.USEquityPricingLoader
|
||||||
:members: __init__, from_files, load_adjusted_array
|
.. :members: __init__, from_files, load_adjusted_array
|
||||||
:member-order: bysource
|
.. :member-order: bysource
|
||||||
|
|
||||||
Asset Metadata
|
Asset Metadata
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.Asset
|
.. autoclass:: catalyst.assets.Asset
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.Equity
|
.. autoclass:: catalyst.assets.AssetConvertible
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.Future
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.AssetConvertible
|
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Trading Calendar API
|
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:
|
: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
|
Data API
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
Writers
|
.. Writers
|
||||||
```````
|
.. ```````
|
||||||
.. autoclass:: zipline.data.minute_bars.BcolzMinuteBarWriter
|
.. .. autoclass:: zipline.data.minute_bars.BcolzMinuteBarWriter
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarWriter
|
.. .. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarWriter
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentWriter
|
.. .. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentWriter
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.AssetDBWriter
|
.. .. autoclass:: zipline.assets.AssetDBWriter
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
Readers
|
.. Readers
|
||||||
```````
|
.. ```````
|
||||||
.. autoclass:: zipline.data.minute_bars.BcolzMinuteBarReader
|
.. .. autoclass:: zipline.data.minute_bars.BcolzMinuteBarReader
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarReader
|
.. .. autoclass:: zipline.data.us_equity_pricing.BcolzDailyBarReader
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentReader
|
.. .. autoclass:: zipline.data.us_equity_pricing.SQLiteAdjustmentReader
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.assets.AssetFinder
|
.. .. autoclass:: zipline.assets.AssetFinder
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
.. autoclass:: zipline.data.data_portal.DataPortal
|
.. .. autoclass:: zipline.data.data_portal.DataPortal
|
||||||
:members:
|
.. :members:
|
||||||
|
|
||||||
Bundles
|
.. Bundles
|
||||||
```````
|
.. ```````
|
||||||
.. autofunction:: zipline.data.bundles.register
|
.. .. 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
|
.. 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
|
.. data. This mapping is immutable and should only be updated through
|
||||||
:func:`~zipline.data.bundles.register` or
|
.. :func:`~zipline.data.bundles.register` or
|
||||||
:func:`~zipline.data.bundles.unregister`.
|
.. :func:`~zipline.data.bundles.unregister`.
|
||||||
|
|
||||||
.. autofunction:: zipline.data.bundles.yahoo_equities
|
.. .. autofunction:: zipline.data.bundles.yahoo_equities
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -362,16 +356,16 @@ Utilities
|
|||||||
Caching
|
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
|
Command Line
|
||||||
````````````
|
````````````
|
||||||
.. autofunction:: zipline.utils.cli.maybe_show_progress
|
.. autofunction:: catalyst.utils.cli.maybe_show_progress
|
||||||
|
|||||||
@@ -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>`
|
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
|
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
|
: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.
|
have assimilated the contents of this tutorial.
|
||||||
|
|
||||||
Command line interface
|
Command line interface
|
||||||
@@ -473,6 +473,7 @@ Which we execute by running:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
There is a row for each trading day, starting on the first day of our
|
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
|
simulation Jan 1st, 2016. In the columns you can find various
|
||||||
information about the state of your algorithm. The column
|
information about the state of your algorithm. The column
|
||||||
@@ -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
|
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:
|
reason you don't have it installed, you can add it by running:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: bash
|
||||||
|
|
||||||
(catalyst)$ pip install matplotlib
|
(catalyst)$ pip install matplotlib
|
||||||
|
|
||||||
@@ -579,162 +580,8 @@ which you can skim through for now. A copy of this algorithm is available in
|
|||||||
the ``examples`` directory:
|
the ``examples`` directory:
|
||||||
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
|
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
|
||||||
|
|
||||||
.. code-block:: python
|
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
|
||||||
|
:language: 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.utils.stats_utils import extract_transactions
|
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
|
||||||
log = Logger(NAMESPACE)
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.i = 0
|
|
||||||
context.asset = symbol('ltc_usd')
|
|
||||||
context.base_price = None
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
# define the windows for the moving averages
|
|
||||||
short_window = 50
|
|
||||||
long_window = 200
|
|
||||||
|
|
||||||
# Skip as many bars as long_window to properly compute the average
|
|
||||||
context.i += 1
|
|
||||||
if context.i < long_window:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Compute moving averages calling data.history() for each
|
|
||||||
# moving average with the appropriate parameters. We choose to use
|
|
||||||
# minute bars for this simulation -> freq="1m"
|
|
||||||
# Returns a pandas dataframe.
|
|
||||||
short_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=short_window, frequency="1m").mean()
|
|
||||||
long_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=long_window, frequency="1m").mean()
|
|
||||||
|
|
||||||
# Let's keep the price of our asset in a more handy variable
|
|
||||||
price = data.current(context.asset, 'price')
|
|
||||||
|
|
||||||
# If base_price is not set, we use the current value. This is the
|
|
||||||
# price at the first bar which we reference to calculate price_change.
|
|
||||||
if context.base_price is None:
|
|
||||||
context.base_price = price
|
|
||||||
price_change = (price - context.base_price) / context.base_price
|
|
||||||
|
|
||||||
# Save values for later inspection
|
|
||||||
record(price=price,
|
|
||||||
cash=context.portfolio.cash,
|
|
||||||
price_change=price_change,
|
|
||||||
short_mavg=short_mavg,
|
|
||||||
long_mavg=long_mavg)
|
|
||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
|
||||||
# we wait until all orders are executed before considering more trades.
|
|
||||||
orders = get_open_orders(context.asset)
|
|
||||||
if len(orders) > 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Exit if we cannot trade
|
|
||||||
if not data.can_trade(context.asset):
|
|
||||||
return
|
|
||||||
|
|
||||||
# We check what's our position on our portfolio and trade accordingly
|
|
||||||
pos_amount = context.portfolio.positions[context.asset].amount
|
|
||||||
|
|
||||||
# Trading logic
|
|
||||||
if short_mavg > long_mavg and pos_amount == 0:
|
|
||||||
# we buy 100% of our portfolio for this asset
|
|
||||||
order_target_percent(context.asset, 1)
|
|
||||||
elif short_mavg < long_mavg and pos_amount > 0:
|
|
||||||
# we sell all our positions for this asset
|
|
||||||
order_target_percent(context.asset, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context, perf):
|
|
||||||
|
|
||||||
# Get the base_currency that was passed as a parameter to the simulation
|
|
||||||
exchange = list(context.exchanges.values())[0]
|
|
||||||
base_currency = exchange.base_currency.upper()
|
|
||||||
|
|
||||||
# First chart: Plot portfolio value using base_currency
|
|
||||||
ax1 = plt.subplot(411)
|
|
||||||
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
|
|
||||||
ax1.legend_.remove()
|
|
||||||
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
|
|
||||||
start, end = ax1.get_ylim()
|
|
||||||
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Second chart: Plot asset price, moving averages and buys/sells
|
|
||||||
ax2 = plt.subplot(412, sharex=ax1)
|
|
||||||
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
|
||||||
ax2.legend_.remove()
|
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
|
||||||
asset = context.asset.symbol,
|
|
||||||
base = base_currency
|
|
||||||
))
|
|
||||||
start, end = ax2.get_ylim()
|
|
||||||
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
transaction_df = extract_transactions(perf)
|
|
||||||
if not transaction_df.empty:
|
|
||||||
buy_df = transaction_df[transaction_df['amount'] > 0]
|
|
||||||
sell_df = transaction_df[transaction_df['amount'] < 0]
|
|
||||||
ax2.scatter(
|
|
||||||
buy_df.index.to_pydatetime(),
|
|
||||||
perf.loc[buy_df.index, 'price'],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='green',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
ax2.scatter(
|
|
||||||
sell_df.index.to_pydatetime(),
|
|
||||||
perf.loc[sell_df.index, 'price'],
|
|
||||||
marker='v',
|
|
||||||
s=100,
|
|
||||||
c='red',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
|
|
||||||
# Third chart: Compare percentage change between our portfolio
|
|
||||||
# and the price of the asset
|
|
||||||
ax3 = plt.subplot(413, sharex=ax1)
|
|
||||||
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
|
|
||||||
ax3.legend_.remove()
|
|
||||||
ax3.set_ylabel('Percent Change')
|
|
||||||
start, end = ax3.get_ylim()
|
|
||||||
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Fourth chart: Plot our cash
|
|
||||||
ax4 = plt.subplot(414, sharex=ax1)
|
|
||||||
perf.cash.plot(ax=ax4)
|
|
||||||
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
|
||||||
start, end = ax4.get_ylim()
|
|
||||||
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
|
|
||||||
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=1000,
|
|
||||||
data_frequency='minute',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace=NAMESPACE,
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2017-9-22', utc=True),
|
|
||||||
end=pd.to_datetime('2017-9-23', utc=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
In order to run the code above, you have to ingest the needed data first:
|
In order to run the code above, you have to ingest the needed data first:
|
||||||
|
|
||||||
@@ -806,6 +653,7 @@ the ``scikit-learn`` functions require ``numpy.ndarray``\ s rather than
|
|||||||
``pandas.DataFrame``\ s, so you can simply pass the underlying
|
``pandas.DataFrame``\ s, so you can simply pass the underlying
|
||||||
``ndarray`` of a ``DataFrame`` via ``.values``).
|
``ndarray`` of a ``DataFrame`` via ``.values``).
|
||||||
|
|
||||||
|
.. _jupyter:
|
||||||
|
|
||||||
Jupyter Notebook
|
Jupyter Notebook
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
@@ -826,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
|
environment. It's available as ``pip`` package, so regardless of how you
|
||||||
installed Catalyst, go inside your catalyst environemnt and run:
|
installed Catalyst, go inside your catalyst environemnt and run:
|
||||||
|
|
||||||
.. code:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
(catalyst)$ pip install jupyter
|
(catalyst)$ pip install jupyter
|
||||||
|
|
||||||
Once you have Jupyter Notebook installed, every time you want to use it run:
|
Once you have Jupyter Notebook installed, every time you want to use it run:
|
||||||
|
|
||||||
.. code:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
(catalyst)$ jupyter notebook
|
(catalyst)$ jupyter notebook
|
||||||
|
|
||||||
@@ -846,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
|
the data from the command line interface (CLI). In the example below, you would
|
||||||
need to run first:
|
need to run first:
|
||||||
|
|
||||||
.. code:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
catalyst ingest-exchange -x bitfinex -i btc_usd
|
catalyst ingest-exchange -x bitfinex -i btc_usd
|
||||||
|
|
||||||
@@ -16607,7 +16455,49 @@ NaN
|
|||||||
|
|
||||||
</div>
|
</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
|
Next steps
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|||||||
+5
-2
@@ -27,8 +27,8 @@ extlinks = {
|
|||||||
|
|
||||||
# -- Docstrings ---------------------------------------------------------------
|
# -- Docstrings ---------------------------------------------------------------
|
||||||
|
|
||||||
#extensions += ['numpydoc']
|
extensions += ['numpydoc']
|
||||||
#numpydoc_show_class_members = False
|
numpydoc_show_class_members = False
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['.templates']
|
templates_path = ['.templates']
|
||||||
@@ -97,3 +97,6 @@ intersphinx_mapping = {
|
|||||||
doctest_global_setup = "import catalyst"
|
doctest_global_setup = "import catalyst"
|
||||||
|
|
||||||
todo_include_todos = True
|
todo_include_todos = True
|
||||||
|
|
||||||
|
suppress_warnings = ['image.nonlocal_uri']
|
||||||
|
|
||||||
|
|||||||
@@ -36,25 +36,15 @@ Finally, you can build the C extensions by running:
|
|||||||
|
|
||||||
$ python setup.py build_ext --inplace
|
$ 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
|
__ https://docs.docker.com/get-started/
|
||||||
|
|
||||||
.. 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/
|
|
||||||
|
|
||||||
Git Branching Structure
|
Git Branching Structure
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|||||||
+18
-881
@@ -1,4 +1,5 @@
|
|||||||
|
|
|
|
||||||
|
|
||||||
Example Algorithms
|
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>`_
|
Source code: `examples/buy_btc_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_btc_simple.py>`_
|
||||||
|
|
||||||
.. code-block:: python
|
.. literalinclude:: ../../catalyst/examples/buy_btc_simple.py
|
||||||
|
:language: 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'))
|
|
||||||
|
|
||||||
This simple algorithm does not produce any output nor displays any chart.
|
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
|
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:
|
First ingest the historical pricing data needed to run this algorithm:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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
|
earlier date, you'll get an error), and the most recent date you can choose is
|
||||||
one day prior to the current date.
|
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
|
.. literalinclude:: ../../catalyst/examples/buy_and_hodl.py
|
||||||
|
:language: 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),
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_buy_and_hodl.png
|
.. 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
|
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 strategy is covered in detail in the last part of
|
||||||
`this tutorial <beginner-tutorial.html#history>`_.
|
`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
|
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
|
||||||
import pandas as pd
|
:language: python
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/tutorial_dual_moving_average.png
|
.. 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
|
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
|
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.
|
up quickly, we're going to buy; when it goes down quickly, we're going to sell.
|
||||||
Hopefully, we'll ride the waves.
|
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
|
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
|
.. literalinclude:: ../../catalyst/examples/mean_reversion_simple.py
|
||||||
import tempfile
|
:language: python
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_mean_reversion_simple.png
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_mean_reversion_simple.png
|
||||||
|
|
||||||
@@ -762,8 +158,6 @@ strategy.
|
|||||||
Simple Universe
|
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
|
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
|
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
|
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
|
catalyst ingest-exchange -x bitfinex -f minute
|
||||||
|
|
||||||
.. code-block:: bash
|
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/simple_universe.py>`_
|
||||||
|
|
||||||
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.utils.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')
|
|
||||||
|
|
||||||
|
.. literalinclude:: ../../catalyst/examples/simple_universe.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
|
||||||
.. _portfolio_optimization:
|
.. _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:
|
in writting the following article:
|
||||||
`Markowitz Portfolio Optimization for Cryptocurrencies <https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556>`_.
|
`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>`_
|
||||||
|
|
||||||
'''
|
.. literalinclude:: ../../catalyst/examples/portfolio_optimization.py
|
||||||
You can run this code using the Python interpreter:
|
:language: python
|
||||||
|
|
||||||
$ 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, )
|
|
||||||
|
|
||||||
.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ.
|
.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ.
|
||||||
:align: center
|
:align: center
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
.. include:: ../../README.rst
|
.. include:: ../../README.rst
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|||||||
+56
-12
@@ -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
|
smaller size) than its big brother Anaconda, but it still contains all the
|
||||||
main packages needed. To install MiniConda, you can follow these steps:
|
main packages needed. To install MiniConda, you can follow these steps:
|
||||||
|
|
||||||
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select Python 2.7
|
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select either
|
||||||
for your Operating System.
|
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
|
2. Install MiniConda. See the `Installation Instructions
|
||||||
<https://conda.io/docs/user-guide/install/index.html>`_ if you need help.
|
<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
|
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:
|
Once either Conda or MiniConda has been set up you can install Catalyst:
|
||||||
|
|
||||||
1. Download the file `python2.7-environment.yml
|
1. Download the file `python3.6-environment.yml
|
||||||
<https://github.com/enigmampc/catalyst/blob/master/etc/python2.7-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 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
|
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.
|
``.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
|
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.
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
conda env create -f python2.7-environment.yml
|
conda env create -f python2.7-environment.yml
|
||||||
@@ -122,10 +133,18 @@ with the following steps:
|
|||||||
|
|
||||||
2. Create the environment:
|
2. Create the environment:
|
||||||
|
|
||||||
|
for python 2.7:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
conda create --name catalyst python=2.7 scipy zlib
|
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:
|
3. Activate the environment:
|
||||||
|
|
||||||
**Linux or MacOS:**
|
**Linux or MacOS:**
|
||||||
@@ -295,10 +314,20 @@ Troubleshooting ``pip`` Install
|
|||||||
|
|
||||||
$ 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:
|
.. _pipenv:
|
||||||
|
|
||||||
Installing with ``pipenv``
|
Installing with ``pipenv``
|
||||||
-------------------------
|
--------------------------
|
||||||
|
|
||||||
Installing Catalyst via ``pipenv`` is perhaps easier that installing it via
|
Installing Catalyst via ``pipenv`` is perhaps easier that installing it via
|
||||||
``pip`` itself but you need to install ``pipenv`` first via ``pip``.
|
``pip`` itself but you need to install ``pipenv`` first via ``pip``.
|
||||||
@@ -443,12 +472,22 @@ about matplotlib backends, please refer to the
|
|||||||
Windows Requirements
|
Windows Requirements
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
In Windows, you will first need to install the `Microsoft Visual C++ Compiler
|
In Windows, you will first need to install the Microsoft Visual C++ Compiler,
|
||||||
for Python 2.7
|
which is different depending on the version of Python that you plan to use:
|
||||||
<https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_. This
|
|
||||||
package contains the compiler and the set of system headers necessary for
|
* Python 3.5, 3.6: `Visual C++ 2015 Build Tools
|
||||||
producing binary wheels for Python 2.7 packages. If it's not already in your
|
<http://landinghub.visualstudio.com/visual-cpp-build-tools>`_,
|
||||||
system, download it and install it before proceeding to the next step.
|
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
|
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
|
to install Catalyst in Windows is to use :ref:`Conda <conda>`. If you didn't
|
||||||
@@ -476,6 +515,7 @@ mentioned above are as follows:
|
|||||||
default you get 0 as the Value Data)
|
default you get 0 as the Value Data)
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
- **The installer has encountered an unexpected error installing this package.
|
- **The installer has encountered an unexpected error installing this package.
|
||||||
This may indicate a problem with this package. The error code is 2503.**
|
This may indicate a problem with this package. The error code is 2503.**
|
||||||
|
|
||||||
@@ -522,6 +562,10 @@ If after following the instructions above, and going through the
|
|||||||
*Troubleshooting* sections, you still experience problems installing Catalyst,
|
*Troubleshooting* sections, you still experience problems installing Catalyst,
|
||||||
you can seek additional help through the following channels:
|
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
|
- Join our `Discord community <https://discord.gg/SJK32GY>`_, and head over
|
||||||
the #catalyst_dev channel where many other users (as well as the project
|
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
|
developers) hang out, and can assist you with your particular issue. The
|
||||||
|
|||||||
@@ -30,22 +30,24 @@ Paper Trading vs Live Trading modes
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Catalyst currently supports three different modes in which you can execute your
|
Catalyst currently supports three different modes in which you can execute your
|
||||||
trading algorithm. The first is backtesting, which is covered extensively in the
|
trading algorithm. The first is **backtesting**, which is covered extensively in
|
||||||
tutorial, and uses historical data to run your algorithm. There is no
|
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
|
interaction with the exchange in backtesting mode, and this is the first mode
|
||||||
that you should test any new algorithm.
|
that you should test any new algorithm.
|
||||||
|
|
||||||
Once you are confident with the simulations that you have obtained with your
|
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
|
algorithm in backtesting, you may switch to live trading, where you have two
|
||||||
different modes:
|
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
|
* **Paper Trading**: The simulated algorithm runs in real time, and fetches
|
||||||
exchange, and are instead kept within Catalyst and simulated. No real currency
|
pricing data in real time from the exchange, but the orders never reach the
|
||||||
is bought or sold. Think of it as a `backtesting happening in real time`.
|
exchange, and are instead kept within Catalyst and simulated. No real currency
|
||||||
* *Live Trading*: This is the proper live trading mode in which an algorithm
|
is bought or sold. Think of it as a `backtesting happening in real time`.
|
||||||
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
|
* **Live Trading**: This is the proper live trading mode in which an algorithm
|
||||||
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:
|
These three modes are controlled by the following variables:
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention.
|
|||||||
|
|
||||||
Here are some examples:
|
Here are some examples:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code:: python
|
||||||
|
|
||||||
# With Bitfinex
|
# With Bitfinex
|
||||||
bitcoin_usd_asset = symbol('btc_usd')
|
bitcoin_usd_asset = symbol('btc_usd')
|
||||||
@@ -174,6 +176,22 @@ Here is the breakdown of the new arguments:
|
|||||||
- ``simulate_orders``: Enables the paper trading mode, in which orders are
|
- ``simulate_orders``: Enables the paper trading mode, in which orders are
|
||||||
simulated in Catalyst instead of processed on the exchange. It defaults to
|
simulated in Catalyst instead of processed on the exchange. It defaults to
|
||||||
``True``.
|
``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:
|
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>`_
|
`Buy Low and Sell High <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_low_sell_high_live.py>`_
|
||||||
|
|||||||
@@ -2,6 +2,93 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
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
|
Version 0.5.2
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
**Release Date**: 2018-02-08
|
**Release Date**: 2018-02-08
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Installation: MacOS
|
|||||||
|
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
|
|
||||||
Installation: Windows
|
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>
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/H8HqcEbZmkk" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
Where things don't:
|
Where things don't:
|
||||||
|
|
||||||
.. raw:: html
|
.. raw:: html
|
||||||
@@ -29,6 +31,7 @@ Where things don't:
|
|||||||
|
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
|
|
||||||
Backtesting a Strategy
|
Backtesting a Strategy
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@@ -44,6 +47,7 @@ sell. Hopefully, we’ll ride the waves.
|
|||||||
|
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
|
|
||||||
Live Trading a Strategy
|
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
|
.. raw:: html
|
||||||
|
|
||||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/NupiE-Xuglw" frameborder="0" allowfullscreen></iframe>
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/NupiE-Xuglw" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
name: catalyst
|
name: catalyst
|
||||||
channels:
|
channels:
|
||||||
- defaults
|
- defaults
|
||||||
|
- conda-forge
|
||||||
dependencies:
|
dependencies:
|
||||||
- certifi=2016.2.28=py27_0
|
- certifi=2016.2.28=py27_0
|
||||||
- mkl=2017.0.3
|
- mkl=2017.0.3
|
||||||
@@ -20,8 +21,10 @@ dependencies:
|
|||||||
- bcolz==0.12.1
|
- bcolz==0.12.1
|
||||||
- bottleneck==1.2.1
|
- bottleneck==1.2.1
|
||||||
- chardet==3.0.4
|
- chardet==3.0.4
|
||||||
- ccxt==1.10.1049
|
- ccxt==1.10.1094
|
||||||
- web3==4.0.0b7
|
# 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
|
- requests-toolbelt==0.8.0
|
||||||
- click==6.7
|
- click==6.7
|
||||||
- contextlib2==0.5.5
|
- contextlib2==0.5.5
|
||||||
@@ -36,7 +39,7 @@ dependencies:
|
|||||||
- lru-dict==1.1.6
|
- lru-dict==1.1.6
|
||||||
- mako==1.0.7
|
- mako==1.0.7
|
||||||
- markupsafe==1.0
|
- markupsafe==1.0
|
||||||
- matplotlib==2.1.0
|
- matplotlib==2.1.2
|
||||||
- multipledispatch==0.4.9
|
- multipledispatch==0.4.9
|
||||||
- networkx==2.0
|
- networkx==2.0
|
||||||
- numexpr==2.6.4
|
- numexpr==2.6.4
|
||||||
@@ -59,4 +62,4 @@ dependencies:
|
|||||||
- tables==3.4.2
|
- tables==3.4.2
|
||||||
- toolz==0.8.2
|
- toolz==0.8.2
|
||||||
- urllib3==1.22
|
- urllib3==1.22
|
||||||
- enigma-catalyst>=0.3
|
- enigma-catalyst>=0.5
|
||||||
|
|||||||
@@ -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.19.2
|
||||||
|
- 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
|
||||||
@@ -81,8 +81,8 @@ empyrical==0.2.1
|
|||||||
tables==3.3.0
|
tables==3.3.0
|
||||||
|
|
||||||
#Catalyst dependencies
|
#Catalyst dependencies
|
||||||
ccxt==1.10.1049
|
ccxt==1.10.1094
|
||||||
boto3==1.4.8
|
boto3==1.4.8
|
||||||
redo==1.6
|
redo==1.6
|
||||||
web3==4.0.0b7
|
web3==4.0.0b11; python_version > '3.4'
|
||||||
requests-toolbelt==0.8.0
|
requests-toolbelt==0.8.0
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ babel==1.3
|
|||||||
docutils==0.12
|
docutils==0.12
|
||||||
snowballstemmer==1.2.0
|
snowballstemmer==1.2.0
|
||||||
sphinx-rtd-theme==0.1.8
|
sphinx-rtd-theme==0.1.8
|
||||||
sphinx==1.3.4
|
sphinx==1.6.7
|
||||||
pbr==1.10.0
|
pbr==1.10.0
|
||||||
|
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Sphinx>=1.3.2
|
Sphinx==1.6.7
|
||||||
numpydoc>=0.5.0
|
numpydoc>=0.5.0
|
||||||
sphinx-autobuild==0.6.0
|
sphinx-autobuild==0.6.0
|
||||||
docutils==0.12
|
docutils==0.12
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
web3==4.0.0b7
|
|
||||||
requests-toolbelt==0.8.0
|
|
||||||
@@ -11,7 +11,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle, \
|
|||||||
BUNDLE_NAME_TEMPLATE
|
BUNDLE_NAME_TEMPLATE
|
||||||
from catalyst.exchange.utils.bundle_utils import get_bcolz_chunk, \
|
from catalyst.exchange.utils.bundle_utils import get_bcolz_chunk, \
|
||||||
get_df_from_arrays
|
get_df_from_arrays
|
||||||
from exchange.utils.datetime_utils import get_start_dt
|
from catalyst.exchange.utils.datetime_utils import get_start_dt
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
||||||
from catalyst.exchange.utils.factory import get_exchange
|
from catalyst.exchange.utils.factory import get_exchange
|
||||||
from catalyst.exchange.utils.stats_utils import df_to_string
|
from catalyst.exchange.utils.stats_utils import df_to_string
|
||||||
@@ -42,7 +42,7 @@ class TestExchangeBundle:
|
|||||||
|
|
||||||
def test_ingest_minute(self):
|
def test_ingest_minute(self):
|
||||||
data_frequency = 'minute'
|
data_frequency = 'minute'
|
||||||
exchange_name = 'poloniex'
|
exchange_name = 'binance'
|
||||||
|
|
||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
@@ -50,8 +50,8 @@ class TestExchangeBundle:
|
|||||||
exchange.get_asset('eth_btc')
|
exchange.get_asset('eth_btc')
|
||||||
]
|
]
|
||||||
|
|
||||||
start = pd.to_datetime('2016-03-01', utc=True)
|
start = pd.to_datetime('2018-03-01', utc=True)
|
||||||
end = pd.to_datetime('2017-11-1', utc=True)
|
end = pd.to_datetime('2018-03-8', utc=True)
|
||||||
|
|
||||||
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
||||||
exchange_bundle.ingest(
|
exchange_bundle.ingest(
|
||||||
@@ -101,7 +101,7 @@ class TestExchangeBundle:
|
|||||||
# data_frequency = 'daily'
|
# data_frequency = 'daily'
|
||||||
# include_symbols = 'neo_btc,bch_btc,eth_btc'
|
# include_symbols = 'neo_btc,bch_btc,eth_btc'
|
||||||
|
|
||||||
exchange_name = 'bitfinex'
|
exchange_name = 'binance'
|
||||||
data_frequency = 'minute'
|
data_frequency = 'minute'
|
||||||
|
|
||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
from catalyst.exchange.utils.exchange_utils import transform_candles_to_df, \
|
||||||
|
forward_fill_df_if_needed, get_candles_df
|
||||||
|
|
||||||
|
from catalyst.testing.fixtures import WithLogger, ZiplineTestCase
|
||||||
|
from datetime import timedelta
|
||||||
|
from pandas import Timestamp, DataFrame, concat
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class TestExchangeUtils(WithLogger, ZiplineTestCase):
|
||||||
|
@classmethod
|
||||||
|
def get_specific_field_from_df(cls, df, field, asset):
|
||||||
|
new_df = DataFrame(df[field])
|
||||||
|
new_df.columns = [asset]
|
||||||
|
new_df.index.name = None
|
||||||
|
return new_df
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify_forward_fill_df_if_needed(cls, candles, periods, expected_df):
|
||||||
|
observed_df = forward_fill_df_if_needed(
|
||||||
|
transform_candles_to_df(candles),
|
||||||
|
periods)
|
||||||
|
assert (expected_df.equals(observed_df))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify_get_candles_df(cls, assets, candles, end_fixed_dt,
|
||||||
|
expected_df, check_next_candle=False):
|
||||||
|
# run on all the fields
|
||||||
|
for field in ['volume', 'open', 'close', 'high', 'low']:
|
||||||
|
|
||||||
|
field_dt = cls.get_specific_field_from_df(expected_df,
|
||||||
|
field,
|
||||||
|
assets[0])
|
||||||
|
# run on several timestamps
|
||||||
|
for delta in range(5):
|
||||||
|
end_dt = end_fixed_dt + timedelta(minutes=delta)
|
||||||
|
assert (field_dt.equals(get_candles_df({assets[0]: candles},
|
||||||
|
field, '5T', 3,
|
||||||
|
end_dt=end_dt)))
|
||||||
|
|
||||||
|
field_dt_a1 = cls.get_specific_field_from_df(expected_df,
|
||||||
|
field,
|
||||||
|
assets[0])
|
||||||
|
field_dt_a2 = cls.get_specific_field_from_df(expected_df,
|
||||||
|
field,
|
||||||
|
assets[1])
|
||||||
|
observed_df = get_candles_df({assets[0]: candles,
|
||||||
|
assets[1]: candles},
|
||||||
|
field, '5T', 3,
|
||||||
|
end_dt=end_dt)
|
||||||
|
|
||||||
|
assert (observed_df.equals(concat([field_dt_a1, field_dt_a2],
|
||||||
|
axis=1)))
|
||||||
|
|
||||||
|
if check_next_candle:
|
||||||
|
# one candle forward
|
||||||
|
end_dt = end_fixed_dt + timedelta(minutes=6)
|
||||||
|
observed_df = get_candles_df({assets[0]: candles,
|
||||||
|
assets[1]: candles},
|
||||||
|
field, '5T', 3,
|
||||||
|
end_dt=end_dt)
|
||||||
|
|
||||||
|
assert (not observed_df.equals(concat([field_dt_a1,
|
||||||
|
field_dt_a2],
|
||||||
|
axis=1)))
|
||||||
|
assert (concat([field_dt_a1, field_dt_a2],
|
||||||
|
axis=1)[1:].equals(observed_df[:-1]))
|
||||||
|
|
||||||
|
def test_get_candles_df(self):
|
||||||
|
assets = ['btc_usdt', 'eth_usdt']
|
||||||
|
|
||||||
|
# test forward fill in the end
|
||||||
|
candles = [{'high': 595, 'volume': 10, 'low': 594,
|
||||||
|
'close': 595, 'open': 594,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:45:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594, 'volume': 108, 'low': 592,
|
||||||
|
'close': 593, 'open': 592,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:50:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
expected = [{'high': 595.0, 'volume': 10.0, 'low': 594.0,
|
||||||
|
'close': 595.0, 'open': 594.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:45:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594.0, 'volume': 108.0, 'low': 592.0,
|
||||||
|
'close': 593.0, 'open': 592.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:50:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 593.0, 'volume': 0.0, 'low': 593.0,
|
||||||
|
'close': 593.0, 'open': 593.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:55:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
periods = [Timestamp('2018-03-01 09:45:00+0000', tz='UTC'),
|
||||||
|
Timestamp('2018-03-01 09:50:00+0000', tz='UTC'),
|
||||||
|
Timestamp('2018-03-01 09:55:00+0000', tz='UTC')]
|
||||||
|
|
||||||
|
expected_df = transform_candles_to_df(expected)
|
||||||
|
|
||||||
|
self.verify_forward_fill_df_if_needed(candles, periods,
|
||||||
|
expected_df)
|
||||||
|
self.verify_get_candles_df(assets, candles, periods[2],
|
||||||
|
expected_df, True)
|
||||||
|
|
||||||
|
# test forward fill in the middle
|
||||||
|
candles = [{'high': 595, 'volume': 10, 'low': 594,
|
||||||
|
'close': 595, 'open': 594,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:45:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594, 'volume': 108, 'low': 592,
|
||||||
|
'close': 593, 'open': 592,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:55:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
expected = [{'high': 595.0, 'volume': 10.0, 'low': 594.0,
|
||||||
|
'close': 595.0, 'open': 594.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:45:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 595.0, 'volume': 0.0, 'low': 595.0,
|
||||||
|
'close': 595.0, 'open': 595.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:50:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594.0, 'volume': 108.0, 'low': 592.0,
|
||||||
|
'close': 593.0, 'open': 592.0,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:55:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
expected_df = transform_candles_to_df(expected)
|
||||||
|
self.verify_forward_fill_df_if_needed(candles, periods, expected_df)
|
||||||
|
self.verify_get_candles_df(assets, candles, periods[2], expected_df)
|
||||||
|
|
||||||
|
# test "forward fill" at the beginning
|
||||||
|
candles = [{'high': 595, 'volume': 10, 'low': 594,
|
||||||
|
'close': 595, 'open': 594,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:50:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594, 'volume': 108, 'low': 592,
|
||||||
|
'close': 593, 'open': 592,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:55:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
expected = [{'high': np.NaN, 'volume': 0.0, 'low': np.NaN,
|
||||||
|
'close': np.NaN, 'open': np.NaN,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:45:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 595, 'volume': 10, 'low': 594,
|
||||||
|
'close': 595, 'open': 594,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:50:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
},
|
||||||
|
{'high': 594, 'volume': 108, 'low': 592,
|
||||||
|
'close': 593, 'open': 592,
|
||||||
|
'last_traded': Timestamp('2018-03-01 09:55:00+0000',
|
||||||
|
tz='UTC')
|
||||||
|
}]
|
||||||
|
|
||||||
|
expected_df = transform_candles_to_df(expected)
|
||||||
|
self.verify_forward_fill_df_if_needed(candles, periods, expected_df)
|
||||||
|
# Not the same due to dropna - commenting out for now
|
||||||
|
# self.verify_get_candles_df(assets, candles, periods[2], expected_df)
|
||||||
@@ -2,6 +2,7 @@ import random
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from datetime import timedelta
|
||||||
from logbook import TestHandler
|
from logbook import TestHandler
|
||||||
from pandas.util.testing import assert_frame_equal
|
from pandas.util.testing import assert_frame_equal
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ from catalyst.exchange.utils.exchange_utils import get_candles_df
|
|||||||
from catalyst.exchange.utils.factory import get_exchange
|
from catalyst.exchange.utils.factory import get_exchange
|
||||||
from catalyst.exchange.utils.test_utils import output_df, \
|
from catalyst.exchange.utils.test_utils import output_df, \
|
||||||
select_random_assets
|
select_random_assets
|
||||||
|
from catalyst.exchange.utils.stats_utils import set_print_settings
|
||||||
|
|
||||||
pd.set_option('display.expand_frame_repr', False)
|
pd.set_option('display.expand_frame_repr', False)
|
||||||
pd.set_option('precision', 8)
|
pd.set_option('precision', 8)
|
||||||
@@ -35,7 +37,7 @@ class TestSuiteBundle:
|
|||||||
return data_portal
|
return data_portal
|
||||||
|
|
||||||
def compare_bundle_with_exchange(self, exchange, assets, end_dt, bar_count,
|
def compare_bundle_with_exchange(self, exchange, assets, end_dt, bar_count,
|
||||||
freq, data_frequency, data_portal):
|
freq, data_frequency, data_portal, field):
|
||||||
"""
|
"""
|
||||||
Creates DataFrames from the bundle and exchange for the specified
|
Creates DataFrames from the bundle and exchange for the specified
|
||||||
data set.
|
data set.
|
||||||
@@ -58,14 +60,26 @@ class TestSuiteBundle:
|
|||||||
|
|
||||||
log_catcher = TestHandler()
|
log_catcher = TestHandler()
|
||||||
with log_catcher:
|
with log_catcher:
|
||||||
|
symbols = [asset.symbol for asset in assets]
|
||||||
|
print(
|
||||||
|
'comparing {} for {}/{} with {} timeframe until {}'.format(
|
||||||
|
field, exchange.name, symbols, freq, end_dt
|
||||||
|
)
|
||||||
|
)
|
||||||
data['bundle'] = data_portal.get_history_window(
|
data['bundle'] = data_portal.get_history_window(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
frequency=freq,
|
frequency=freq,
|
||||||
field='close',
|
field=field,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
)
|
)
|
||||||
|
set_print_settings()
|
||||||
|
print(
|
||||||
|
'the bundle data:\n{}'.format(
|
||||||
|
data['bundle']
|
||||||
|
)
|
||||||
|
)
|
||||||
candles = exchange.get_candles(
|
candles = exchange.get_candles(
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
freq=freq,
|
freq=freq,
|
||||||
@@ -74,11 +88,16 @@ class TestSuiteBundle:
|
|||||||
)
|
)
|
||||||
data['exchange'] = get_candles_df(
|
data['exchange'] = get_candles_df(
|
||||||
candles=candles,
|
candles=candles,
|
||||||
field='close',
|
field=field,
|
||||||
freq=freq,
|
freq=freq,
|
||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
)
|
)
|
||||||
|
print(
|
||||||
|
'the exchange data:\n{}'.format(
|
||||||
|
data['exchange']
|
||||||
|
)
|
||||||
|
)
|
||||||
for source in data:
|
for source in data:
|
||||||
df = data[source]
|
df = data[source]
|
||||||
path, folder = output_df(
|
path, folder = output_df(
|
||||||
@@ -88,24 +107,85 @@ class TestSuiteBundle:
|
|||||||
print('saved {} test results: {}'.format(end_dt, folder))
|
print('saved {} test results: {}'.format(end_dt, folder))
|
||||||
|
|
||||||
assert_frame_equal(
|
assert_frame_equal(
|
||||||
right=data['bundle'],
|
right=data['bundle'][:-1],
|
||||||
left=data['exchange'],
|
left=data['exchange'][:-1],
|
||||||
check_less_precise=1,
|
check_less_precise=1,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
assert_frame_equal(
|
assert_frame_equal(
|
||||||
right=data['bundle'],
|
right=data['bundle'][:-1],
|
||||||
left=data['exchange'],
|
left=data['exchange'][:-1],
|
||||||
check_less_precise=min([a.decimals for a in assets]),
|
check_less_precise=min([a.decimals for a in assets]),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Some differences were found within a 1 decimal point '
|
print(
|
||||||
'interval of confidence: {}'.format(e))
|
'Some differences were found within a 1 decimal point '
|
||||||
|
'interval of confidence: {}'.format(e)
|
||||||
|
)
|
||||||
with open(os.path.join(folder, 'compare.txt'), 'w+') as handle:
|
with open(os.path.join(folder, 'compare.txt'), 'w+') as handle:
|
||||||
handle.write(e.args[0])
|
handle.write(e.args[0])
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def compare_current_with_last_candle(self, exchange, assets, end_dt,
|
||||||
|
freq, data_frequency, data_portal):
|
||||||
|
"""
|
||||||
|
Creates DataFrames from the bundle and exchange for the specified
|
||||||
|
data set.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange: Exchange
|
||||||
|
assets
|
||||||
|
end_dt
|
||||||
|
bar_count
|
||||||
|
freq
|
||||||
|
data_frequency
|
||||||
|
data_portal
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
|
data = dict()
|
||||||
|
|
||||||
|
assets = sorted(assets, key=lambda a: a.symbol)
|
||||||
|
log_catcher = TestHandler()
|
||||||
|
with log_catcher:
|
||||||
|
symbols = [asset.symbol for asset in assets]
|
||||||
|
print(
|
||||||
|
'comparing data for {}/{} with {} timeframe on {}'.format(
|
||||||
|
exchange.name, symbols, freq, end_dt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data['candle'] = data_portal.get_history_window(
|
||||||
|
assets=assets,
|
||||||
|
end_dt=end_dt,
|
||||||
|
bar_count=1,
|
||||||
|
frequency=freq,
|
||||||
|
field='close',
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
)
|
||||||
|
set_print_settings()
|
||||||
|
print(
|
||||||
|
'the bundle first / last row:\n{}'.format(
|
||||||
|
data['candle'].iloc[[-1]]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
current = data_portal.get_spot_value(
|
||||||
|
assets=assets,
|
||||||
|
field='close',
|
||||||
|
dt=end_dt,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
)
|
||||||
|
data['current'] = pd.Series(data=current, index=assets)
|
||||||
|
print(
|
||||||
|
'the current price:\n{}'.format(
|
||||||
|
data['current']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
def test_validate_bundles(self):
|
def test_validate_bundles(self):
|
||||||
# exchange_population = 3
|
# exchange_population = 3
|
||||||
asset_population = 3
|
asset_population = 3
|
||||||
@@ -125,8 +205,11 @@ class TestSuiteBundle:
|
|||||||
|
|
||||||
frequencies = exchange.get_candle_frequencies(data_frequency)
|
frequencies = exchange.get_candle_frequencies(data_frequency)
|
||||||
freq = random.sample(frequencies, 1)[0]
|
freq = random.sample(frequencies, 1)[0]
|
||||||
|
rnd = random.SystemRandom()
|
||||||
|
# field = rnd.choice(['open', 'high', 'low', 'close', 'volume'])
|
||||||
|
field = rnd.choice(['volume'])
|
||||||
|
|
||||||
bar_count = random.randint(1, 10)
|
bar_count = random.randint(3, 6)
|
||||||
|
|
||||||
assets = select_random_assets(
|
assets = select_random_assets(
|
||||||
exchange.assets, asset_population
|
exchange.assets, asset_population
|
||||||
@@ -139,6 +222,7 @@ class TestSuiteBundle:
|
|||||||
if end_dt is None or asset_end_dt < end_dt:
|
if end_dt is None or asset_end_dt < end_dt:
|
||||||
end_dt = asset_end_dt
|
end_dt = asset_end_dt
|
||||||
|
|
||||||
|
end_dt = end_dt + timedelta(minutes=3)
|
||||||
dt_range = pd.date_range(
|
dt_range = pd.date_range(
|
||||||
end=end_dt, periods=bar_count, freq=freq
|
end=end_dt, periods=bar_count, freq=freq
|
||||||
)
|
)
|
||||||
@@ -150,5 +234,48 @@ class TestSuiteBundle:
|
|||||||
freq=freq,
|
freq=freq,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
data_portal=data_portal,
|
data_portal=data_portal,
|
||||||
|
field=field,
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_validate_last_candle(self):
|
||||||
|
# exchange_population = 3
|
||||||
|
asset_population = 3
|
||||||
|
data_frequency = random.choice(['minute'])
|
||||||
|
|
||||||
|
# bundle = 'dailyBundle' if data_frequency
|
||||||
|
# == 'daily' else 'minuteBundle'
|
||||||
|
# exchanges = select_random_exchanges(
|
||||||
|
# population=exchange_population,
|
||||||
|
# features=[bundle],
|
||||||
|
# ) # Type: list[Exchange]
|
||||||
|
exchanges = [get_exchange('poloniex', skip_init=True)]
|
||||||
|
|
||||||
|
data_portal = TestSuiteBundle.get_data_portal(exchanges)
|
||||||
|
for exchange in exchanges:
|
||||||
|
exchange.init()
|
||||||
|
|
||||||
|
frequencies = exchange.get_candle_frequencies(data_frequency)
|
||||||
|
freq = random.sample(frequencies, 1)[0]
|
||||||
|
|
||||||
|
assets = select_random_assets(
|
||||||
|
exchange.assets, asset_population
|
||||||
|
)
|
||||||
|
end_dt = None
|
||||||
|
for asset in assets:
|
||||||
|
attribute = 'end_{}'.format(data_frequency)
|
||||||
|
asset_end_dt = getattr(asset, attribute)
|
||||||
|
|
||||||
|
if end_dt is None or asset_end_dt < end_dt:
|
||||||
|
end_dt = asset_end_dt
|
||||||
|
|
||||||
|
end_dt = end_dt + timedelta(minutes=3)
|
||||||
|
self.compare_current_with_last_candle(
|
||||||
|
exchange=exchange,
|
||||||
|
assets=assets,
|
||||||
|
end_dt=end_dt,
|
||||||
|
freq=freq,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
data_portal=data_portal,
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from catalyst.marketplace.marketplace import Marketplace
|
from catalyst.marketplace.marketplace import Marketplace
|
||||||
from catalyst.testing.fixtures import WithLogger, ZiplineTestCase
|
from catalyst.testing.fixtures import WithLogger, ZiplineTestCase
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
|
|
||||||
class TestMarketplace(WithLogger, ZiplineTestCase):
|
class TestMarketplace(WithLogger, ZiplineTestCase):
|
||||||
@@ -16,12 +15,12 @@ class TestMarketplace(WithLogger, ZiplineTestCase):
|
|||||||
|
|
||||||
def test_subscribe(self):
|
def test_subscribe(self):
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
marketplace.subscribe('marketcap2222')
|
marketplace.subscribe('marketcap')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_ingest(self):
|
def test_ingest(self):
|
||||||
marketplace = Marketplace()
|
marketplace = Marketplace()
|
||||||
ds_def = marketplace.ingest('marketcap1234')
|
ds_def = marketplace.ingest('marketcap')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_publish(self):
|
def test_publish(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user