mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
Compare commits
2
Commits
0.4.0
..
unit-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c571c1c2ae | ||
|
|
f279101393 |
+3
-72
@@ -1,72 +1,3 @@
|
|||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
|
All the documentation for `Catalyst <https://github.com/enigmampc/catalyst>`_
|
||||||
:target: https://enigmampc.github.io/catalyst
|
can be found in the
|
||||||
:align: center
|
`documentation website <https://enigmampc.github.io/catalyst>`_.
|
||||||
:alt: Enigma | Catalyst
|
|
||||||
|
|
||||||
|version tag|
|
|
||||||
|version status|
|
|
||||||
|discord|
|
|
||||||
|twitter|
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
Catalyst is an algorithmic trading library for crypto-assets written in Python.
|
|
||||||
It allows trading strategies to be easily expressed and backtested against
|
|
||||||
historical data (with daily and minute resolution), providing analytics and
|
|
||||||
insights regarding a particular strategy's performance. Catalyst also supports
|
|
||||||
live-trading of crypto-assets starting with three exchanges (Bitfinex, Bittrex,
|
|
||||||
and Poloniex) with more being added over time. Catalyst empowers users to share
|
|
||||||
and curate data and build profitable, data-driven investment strategies. Please
|
|
||||||
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst, or
|
|
||||||
refer to the `whitepaper <https://www.enigma.co/enigma_catalyst.pdf>`_ for
|
|
||||||
further technical details.
|
|
||||||
|
|
||||||
Catalyst builds on top of the well-established
|
|
||||||
`Zipline <https://github.com/quantopian/zipline>`_ project. We did our best to
|
|
||||||
minimize structural changes to the general API to maximize compatibility with
|
|
||||||
existing trading algorithms, developer knowledge, and tutorials. Join us on
|
|
||||||
`Discord <https://discord.gg/SJK32GY>`_ where we have a *#catalyst_dev* channel
|
|
||||||
for questions around Catalyst, algorithmic trading and technical support.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
- Ease of use: Catalyst tries to get out of your way so that you can
|
|
||||||
focus on algorithm development. See
|
|
||||||
`examples of trading strategies <https://github.com/enigmampc/catalyst/tree/master/catalyst/examples>`_
|
|
||||||
provided.
|
|
||||||
- Support for several of the top crypto-exchanges by trading volume:
|
|
||||||
`Bitfinex <https://www.bitfinex.com>`_, `Bittrex <http://www.bittrex.com>`_,
|
|
||||||
and `Poloniex <https://www.poloniex.com>`_.
|
|
||||||
- Secure: You and only you have access to each exchange API keys for your accounts.
|
|
||||||
- Input of historical pricing data of all crypto-assets by exchange,
|
|
||||||
with daily and minute resolution. See
|
|
||||||
`Catalyst Market Coverage Overview <https://www.enigma.co/catalyst/status>`_.
|
|
||||||
- Backtesting and live-trading functionality, with a seamless transition
|
|
||||||
between the two modes.
|
|
||||||
- Output of performance statistics are based on Pandas DataFrames to
|
|
||||||
integrate nicely into the existing PyData eco-system.
|
|
||||||
- Statistic and machine learning libraries like matplotlib, scipy,
|
|
||||||
statsmodels, and sklearn support development, analysis, and
|
|
||||||
visualization of state-of-the-art trading systems.
|
|
||||||
- Addition of Bitcoin price (btc_usdt) as a benchmark for comparing
|
|
||||||
performance across trading algorithms.
|
|
||||||
|
|
||||||
Go to our `Documentation Website <https://enigmampc.github.io/catalyst/>`_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. |version tag| image:: https://img.shields.io/pypi/v/enigma-catalyst.svg
|
|
||||||
:target: https://pypi.python.org/pypi/enigma-catalyst
|
|
||||||
|
|
||||||
.. |version status| image:: https://img.shields.io/pypi/pyversions/enigma-catalyst.svg
|
|
||||||
:target: https://pypi.python.org/pypi/enigma-catalyst
|
|
||||||
|
|
||||||
.. |discord| image:: https://img.shields.io/badge/discord-join%20chat-green.svg
|
|
||||||
:target: https://discordapp.com/invite/SJK32GY
|
|
||||||
|
|
||||||
.. |twitter| image:: https://img.shields.io/twitter/follow/enigmampc.svg?style=social&label=Follow&style=flat-square
|
|
||||||
:target: https://twitter.com/enigmampc
|
|
||||||
|
|
||||||
|
|
||||||
+10
-4
@@ -29,14 +29,11 @@ from ._version import get_versions
|
|||||||
from . algorithm import TradingAlgorithm
|
from . algorithm import TradingAlgorithm
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher
|
|
||||||
|
|
||||||
__version__ = get_versions()['version']
|
|
||||||
del get_versions
|
|
||||||
|
|
||||||
# PERF: Fire a warning if calendars were instantiated during catalyst import.
|
# PERF: Fire a warning if calendars were instantiated during catalyst import.
|
||||||
# Having calendars doesn't break anything per-se, but it makes catalyst imports
|
# Having calendars doesn't break anything per-se, but it makes catalyst imports
|
||||||
# noticeably slower, which becomes particularly noticeable in the Zipline CLI.
|
# noticeably slower, which becomes particularly noticeable in the Zipline CLI.
|
||||||
|
from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher
|
||||||
if global_calendar_dispatcher._calendars:
|
if global_calendar_dispatcher._calendars:
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
@@ -47,6 +44,10 @@ if global_calendar_dispatcher._calendars:
|
|||||||
del global_calendar_dispatcher
|
del global_calendar_dispatcher
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = get_versions()['version']
|
||||||
|
del get_versions
|
||||||
|
|
||||||
|
|
||||||
def load_ipython_extension(ipython):
|
def load_ipython_extension(ipython):
|
||||||
from .__main__ import catalyst_magic
|
from .__main__ import catalyst_magic
|
||||||
ipython.register_magic_function(catalyst_magic, 'line_cell', 'catalyst')
|
ipython.register_magic_function(catalyst_magic, 'line_cell', 'catalyst')
|
||||||
@@ -68,6 +69,7 @@ if os.name == 'nt':
|
|||||||
_()
|
_()
|
||||||
del _
|
del _
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TradingAlgorithm',
|
'TradingAlgorithm',
|
||||||
'api',
|
'api',
|
||||||
@@ -78,3 +80,7 @@ __all__ = [
|
|||||||
'run_algorithm',
|
'run_algorithm',
|
||||||
'utils',
|
'utils',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
from ._version import get_versions
|
||||||
|
__version__ = get_versions()['version']
|
||||||
|
del get_versions
|
||||||
|
|||||||
+23
-44
@@ -10,6 +10,7 @@ from six import text_type
|
|||||||
from catalyst.data import bundles as bundles_module
|
from catalyst.data import bundles as bundles_module
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_utils import delete_algo_folder
|
from catalyst.exchange.exchange_utils import delete_algo_folder
|
||||||
|
from catalyst.exchange.factory import get_exchange
|
||||||
from catalyst.utils.cli import Date, Timestamp
|
from catalyst.utils.cli import Date, Timestamp
|
||||||
from catalyst.utils.run_algo import _run, load_extensions
|
from catalyst.utils.run_algo import _run, load_extensions
|
||||||
|
|
||||||
@@ -193,7 +194,9 @@ def ipython_only(option):
|
|||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
'--exchange-name',
|
'--exchange-name',
|
||||||
help='The name of the targeted exchange.',
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the targeted exchange (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-n',
|
'-n',
|
||||||
@@ -255,9 +258,8 @@ def run(ctx,
|
|||||||
ctx.fail("must specify a base currency with '-c' in backtest mode")
|
ctx.fail("must specify a base currency with '-c' in backtest mode")
|
||||||
|
|
||||||
if capital_base is None:
|
if capital_base is None:
|
||||||
ctx.fail("must specify a capital base with '--capital-base'")
|
ctx.fail("must specify a capital base with '--capital-base'"
|
||||||
|
" in backtest mode")
|
||||||
click.echo('Running in backtesting mode.')
|
|
||||||
|
|
||||||
perf = _run(
|
perf = _run(
|
||||||
initialize=None,
|
initialize=None,
|
||||||
@@ -282,9 +284,7 @@ def run(ctx,
|
|||||||
exchange=exchange_name,
|
exchange=exchange_name,
|
||||||
algo_namespace=algo_namespace,
|
algo_namespace=algo_namespace,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
live_graph=False,
|
live_graph=False
|
||||||
simulate_orders=True,
|
|
||||||
stats_output=None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if output == '-':
|
if output == '-':
|
||||||
@@ -336,12 +336,6 @@ def catalyst_magic(line, cell=None):
|
|||||||
type=click.File('r'),
|
type=click.File('r'),
|
||||||
help='The file that contains the algorithm to run.',
|
help='The file that contains the algorithm to run.',
|
||||||
)
|
)
|
||||||
@click.option(
|
|
||||||
'--capital-base',
|
|
||||||
type=float,
|
|
||||||
show_default=True,
|
|
||||||
help='The amount of capital (in base_currency) allocated to trading.',
|
|
||||||
)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
'-t',
|
'-t',
|
||||||
'--algotext',
|
'--algotext',
|
||||||
@@ -380,7 +374,9 @@ def catalyst_magic(line, cell=None):
|
|||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
'--exchange-name',
|
'--exchange-name',
|
||||||
help='The name of the targeted exchange.',
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the targeted exchange (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-n',
|
'-n',
|
||||||
@@ -399,17 +395,9 @@ def catalyst_magic(line, cell=None):
|
|||||||
default=False,
|
default=False,
|
||||||
help='Display live graph.',
|
help='Display live graph.',
|
||||||
)
|
)
|
||||||
@click.option(
|
|
||||||
'--simulate-orders/--no-simulate-orders',
|
|
||||||
is_flag=True,
|
|
||||||
default=True,
|
|
||||||
help='Simulating orders enable the paper trading mode. No orders will be '
|
|
||||||
'sent to the exchange unless set to false.',
|
|
||||||
)
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def live(ctx,
|
def live(ctx,
|
||||||
algofile,
|
algofile,
|
||||||
capital_base,
|
|
||||||
algotext,
|
algotext,
|
||||||
define,
|
define,
|
||||||
output,
|
output,
|
||||||
@@ -418,8 +406,7 @@ def live(ctx,
|
|||||||
exchange_name,
|
exchange_name,
|
||||||
algo_namespace,
|
algo_namespace,
|
||||||
base_currency,
|
base_currency,
|
||||||
live_graph,
|
live_graph):
|
||||||
simulate_orders):
|
|
||||||
"""Trade live with the given algorithm.
|
"""Trade live with the given algorithm.
|
||||||
"""
|
"""
|
||||||
if (algotext is not None) == (algofile is not None):
|
if (algotext is not None) == (algofile is not None):
|
||||||
@@ -430,22 +417,11 @@ def live(ctx,
|
|||||||
|
|
||||||
if exchange_name is None:
|
if exchange_name is None:
|
||||||
ctx.fail("must specify an exchange name '-x'")
|
ctx.fail("must specify an exchange name '-x'")
|
||||||
|
|
||||||
if algo_namespace is None:
|
if algo_namespace is None:
|
||||||
ctx.fail("must specify an algorithm name '-n' in live execution mode")
|
ctx.fail("must specify an algorithm name '-n' in live execution mode")
|
||||||
|
|
||||||
if base_currency is None:
|
if base_currency is None:
|
||||||
ctx.fail("must specify a base currency '-c' in live execution mode")
|
ctx.fail("must specify a base currency '-c' in live execution mode")
|
||||||
|
|
||||||
if capital_base is None:
|
|
||||||
ctx.fail("must specify a capital base with '--capital-base'")
|
|
||||||
|
|
||||||
if simulate_orders:
|
|
||||||
click.echo('Running in paper trading mode.')
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo('Running in live trading mode.')
|
|
||||||
|
|
||||||
perf = _run(
|
perf = _run(
|
||||||
initialize=None,
|
initialize=None,
|
||||||
handle_data=None,
|
handle_data=None,
|
||||||
@@ -455,7 +431,7 @@ def live(ctx,
|
|||||||
algotext=algotext,
|
algotext=algotext,
|
||||||
defines=define,
|
defines=define,
|
||||||
data_frequency=None,
|
data_frequency=None,
|
||||||
capital_base=capital_base,
|
capital_base=None,
|
||||||
data=None,
|
data=None,
|
||||||
bundle=None,
|
bundle=None,
|
||||||
bundle_timestamp=None,
|
bundle_timestamp=None,
|
||||||
@@ -469,9 +445,7 @@ def live(ctx,
|
|||||||
exchange=exchange_name,
|
exchange=exchange_name,
|
||||||
algo_namespace=algo_namespace,
|
algo_namespace=algo_namespace,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
live_graph=live_graph,
|
live_graph=live_graph
|
||||||
simulate_orders=simulate_orders,
|
|
||||||
stats_output=None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if output == '-':
|
if output == '-':
|
||||||
@@ -486,7 +460,9 @@ def live(ctx,
|
|||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
'--exchange-name',
|
'--exchange-name',
|
||||||
help='The name of the exchange bundle to ingest.',
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the exchange bundle to ingest (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-f',
|
'-f',
|
||||||
@@ -544,8 +520,7 @@ def live(ctx,
|
|||||||
default=False,
|
default=False,
|
||||||
help='Report potential anomalies found in data bundles.'
|
help='Report potential anomalies found in data bundles.'
|
||||||
)
|
)
|
||||||
@click.pass_context
|
def ingest_exchange(exchange_name, data_frequency, start, end,
|
||||||
def ingest_exchange(ctx, exchange_name, data_frequency, start, end,
|
|
||||||
include_symbols, exclude_symbols, csv, show_progress,
|
include_symbols, exclude_symbols, csv, show_progress,
|
||||||
verbose, validate):
|
verbose, validate):
|
||||||
"""
|
"""
|
||||||
@@ -590,7 +565,9 @@ def clean_algo(ctx, algo_namespace):
|
|||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
'--exchange-name',
|
'--exchange-name',
|
||||||
help='The name of the exchange bundle to ingest.',
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the exchange bundle to ingest (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-f',
|
'-f',
|
||||||
@@ -629,7 +606,9 @@ def clean_exchange(ctx, exchange_name, data_frequency):
|
|||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
'--exchange-name',
|
'--exchange-name',
|
||||||
help='The name of the exchange bundle to ingest.',
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the exchange bundle to ingest (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-c',
|
'-c',
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ from catalyst.utils.events import (
|
|||||||
from catalyst.utils.factory import create_simulation_parameters
|
from catalyst.utils.factory import create_simulation_parameters
|
||||||
from catalyst.utils.math_utils import (
|
from catalyst.utils.math_utils import (
|
||||||
tolerant_equals,
|
tolerant_equals,
|
||||||
|
round_if_near_integer,
|
||||||
round_nearest
|
round_nearest
|
||||||
)
|
)
|
||||||
from catalyst.utils.pandas_utils import clear_dataframe_indexer_caches
|
from catalyst.utils.pandas_utils import clear_dataframe_indexer_caches
|
||||||
@@ -1484,6 +1485,7 @@ class TradingAlgorithm(object):
|
|||||||
"""
|
"""
|
||||||
Converts the number of shares to the smallest tradable lot size for
|
Converts the number of shares to the smallest tradable lot size for
|
||||||
the asset being ordered.
|
the asset being ordered.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return round_nearest(amount, asset.min_trade_size)
|
return round_nearest(amount, asset.min_trade_size)
|
||||||
|
|
||||||
@@ -1521,7 +1523,6 @@ class TradingAlgorithm(object):
|
|||||||
self.updated_portfolio(),
|
self.updated_portfolio(),
|
||||||
self.get_datetime(),
|
self.get_datetime(),
|
||||||
self.trading_client.current_data)
|
self.trading_client.current_data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __convert_order_params_for_blotter(limit_price, stop_price, style):
|
def __convert_order_params_for_blotter(limit_price, stop_price, style):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -405,9 +405,6 @@ cdef class TradingPair(Asset):
|
|||||||
cdef readonly float taker
|
cdef readonly float taker
|
||||||
cdef readonly int trading_state
|
cdef readonly int trading_state
|
||||||
cdef readonly object data_source
|
cdef readonly object data_source
|
||||||
cdef readonly float max_trade_size
|
|
||||||
cdef readonly float lot
|
|
||||||
cdef readonly int decimals
|
|
||||||
|
|
||||||
_kwargnames = frozenset({
|
_kwargnames = frozenset({
|
||||||
'sid',
|
'sid',
|
||||||
@@ -426,13 +423,10 @@ cdef class TradingPair(Asset):
|
|||||||
'end_minute',
|
'end_minute',
|
||||||
'exchange_symbol',
|
'exchange_symbol',
|
||||||
'min_trade_size',
|
'min_trade_size',
|
||||||
'max_trade_size',
|
|
||||||
'lot',
|
|
||||||
'maker',
|
'maker',
|
||||||
'taker',
|
'taker',
|
||||||
'trading_state',
|
'trading_state',
|
||||||
'data_source',
|
'data_source'
|
||||||
'decimals'
|
|
||||||
})
|
})
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
object symbol,
|
object symbol,
|
||||||
@@ -449,11 +443,8 @@ cdef class TradingPair(Asset):
|
|||||||
object auto_close_date=None,
|
object auto_close_date=None,
|
||||||
object exchange_full=None,
|
object exchange_full=None,
|
||||||
float min_trade_size=0.0001,
|
float min_trade_size=0.0001,
|
||||||
float max_trade_size=1000000,
|
|
||||||
float maker=0.0015,
|
float maker=0.0015,
|
||||||
float taker=0.0025,
|
float taker=0.0025,
|
||||||
float lot=0,
|
|
||||||
int decimals = 8,
|
|
||||||
int trading_state=0,
|
int trading_state=0,
|
||||||
object data_source='catalyst'):
|
object data_source='catalyst'):
|
||||||
"""
|
"""
|
||||||
@@ -518,12 +509,9 @@ cdef class TradingPair(Asset):
|
|||||||
:param auto_close_date:
|
:param auto_close_date:
|
||||||
:param exchange_full:
|
:param exchange_full:
|
||||||
:param min_trade_size:
|
:param min_trade_size:
|
||||||
:param max_trade_size:
|
|
||||||
:param maker:
|
:param maker:
|
||||||
:param taker:
|
:param taker:
|
||||||
:param data_source
|
:param data_source
|
||||||
:param decimals
|
|
||||||
:param lot
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
symbol = symbol.lower()
|
symbol = symbol.lower()
|
||||||
@@ -547,9 +535,6 @@ cdef class TradingPair(Asset):
|
|||||||
if end_date is None:
|
if end_date is None:
|
||||||
end_date = pd.Timestamp.utcnow() + timedelta(days=365)
|
end_date = pd.Timestamp.utcnow() + timedelta(days=365)
|
||||||
|
|
||||||
if lot == 0 and min_trade_size > 0:
|
|
||||||
lot = min_trade_size
|
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
sid,
|
sid,
|
||||||
exchange,
|
exchange,
|
||||||
@@ -571,9 +556,6 @@ cdef class TradingPair(Asset):
|
|||||||
self.exchange_symbol = exchange_symbol
|
self.exchange_symbol = exchange_symbol
|
||||||
self.trading_state = trading_state
|
self.trading_state = trading_state
|
||||||
self.data_source = data_source
|
self.data_source = data_source
|
||||||
self.max_trade_size = max_trade_size
|
|
||||||
self.lot = lot
|
|
||||||
self.decimals = decimals
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \
|
return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \
|
||||||
@@ -600,7 +582,6 @@ cdef class TradingPair(Asset):
|
|||||||
"""
|
"""
|
||||||
Convert to a python dict.
|
Convert to a python dict.
|
||||||
"""
|
"""
|
||||||
#TODO: missing fields
|
|
||||||
super_dict = super(TradingPair, self).to_dict()
|
super_dict = super(TradingPair, self).to_dict()
|
||||||
super_dict['end_daily'] = self.end_daily
|
super_dict['end_daily'] = self.end_daily
|
||||||
super_dict['end_minute'] = self.end_minute
|
super_dict['end_minute'] = self.end_minute
|
||||||
@@ -629,7 +610,6 @@ 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
|
|
||||||
return (self.__class__, (self.symbol,
|
return (self.__class__, (self.symbol,
|
||||||
self.exchange,
|
self.exchange,
|
||||||
self.start_date,
|
self.start_date,
|
||||||
@@ -640,12 +620,7 @@ cdef class TradingPair(Asset):
|
|||||||
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.lot,
|
|
||||||
self.decimals,
|
|
||||||
self.taker,
|
|
||||||
self.maker))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import logbook
|
|||||||
For example, if you want to see the DEBUG messages, run:
|
For example, if you want to see the DEBUG messages, run:
|
||||||
$ export CATALYST_LOG_LEVEL=10
|
$ export CATALYST_LOG_LEVEL=10
|
||||||
'''
|
'''
|
||||||
LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO))
|
# LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO))
|
||||||
|
LOG_LEVEL = logbook.DEBUG
|
||||||
|
|
||||||
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
||||||
'{exchange}/symbols.json'
|
'{exchange}/symbols.json'
|
||||||
|
|||||||
+40
-45
@@ -1,14 +1,7 @@
|
|||||||
import os
|
import json, time, csv
|
||||||
import time
|
|
||||||
import shutil
|
|
||||||
import json
|
|
||||||
import csv
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import os, time, shutil, requests, logbook
|
||||||
import logbook
|
|
||||||
|
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename
|
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +13,6 @@ CONN_RETRIES = 2
|
|||||||
logbook.StderrHandler().push_application()
|
logbook.StderrHandler().push_application()
|
||||||
log = logbook.Logger(__name__)
|
log = logbook.Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PoloniexCurator(object):
|
class PoloniexCurator(object):
|
||||||
'''
|
'''
|
||||||
OHLCV data feed generator for crypto data. Based on Poloniex market data
|
OHLCV data feed generator for crypto data. Based on Poloniex market data
|
||||||
@@ -38,6 +30,7 @@ class PoloniexCurator(object):
|
|||||||
CSV_OUT_FOLDER))
|
CSV_OUT_FOLDER))
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
|
|
||||||
|
|
||||||
def get_currency_pairs(self):
|
def get_currency_pairs(self):
|
||||||
'''
|
'''
|
||||||
Retrieves and returns all currency pairs from the exchange
|
Retrieves and returns all currency pairs from the exchange
|
||||||
@@ -61,6 +54,8 @@ class PoloniexCurator(object):
|
|||||||
len(self.currency_pairs)
|
len(self.currency_pairs)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _retrieve_tradeID_date(self, row):
|
def _retrieve_tradeID_date(self, row):
|
||||||
'''
|
'''
|
||||||
Helper function that reads tradeID and date fields from CSV readline
|
Helper function that reads tradeID and date fields from CSV readline
|
||||||
@@ -70,6 +65,7 @@ class PoloniexCurator(object):
|
|||||||
infer_datetime_format=True).value // 10 ** 9
|
infer_datetime_format=True).value // 10 ** 9
|
||||||
return tId, d
|
return tId, d
|
||||||
|
|
||||||
|
|
||||||
def retrieve_trade_history(self, currencyPair, start=DT_START,
|
def retrieve_trade_history(self, currencyPair, start=DT_START,
|
||||||
end=DT_END, temp=None):
|
end=DT_END, temp=None):
|
||||||
'''
|
'''
|
||||||
@@ -94,27 +90,18 @@ class PoloniexCurator(object):
|
|||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
if(f.tell() > 2): # Check file size is not 0
|
if(f.tell() > 2): # Check file size is not 0
|
||||||
f.seek(0) # Go to start to read
|
f.seek(0) # Go to start to read
|
||||||
last_tradeID, end_file = self._retrieve_tradeID_date(
|
last_tradeID, end_file = self._retrieve_tradeID_date(f.readline())
|
||||||
f.readline())
|
|
||||||
f.seek(-2, os.SEEK_END) # Jump to the 2nd last byte
|
f.seek(-2, os.SEEK_END) # Jump to the 2nd last byte
|
||||||
while f.read(1) != b"\n": # Until EOL is found...
|
while f.read(1) != b"\n": # Until EOL is found...
|
||||||
# ...jump back the read byte plus one more.
|
f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more.
|
||||||
f.seek(-2, os.SEEK_CUR)
|
first_tradeID, start_file = self._retrieve_tradeID_date(f.readline())
|
||||||
first_tradeID, start_file = self._retrieve_tradeID_date(
|
|
||||||
f.readline())
|
|
||||||
|
|
||||||
if(end_file + 3600 * 6 > DT_END
|
if( end_file + 3600 * 6 > DT_END and ( first_tradeID == 1
|
||||||
and (first_tradeID == 1
|
or (currencyPair == 'BTC_HUC' and first_tradeID == 2)
|
||||||
or (currencyPair == 'BTC_HUC'
|
or (currencyPair == 'BTC_RIC' and first_tradeID == 2)
|
||||||
and first_tradeID == 2)
|
or (currencyPair == 'BTC_XCP' and first_tradeID == 2)
|
||||||
or (currencyPair == 'BTC_RIC'
|
or (currencyPair == 'BTC_NAV' and first_tradeID == 4569)
|
||||||
and first_tradeID == 2)
|
or (currencyPair == 'BTC_POT' and first_tradeID == 23511) ) ):
|
||||||
or (currencyPair == 'BTC_XCP'
|
|
||||||
and first_tradeID == 2)
|
|
||||||
or (currencyPair == 'BTC_NAV'
|
|
||||||
and first_tradeID == 4569)
|
|
||||||
or (currencyPair == 'BTC_POT'
|
|
||||||
and first_tradeID == 23511))):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -142,6 +129,7 @@ class PoloniexCurator(object):
|
|||||||
start = str(newstart),
|
start = str(newstart),
|
||||||
end = str(end)
|
end = str(end)
|
||||||
)
|
)
|
||||||
|
print url
|
||||||
|
|
||||||
attempts = 0
|
attempts = 0
|
||||||
success = 0
|
success = 0
|
||||||
@@ -149,14 +137,14 @@ class PoloniexCurator(object):
|
|||||||
try:
|
try:
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Failed to retrieve trade history data'
|
log.error('Failed to retrieve trade history data for {}'.format(
|
||||||
'for {}'.format(currencyPair))
|
currencyPair
|
||||||
|
))
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if(isinstance(response.json(), dict)
|
if isinstance(response.json(), dict) and response.json()['error']:
|
||||||
and response.json()['error']):
|
|
||||||
log.error('Failed to to retrieve trade history data '
|
log.error('Failed to to retrieve trade history data '
|
||||||
'for {}: {}'.format(
|
'for {}: {}'.format(
|
||||||
currencyPair,
|
currencyPair,
|
||||||
@@ -173,6 +161,7 @@ class PoloniexCurator(object):
|
|||||||
if not success:
|
if not success:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
If we get to transactionId == 1, and we already have that on
|
If we get to transactionId == 1, and we already have that on
|
||||||
disk, we got to the end of TradeHistory for this coin.
|
disk, we got to the end of TradeHistory for this coin.
|
||||||
@@ -207,12 +196,11 @@ class PoloniexCurator(object):
|
|||||||
item['rate'],
|
item['rate'],
|
||||||
item['amount'],
|
item['amount'],
|
||||||
item['total'],
|
item['total'],
|
||||||
item['globalTradeID'],
|
item['globalTradeID']
|
||||||
])
|
])
|
||||||
if( response.json()[-1]['tradeID'] > last_tradeID ):
|
if( response.json()[-1]['tradeID'] > last_tradeID ):
|
||||||
end = pd.to_datetime( response.json()[-1]['date'],
|
end = pd.to_datetime( response.json()[-1]['date'],
|
||||||
infer_datetime_format=True
|
infer_datetime_format=True).value // 10 ** 9
|
||||||
).value // 10**9
|
|
||||||
self.retrieve_trade_history(currencyPair, start,
|
self.retrieve_trade_history(currencyPair, start,
|
||||||
end, temp=temp)
|
end, temp=temp)
|
||||||
else:
|
else:
|
||||||
@@ -252,6 +240,8 @@ class PoloniexCurator(object):
|
|||||||
'''
|
'''
|
||||||
self.retrieve_trade_history(currencyPair, start, end)
|
self.retrieve_trade_history(currencyPair, start, end)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_ohlcv(self, df):
|
def generate_ohlcv(self, df):
|
||||||
'''
|
'''
|
||||||
Generates OHLCV dataframe from a dataframe containing all TradeHistory
|
Generates OHLCV dataframe from a dataframe containing all TradeHistory
|
||||||
@@ -261,13 +251,15 @@ class PoloniexCurator(object):
|
|||||||
vol = df['total'].to_frame('volume') # set Vol aside
|
vol = df['total'].to_frame('volume') # set Vol aside
|
||||||
df.drop('total', axis=1, inplace=True) # Drop volume data
|
df.drop('total', axis=1, inplace=True) # Drop volume data
|
||||||
ohlc = df.resample('T').ohlc() # Resample OHLC 1min
|
ohlc = df.resample('T').ohlc() # Resample OHLC 1min
|
||||||
ohlc.cols = ohlc.cols.map(lambda t: t[1]) # Raname cols
|
ohlc.columns = ohlc.columns.map(lambda t: t[1]) # Raname columns by dropping 'rate'
|
||||||
closes = ohlc['close'].fillna(method='pad') # Pad fwd missing close
|
closes = ohlc['close'].fillna(method='pad') # Pad fwd missing 'close'
|
||||||
ohlc = ohlc.apply(lambda x: x.fillna(closes)) # Fill NA w/ last close
|
ohlc = ohlc.apply(lambda x: x.fillna(closes)) # Fill N/A with last close
|
||||||
vol = vol.resample('T').sum().fillna(0) # Add volumes by bin
|
vol = vol.resample('T').sum().fillna(0) # Add volumes by bin
|
||||||
ohlcv = pd.concat([ohlc, vol], axis=1) # Concat OHLC + Vol
|
ohlcv = pd.concat([ohlc,vol], axis=1) # Concatenate OHLC + Vol
|
||||||
return ohlcv
|
return ohlcv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def write_ohlcv_file(self, currencyPair):
|
def write_ohlcv_file(self, currencyPair):
|
||||||
'''
|
'''
|
||||||
Generates OHLCV data file with 1minute bars from TradeHistory on disk
|
Generates OHLCV data file with 1minute bars from TradeHistory on disk
|
||||||
@@ -313,10 +305,12 @@ class PoloniexCurator(object):
|
|||||||
item.volume,
|
item.volume,
|
||||||
])
|
])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Error opening {}'.format(csv_1min))
|
log.error('Error opening {}'.format(csv_fn))
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
log.debug('{}: Generated 1min OHLCV data.'.format(currencyPair))
|
log.debug('{}: Generated 1min OHLCV data.'.format(currencyPair))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def onemin_to_dataframe(self, currencyPair, start, end):
|
def onemin_to_dataframe(self, currencyPair, start, end):
|
||||||
'''
|
'''
|
||||||
Returns a data frame for a given currencyPair from data on disk
|
Returns a data frame for a given currencyPair from data on disk
|
||||||
@@ -327,11 +321,13 @@ class PoloniexCurator(object):
|
|||||||
'high',
|
'high',
|
||||||
'low',
|
'low',
|
||||||
'close',
|
'close',
|
||||||
'volume'])
|
'volume']
|
||||||
|
)
|
||||||
df['date'] = pd.to_datetime(df['date'],unit='s')
|
df['date'] = pd.to_datetime(df['date'],unit='s')
|
||||||
df.set_index('date', inplace=True)
|
df.set_index('date', inplace=True)
|
||||||
return df[start : end]
|
return df[start : end]
|
||||||
|
|
||||||
|
|
||||||
def generate_symbols_json(self, filename=None):
|
def generate_symbols_json(self, filename=None):
|
||||||
'''
|
'''
|
||||||
Generates a symbols.json file with corresponding start_date
|
Generates a symbols.json file with corresponding start_date
|
||||||
@@ -346,15 +342,13 @@ class PoloniexCurator(object):
|
|||||||
for currencyPair in self.currency_pairs:
|
for currencyPair in self.currency_pairs:
|
||||||
start = None
|
start = None
|
||||||
csv_fn = '{}crypto_trades-{}.csv'.format(
|
csv_fn = '{}crypto_trades-{}.csv'.format(
|
||||||
CSV_OUT_FOLDER,
|
CSV_OUT_FOLDER, currencyPair)
|
||||||
currencyPair)
|
|
||||||
with open(csv_fn, 'r') as f:
|
with open(csv_fn, 'r') as f:
|
||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
if(f.tell() > 2): # Check file size is not 0
|
if(f.tell() > 2): # Check file size is not 0
|
||||||
f.seek(-2, os.SEEK_END) # Jump to 2nd last byte
|
f.seek(-2, os.SEEK_END) # Jump to 2nd last byte
|
||||||
while f.read(1) != b"\n": # Until EOL is found...
|
while f.read(1) != b"\n": # Until EOL is found...
|
||||||
# ...jump back the read byte plus one more.
|
f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more.
|
||||||
f.seek(-2, os.SEEK_CUR)
|
|
||||||
start = pd.to_datetime( f.readline().split(',')[1],
|
start = pd.to_datetime( f.readline().split(',')[1],
|
||||||
infer_datetime_format=True)
|
infer_datetime_format=True)
|
||||||
|
|
||||||
@@ -379,3 +373,4 @@ if __name__ == '__main__':
|
|||||||
pc.retrieve_trade_history(currencyPair)
|
pc.retrieve_trade_history(currencyPair)
|
||||||
log.debug('{} up to date.'.format(currencyPair))
|
log.debug('{} up to date.'.format(currencyPair))
|
||||||
pc.write_ohlcv_file(currencyPair)
|
pc.write_ohlcv_file(currencyPair)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# These imports are necessary to force module-scope register calls to happen.
|
# These imports are necessary to force module-scope register calls to happen.
|
||||||
from . import quandl # noqa
|
from . import quandl # noqa
|
||||||
|
from . import poloniex
|
||||||
from .core import (
|
from .core import (
|
||||||
UnknownBundle,
|
UnknownBundle,
|
||||||
bundles,
|
bundles,
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
from itertools import count
|
from itertools import count
|
||||||
import tarfile
|
import tarfile
|
||||||
from time import sleep
|
from time import time, sleep
|
||||||
|
|
||||||
from abc import abstractmethod, abstractproperty
|
from abc import abstractmethod, abstractproperty
|
||||||
import logbook
|
import logbook
|
||||||
@@ -36,7 +37,6 @@ log = logbook.Logger(__name__, level=LOG_LEVEL)
|
|||||||
|
|
||||||
DEFAULT_RETRIES = 5
|
DEFAULT_RETRIES = 5
|
||||||
|
|
||||||
|
|
||||||
class BaseBundle(object):
|
class BaseBundle(object):
|
||||||
def __init__(self, asset_filter=[]):
|
def __init__(self, asset_filter=[]):
|
||||||
self._asset_filter = asset_filter
|
self._asset_filter = asset_filter
|
||||||
@@ -128,7 +128,7 @@ class BaseBundle(object):
|
|||||||
retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5)
|
retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5)
|
||||||
|
|
||||||
if is_compile:
|
if is_compile:
|
||||||
# User has instructed local compilation & ingestion of bundle.
|
# User has instructed local compilation and ingestion of bundle.
|
||||||
# Fetch raw metadata for all symbols.
|
# Fetch raw metadata for all symbols.
|
||||||
raw_metadata = self._fetch_metadata_frame(
|
raw_metadata = self._fetch_metadata_frame(
|
||||||
api_key,
|
api_key,
|
||||||
@@ -157,9 +157,9 @@ class BaseBundle(object):
|
|||||||
show_progress=show_progress,
|
show_progress=show_progress,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Post-process metadata using cached symbol frames, and write
|
# Post-process metadata using cached symbol frames, and write to
|
||||||
# to disk. This metadata must be written before any attempt
|
# disk. This metadata must be written before any attempt to write
|
||||||
# to write minute data.
|
# minute data.
|
||||||
metadata = self._post_process_metadata(
|
metadata = self._post_process_metadata(
|
||||||
raw_metadata,
|
raw_metadata,
|
||||||
cache,
|
cache,
|
||||||
@@ -184,11 +184,10 @@ class BaseBundle(object):
|
|||||||
show_progress=show_progress,
|
show_progress=show_progress,
|
||||||
)
|
)
|
||||||
|
|
||||||
# For legacy purposes, this call is required to ensure the
|
# For legacy purposes, this call is required to ensure the database
|
||||||
# database contains an appropriately initialized file
|
# contains an appropriately initialized file structure. We don't
|
||||||
# structure. We don't forsee a usecase for adjustments at
|
# forsee a usecase for adjustments at this time, but may later
|
||||||
# this time, but may later choose to expose this functionality
|
# choose to expose this functionality in the future.
|
||||||
# in the future.
|
|
||||||
adjustment_writer.write(
|
adjustment_writer.write(
|
||||||
splits=(
|
splits=(
|
||||||
pd.concat(self.splits, ignore_index=True)
|
pd.concat(self.splits, ignore_index=True)
|
||||||
@@ -270,10 +269,10 @@ class BaseBundle(object):
|
|||||||
page_number,
|
page_number,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
raw = pd.DataFrame([])
|
raw = pd.DataFrame([])
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Failed to load metadata from {}. '
|
'Failed to load metadata from {}. '
|
||||||
'Retrying.'.format(self.name)
|
'Retrying.'.format(self.name)
|
||||||
@@ -284,6 +283,7 @@ class BaseBundle(object):
|
|||||||
'attempts.'.format(page_number, retries)
|
'attempts.'.format(page_number, retries)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if raw.empty:
|
if raw.empty:
|
||||||
# Empty DataFrame signals completion.
|
# Empty DataFrame signals completion.
|
||||||
break
|
break
|
||||||
@@ -318,16 +318,16 @@ class BaseBundle(object):
|
|||||||
show_percent=False,
|
show_percent=False,
|
||||||
) as symbols_map:
|
) as symbols_map:
|
||||||
for asset_id, symbol in symbols_map:
|
for asset_id, symbol in symbols_map:
|
||||||
# Attempt to load data from disk, the cache should have an
|
# Attempt to load data from disk, the cache should have an entry
|
||||||
# entry for each symbol at this point of the execution. If one
|
# for each symbol at this point of the execution. If one does
|
||||||
# does not exist, we should fail.
|
# not exist, we should fail.
|
||||||
key = '{sym}.daily.frame'.format(sym=symbol)
|
key = '{sym}.daily.frame'.format(sym=symbol)
|
||||||
try:
|
try:
|
||||||
raw_data = cache[key]
|
raw_data = cache[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Unable to find cached data for symbol:'
|
'Unable to find cached data for symbol: {0}'.format(symbol)
|
||||||
' {0}'.format(symbol))
|
)
|
||||||
|
|
||||||
# Perform and require post-processing of metadata.
|
# Perform and require post-processing of metadata.
|
||||||
final_symbol_metadata = self.post_process_symbol_metadata(
|
final_symbol_metadata = self.post_process_symbol_metadata(
|
||||||
@@ -363,8 +363,8 @@ class BaseBundle(object):
|
|||||||
# returns the cached data unaltered. The `should_sleep` flag
|
# returns the cached data unaltered. The `should_sleep` flag
|
||||||
# indicates that an API call was attempted, and that we should be
|
# indicates that an API call was attempted, and that we should be
|
||||||
# ensure aren't exceeding our rate limit before proceeding to the
|
# ensure aren't exceeding our rate limit before proceeding to the
|
||||||
# next symbol. If the raw_data is updated, it is cached before
|
# next symbol. If the raw_data is updated, it is cached before being
|
||||||
# being returned.
|
# returned.
|
||||||
raw_data, should_sleep = self._maybe_update_symbol_frame(
|
raw_data, should_sleep = self._maybe_update_symbol_frame(
|
||||||
start_time,
|
start_time,
|
||||||
api_key,
|
api_key,
|
||||||
@@ -468,6 +468,7 @@ class BaseBundle(object):
|
|||||||
data_frequency,
|
data_frequency,
|
||||||
)
|
)
|
||||||
raw_data.index = pd.to_datetime(raw_data.index, utc=True)
|
raw_data.index = pd.to_datetime(raw_data.index, utc=True)
|
||||||
|
#raw_data.index = raw_data.index.tz_localize('UTC')
|
||||||
|
|
||||||
# Filter incoming data to fit start and end sessions.
|
# Filter incoming data to fit start and end sessions.
|
||||||
raw_data = raw_data[
|
raw_data = raw_data[
|
||||||
@@ -481,7 +482,7 @@ class BaseBundle(object):
|
|||||||
|
|
||||||
return raw_data
|
return raw_data
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Exception raised fetching {name} data. Retrying.'
|
'Exception raised fetching {name} data. Retrying.'
|
||||||
.format(name=self.name)
|
.format(name=self.name)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
from catalyst.data.bundles.base import BaseBundle
|
from catalyst.data.bundles.base import BaseBundle
|
||||||
from catalyst.utils.memoize import lazyval
|
from catalyst.utils.memoize import lazyval
|
||||||
|
|
||||||
|
|
||||||
class BasePricingBundle(BaseBundle):
|
class BasePricingBundle(BaseBundle):
|
||||||
@lazyval
|
@lazyval
|
||||||
def md_dtypes(self):
|
def md_dtypes(self):
|
||||||
@@ -39,7 +38,6 @@ class BasePricingBundle(BaseBundle):
|
|||||||
('volume', 'float64'),
|
('volume', 'float64'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BaseCryptoPricingBundle(BasePricingBundle):
|
class BaseCryptoPricingBundle(BasePricingBundle):
|
||||||
@lazyval
|
@lazyval
|
||||||
def calendar_name(self):
|
def calendar_name(self):
|
||||||
@@ -57,7 +55,6 @@ class BaseCryptoPricingBundle(BasePricingBundle):
|
|||||||
def dividends(self):
|
def dividends(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class BaseEquityPricingBundle(BasePricingBundle):
|
class BaseEquityPricingBundle(BasePricingBundle):
|
||||||
@lazyval
|
@lazyval
|
||||||
def calendar_name(self):
|
def calendar_name(self):
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ from catalyst.utils.cli import maybe_show_progress
|
|||||||
|
|
||||||
ONE_MEGABYTE = 1024 * 1024
|
ONE_MEGABYTE = 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
def asset_db_path(bundle_name, timestr, environ=None, db_version=None):
|
def asset_db_path(bundle_name, timestr, environ=None, db_version=None):
|
||||||
return pth.data_path(
|
return pth.data_path(
|
||||||
asset_db_relative(bundle_name, timestr, environ, db_version),
|
asset_db_relative(bundle_name, timestr, environ, db_version),
|
||||||
@@ -136,7 +135,6 @@ def ingestions_for_bundle(bundle, environ=None):
|
|||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def download_with_progress(url, chunk_size, **progress_kwargs):
|
def download_with_progress(url, chunk_size, **progress_kwargs):
|
||||||
"""
|
"""
|
||||||
Download streaming data from a URL, printing progress information to the
|
Download streaming data from a URL, printing progress information to the
|
||||||
@@ -707,5 +705,4 @@ def _make_bundle_core():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
bundles, register_bundle, register, unregister, ingest, load, clean = \
|
bundles, register_bundle, register, unregister, ingest, load, clean = _make_bundle_core()
|
||||||
_make_bundle_core()
|
|
||||||
|
|||||||
@@ -14,17 +14,19 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from six.moves.urllib.parse import urlencode
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
from catalyst.data.bundles.core import register_bundle
|
from catalyst.data.bundles.core import register_bundle
|
||||||
from catalyst.data.bundles.base_pricing import BaseCryptoPricingBundle
|
from catalyst.data.bundles.base_pricing import BaseCryptoPricingBundle
|
||||||
from catalyst.utils.memoize import lazyval
|
from catalyst.utils.memoize import lazyval
|
||||||
|
|
||||||
from catalyst.curate.poloniex import PoloniexCurator
|
from catalyst.curate.poloniex import PoloniexCurator
|
||||||
|
|
||||||
|
|
||||||
class PoloniexBundle(BaseCryptoPricingBundle):
|
class PoloniexBundle(BaseCryptoPricingBundle):
|
||||||
@lazyval
|
@lazyval
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -44,8 +46,7 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
@lazyval
|
@lazyval
|
||||||
def tar_url(self):
|
def tar_url(self):
|
||||||
return (
|
return (
|
||||||
'https://s3.amazonaws.com/enigmaco/catalyst-bundles/'
|
'https://s3.amazonaws.com/enigmaco/catalyst-bundles/poloniex/poloniex-bundle.tar.gz'
|
||||||
'poloniex/poloniex-bundle.tar.gz'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@lazyval
|
@lazyval
|
||||||
@@ -71,6 +72,7 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
)
|
)
|
||||||
|
|
||||||
raw = raw[raw['isFrozen'] == 0]
|
raw = raw[raw['isFrozen'] == 0]
|
||||||
|
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
def post_process_symbol_metadata(self, asset_id, sym_md, sym_data):
|
def post_process_symbol_metadata(self, asset_id, sym_md, sym_data):
|
||||||
@@ -96,8 +98,7 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
frequency):
|
frequency):
|
||||||
|
|
||||||
# TODO: replace this with direct exchange call
|
# TODO: replace this with direct exchange call
|
||||||
# The end date and frequency should be used to
|
# The end date and frequency should be used to calculate the number of bars
|
||||||
# calculate the number of bars
|
|
||||||
if(frequency == 'minute'):
|
if(frequency == 'minute'):
|
||||||
pc = PoloniexCurator()
|
pc = PoloniexCurator()
|
||||||
raw = pc.onemin_to_dataframe(symbol, start_date, end_date)
|
raw = pc.onemin_to_dataframe(symbol, start_date, end_date)
|
||||||
@@ -115,9 +116,8 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
)
|
)
|
||||||
raw.set_index('date', inplace=True)
|
raw.set_index('date', inplace=True)
|
||||||
|
|
||||||
# BcolzDailyBarReader introduces a 1/1000 factor in the way
|
# BcolzDailyBarReader introduces a 1/1000 factor in the way pricing is stored
|
||||||
# pricing is stored on disk, which we compensate here to get
|
# on disk, which we compensate here to get the right pricing amounts
|
||||||
# the right pricing amounts
|
|
||||||
# ref: data/us_equity_pricing.py
|
# ref: data/us_equity_pricing.py
|
||||||
scale = 1
|
scale = 1
|
||||||
raw.loc[:, 'open'] /= scale
|
raw.loc[:, 'open'] /= scale
|
||||||
@@ -139,6 +139,7 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
|
|
||||||
return self._format_polo_query(query_params)
|
return self._format_polo_query(query_params)
|
||||||
|
|
||||||
|
|
||||||
def _format_data_url(self,
|
def _format_data_url(self,
|
||||||
api_key,
|
api_key,
|
||||||
symbol,
|
symbol,
|
||||||
@@ -170,7 +171,6 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
|||||||
query=urlencode(query_params),
|
query=urlencode(query_params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
As a second parameter, you can pass an array of currency pairs
|
As a second parameter, you can pass an array of currency pairs
|
||||||
that will be processed as an asset_filter to only process that
|
that will be processed as an asset_filter to only process that
|
||||||
@@ -180,7 +180,9 @@ register_bundle(PoloniexBundle, ['USDT_BTC',])
|
|||||||
For a production environment make sure to use (to bundle all pairs):
|
For a production environment make sure to use (to bundle all pairs):
|
||||||
register_bundle(PoloniexBundle)
|
register_bundle(PoloniexBundle)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if 'ingest' in sys.argv and '-c' in sys.argv:
|
if 'ingest' in sys.argv and '-c' in sys.argv:
|
||||||
register_bundle(PoloniexBundle)
|
register_bundle(PoloniexBundle)
|
||||||
else:
|
else:
|
||||||
register_bundle(PoloniexBundle, create_writers=False)
|
register_bundle(PoloniexBundle, create_writers=False)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlencode
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
from catalyst.data.bundles.core import register_bundle
|
from catalyst.data.bundles.core import register_bundle
|
||||||
@@ -25,16 +26,25 @@ from catalyst.utils.memoize import lazyval
|
|||||||
"""
|
"""
|
||||||
Module for building a complete daily dataset from Quandl's WIKI dataset.
|
Module for building a complete daily dataset from Quandl's WIKI dataset.
|
||||||
"""
|
"""
|
||||||
|
from itertools import count
|
||||||
|
import tarfile
|
||||||
|
from time import time, sleep
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
import pandas as pd
|
||||||
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
|
from catalyst.utils.calendars import register_calendar_alias
|
||||||
|
from catalyst.utils.cli import maybe_show_progress
|
||||||
|
|
||||||
|
from . import core as bundles
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.utils.calendars import register_calendar_alias
|
|
||||||
|
|
||||||
|
|
||||||
log = Logger(__name__, level=LOG_LEVEL)
|
log = Logger(__name__, level=LOG_LEVEL)
|
||||||
seconds_per_call = (pd.Timedelta('10 minutes') / 2000).total_seconds()
|
seconds_per_call = (pd.Timedelta('10 minutes') / 2000).total_seconds()
|
||||||
|
|
||||||
|
|
||||||
class QuandlBundle(BaseEquityPricingBundle):
|
class QuandlBundle(BaseEquityPricingBundle):
|
||||||
@lazyval
|
@lazyval
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -99,8 +109,8 @@ class QuandlBundle(BaseEquityPricingBundle):
|
|||||||
# Filter out invalid symbols
|
# Filter out invalid symbols
|
||||||
raw = raw[~raw.symbol.isin(self._excluded_symbols)]
|
raw = raw[~raw.symbol.isin(self._excluded_symbols)]
|
||||||
|
|
||||||
# cut out all the other stuff in the name column. We need to
|
# cut out all the other stuff in the name column
|
||||||
# escape the paren because it is actually splitting on a regex
|
# we need to escape the paren because it is actually splitting on a regex
|
||||||
raw.asset_name = raw.asset_name.str.split(r' \(', 1).str.get(0)
|
raw.asset_name = raw.asset_name.str.split(r' \(', 1).str.get(0)
|
||||||
|
|
||||||
return raw
|
return raw
|
||||||
@@ -165,6 +175,7 @@ class QuandlBundle(BaseEquityPricingBundle):
|
|||||||
df['sid'] = asset_id
|
df['sid'] = asset_id
|
||||||
self.splits.append(df)
|
self.splits.append(df)
|
||||||
|
|
||||||
|
|
||||||
def _update_dividends(self, asset_id, raw_data):
|
def _update_dividends(self, asset_id, raw_data):
|
||||||
divs = raw_data.ex_dividend
|
divs = raw_data.ex_dividend
|
||||||
df = pd.DataFrame({'amount': divs[divs != 0]})
|
df = pd.DataFrame({'amount': divs[divs != 0]})
|
||||||
@@ -175,6 +186,7 @@ class QuandlBundle(BaseEquityPricingBundle):
|
|||||||
df['record_date'] = df['declared_date'] = df['pay_date'] = pd.NaT
|
df['record_date'] = df['declared_date'] = df['pay_date'] = pd.NaT
|
||||||
self.dividends.append(df)
|
self.dividends.append(df)
|
||||||
|
|
||||||
|
|
||||||
def _format_metadata_url(self, api_key, page_number):
|
def _format_metadata_url(self, api_key, page_number):
|
||||||
"""Build the query RL for the quandl WIKI metadata.
|
"""Build the query RL for the quandl WIKI metadata.
|
||||||
"""
|
"""
|
||||||
@@ -188,10 +200,10 @@ class QuandlBundle(BaseEquityPricingBundle):
|
|||||||
query_params = [('api_key', api_key)] + query_params
|
query_params = [('api_key', api_key)] + query_params
|
||||||
|
|
||||||
return (
|
return (
|
||||||
'https://www.quandl.com/api/v3/datasets.csv?'
|
'https://www.quandl.com/api/v3/datasets.csv?' + urlencode(query_params)
|
||||||
+ urlencode(query_params)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _format_wiki_url(self,
|
def _format_wiki_url(self,
|
||||||
api_key,
|
api_key,
|
||||||
symbol,
|
symbol,
|
||||||
@@ -217,6 +229,5 @@ class QuandlBundle(BaseEquityPricingBundle):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
register_calendar_alias('QUANDL', 'NYSE')
|
register_calendar_alias('QUANDL', 'NYSE')
|
||||||
register_bundle(QuandlBundle)
|
register_bundle(QuandlBundle)
|
||||||
|
|||||||
@@ -133,13 +133,11 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class AssetDispatchMinuteBarReader(AssetDispatchBarReader):
|
class AssetDispatchMinuteBarReader(AssetDispatchBarReader):
|
||||||
|
|
||||||
def _dt_window_size(self, start_dt, end_dt):
|
def _dt_window_size(self, start_dt, end_dt):
|
||||||
return len(self.trading_calendar.minutes_in_range(start_dt, end_dt))
|
return len(self.trading_calendar.minutes_in_range(start_dt, end_dt))
|
||||||
|
|
||||||
|
|
||||||
class AssetDispatchSessionBarReader(AssetDispatchBarReader):
|
class AssetDispatchSessionBarReader(AssetDispatchBarReader):
|
||||||
|
|
||||||
def _dt_window_size(self, start_dt, end_dt):
|
def _dt_window_size(self, start_dt, end_dt):
|
||||||
|
|||||||
+77
-18
@@ -12,6 +12,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
@@ -128,13 +129,11 @@ def load_crypto_market_data(trading_day=None, trading_days=None,
|
|||||||
# before this date.
|
# before this date.
|
||||||
'''
|
'''
|
||||||
if(bundle_data):
|
if(bundle_data):
|
||||||
# If we are using the bundle to retrieve the cryptobenchmark, find
|
# If we are using the bundle to retrieve the cryptobenchmark, find the last
|
||||||
# the last date for which there is trading data in the bundle
|
# date for which there is trading data in the bundle
|
||||||
asset = bundle_data.asset_finder.lookup_symbol(
|
asset = bundle_data.asset_finder.lookup_symbol(symbol=bm_symbol,as_of_date=None)
|
||||||
symbol=bm_symbol,as_of_date=None)
|
|
||||||
ix = bundle_data.daily_bar_reader._last_rows[asset.sid]
|
ix = bundle_data.daily_bar_reader._last_rows[asset.sid]
|
||||||
last_date = pd.to_datetime(
|
last_date = pd.to_datetime(bundle_data.daily_bar_reader._spot_col('day')[ix],unit='s')
|
||||||
bundle_data.daily_bar_reader._spot_col('day')[ix],unit='s')
|
|
||||||
else:
|
else:
|
||||||
last_date = trading_days[trading_days.get_loc(now, method='ffill') - 2]
|
last_date = trading_days[trading_days.get_loc(now, method='ffill') - 2]
|
||||||
'''
|
'''
|
||||||
@@ -165,8 +164,8 @@ def load_crypto_market_data(trading_day=None, trading_days=None,
|
|||||||
br.loc[start_dt] = 0
|
br.loc[start_dt] = 0
|
||||||
br = br.sort_index()
|
br = br.sort_index()
|
||||||
|
|
||||||
# Override first_date for treasury data since we have it for many more
|
# Override first_date for treasury data since we have it for many more years
|
||||||
# years and is independent of crypto data
|
# and is independent of crypto data
|
||||||
first_date_treasury = pd.Timestamp('1990-01-02', tz='UTC')
|
first_date_treasury = pd.Timestamp('1990-01-02', tz='UTC')
|
||||||
tc = ensure_treasury_data(
|
tc = ensure_treasury_data(
|
||||||
bm_symbol,
|
bm_symbol,
|
||||||
@@ -302,14 +301,14 @@ def ensure_crypto_benchmark_data(symbol,
|
|||||||
|
|
||||||
if (bundle == 'poloniex'):
|
if (bundle == 'poloniex'):
|
||||||
'''
|
'''
|
||||||
If we're using the Poloniex bundle, we'll get the benchmark from the
|
If we're using the Poloniex bundle, we'll get the benchmark from the bundle
|
||||||
bundle instead of downloading it from Poloniex every time we need it.
|
instead of downloading it from Poloniex every time we need it.
|
||||||
Poloniex has a captcha for API queries originating from outside the US
|
Poloniex has a captcha for API queries originating from outside the US that
|
||||||
that prevents users abroad from getting Catalyst to work
|
prevents users abroad from getting Catalyst to work
|
||||||
'''
|
'''
|
||||||
logger.info(
|
logger.info(
|
||||||
('Retrieving benchmark data from bundle for {symbol!r}'
|
(
|
||||||
' from {first_date} to {last_date}'),
|
'Retrieving benchmark data from bundle for {symbol!r} from {first_date} to {last_date}'),
|
||||||
symbol=symbol, first_date=first_date, last_date=last_date)
|
symbol=symbol, first_date=first_date, last_date=last_date)
|
||||||
|
|
||||||
asset = bundle_data.asset_finder.lookup_symbol(symbol=symbol,
|
asset = bundle_data.asset_finder.lookup_symbol(symbol=symbol,
|
||||||
@@ -332,11 +331,10 @@ def ensure_crypto_benchmark_data(symbol,
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# This is how it used to be: downloading the benchmark everytime.
|
# This is how it used to be: downloading the benchmark everytime.
|
||||||
# Leaving this code here to be repurposed in the future for
|
# Leaving this code here to be repurposed in the future for other bundles.
|
||||||
# other bundles.
|
|
||||||
logger.info(
|
logger.info(
|
||||||
('Downloading benchmark data for {symbol!r}'
|
(
|
||||||
' from {first_date} to {last_date}'),
|
'Downloading benchmark data for {symbol!r} from {first_date} to {last_date}'),
|
||||||
symbol=symbol, first_date=first_date, last_date=last_date)
|
symbol=symbol, first_date=first_date, last_date=last_date)
|
||||||
|
|
||||||
raise DeprecationWarning('poloniex bundle deprecated')
|
raise DeprecationWarning('poloniex bundle deprecated')
|
||||||
@@ -433,6 +431,67 @@ def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day,
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day,
|
||||||
|
environ=None):
|
||||||
|
"""
|
||||||
|
Ensure we have benchmark data for `symbol` from `first_date` to `last_date`
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
symbol : str
|
||||||
|
The symbol for the benchmark to load.
|
||||||
|
first_date : pd.Timestamp
|
||||||
|
First required date for the cache.
|
||||||
|
last_date : pd.Timestamp
|
||||||
|
Last required date for the cache.
|
||||||
|
now : pd.Timestamp
|
||||||
|
The current time. This is used to prevent repeated attempts to
|
||||||
|
re-download data that isn't available due to scheduling quirks or other
|
||||||
|
failures.
|
||||||
|
trading_day : pd.CustomBusinessDay
|
||||||
|
A trading day delta. Used to find the day before first_date so we can
|
||||||
|
get the close of the day prior to first_date.
|
||||||
|
|
||||||
|
We attempt to download data unless we already have data stored at the data
|
||||||
|
cache for `symbol` whose first entry is before or on `first_date` and whose
|
||||||
|
last entry is on or after `last_date`.
|
||||||
|
|
||||||
|
If we perform a download and the cache criteria are not satisfied, we wait
|
||||||
|
at least one hour before attempting a redownload. This is determined by
|
||||||
|
comparing the current time to the result of os.path.getmtime on the cache
|
||||||
|
path.
|
||||||
|
"""
|
||||||
|
filename = get_benchmark_filename(symbol)
|
||||||
|
data = _load_cached_data(filename, first_date, last_date, now, 'benchmark',
|
||||||
|
environ)
|
||||||
|
if data is not None:
|
||||||
|
return data
|
||||||
|
|
||||||
|
# If no cached data was found or it was missing any dates then download the
|
||||||
|
# necessary data.
|
||||||
|
logger.info(
|
||||||
|
('Downloading benchmark data for {symbol!r} '
|
||||||
|
'from {first_date} to {last_date}'),
|
||||||
|
symbol=symbol,
|
||||||
|
first_date=first_date - trading_day,
|
||||||
|
last_date=last_date
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = get_benchmark_returns(
|
||||||
|
symbol,
|
||||||
|
first_date - trading_day,
|
||||||
|
last_date,
|
||||||
|
)
|
||||||
|
data.to_csv(get_data_filepath(filename, environ))
|
||||||
|
except (OSError, IOError, HTTPError):
|
||||||
|
logger.exception('Failed to cache the new benchmark returns')
|
||||||
|
raise
|
||||||
|
if not has_data_for_dates(data, first_date, last_date):
|
||||||
|
logger.warn("Still don't have expected data after redownload!")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def ensure_treasury_data(symbol, first_date, last_date, now, environ=None):
|
def ensure_treasury_data(symbol, first_date, last_date, now, environ=None):
|
||||||
"""
|
"""
|
||||||
Ensure we have treasury data from treasury module associated with
|
Ensure we have treasury data from treasury module associated with
|
||||||
|
|||||||
@@ -341,9 +341,11 @@ class BcolzMinuteBarMetadata(object):
|
|||||||
'end_session': str(self.end_session.date()),
|
'end_session': str(self.end_session.date()),
|
||||||
# Write these values for backwards compatibility
|
# Write these values for backwards compatibility
|
||||||
'first_trading_day': str(self.start_session.date()),
|
'first_trading_day': str(self.start_session.date()),
|
||||||
'market_opens': (market_opens.values.astype('datetime64[m]').
|
'market_opens': (
|
||||||
|
market_opens.values.astype('datetime64[m]').
|
||||||
astype(np.int64).tolist()),
|
astype(np.int64).tolist()),
|
||||||
'market_closes': (market_closes.values.astype('datetime64[m]').
|
'market_closes': (
|
||||||
|
market_closes.values.astype('datetime64[m]').
|
||||||
astype(np.int64).tolist()),
|
astype(np.int64).tolist()),
|
||||||
}
|
}
|
||||||
with open(self.metadata_path(rootdir), 'w+') as fp:
|
with open(self.metadata_path(rootdir), 'w+') as fp:
|
||||||
@@ -1254,8 +1256,8 @@ class BcolzMinuteBarReader(MinuteBarReader):
|
|||||||
values = carray[start_idx:end_idx + 1]
|
values = carray[start_idx:end_idx + 1]
|
||||||
if indices_to_exclude is not None:
|
if indices_to_exclude is not None:
|
||||||
for excl_start, excl_stop in indices_to_exclude[::-1]:
|
for excl_start, excl_stop in indices_to_exclude[::-1]:
|
||||||
excl_slice = np.s_[excl_start - start_idx:excl_stop
|
excl_slice = np.s_[
|
||||||
- start_idx + 1]
|
excl_start - start_idx:excl_stop - start_idx + 1]
|
||||||
values = np.delete(values, excl_slice)
|
values = np.delete(values, excl_slice)
|
||||||
|
|
||||||
where = values != 0
|
where = values != 0
|
||||||
@@ -1319,7 +1321,8 @@ class H5MinuteBarUpdateWriter(object):
|
|||||||
def __init__(self, path, complevel=None, complib=None):
|
def __init__(self, path, complevel=None, complib=None):
|
||||||
self._complevel = complevel if complevel \
|
self._complevel = complevel if complevel \
|
||||||
is not None else self._COMPLEVEL
|
is not None else self._COMPLEVEL
|
||||||
self._complib = complib if complib is not None else self._COMPLIB
|
self._complib = complib if complib \
|
||||||
|
is not None else self._COMPLIB
|
||||||
self._path = path
|
self._path = path
|
||||||
|
|
||||||
def write(self, frames):
|
def write(self, frames):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from __future__ import division # Python2 req for division of ints yield float
|
from __future__ import division # Python2 req to have division of ints yield float
|
||||||
|
|
||||||
from errno import ENOENT
|
from errno import ENOENT
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -120,8 +120,7 @@ SQLITE_STOCK_DIVIDEND_PAYOUT_COLUMN_DTYPES = {
|
|||||||
UINT32_MAX = iinfo(uint32).max
|
UINT32_MAX = iinfo(uint32).max
|
||||||
UINT64_MAX = iinfo(uint64).max
|
UINT64_MAX = iinfo(uint64).max
|
||||||
|
|
||||||
# Provides 9 decimals resolution. Also affects _equities.pyx L220
|
PRICE_ADJUSTMENT_FACTOR = 1000000000 # Provides 9 decimals resolution. Also affects _equities.pyx L220
|
||||||
PRICE_ADJUSTMENT_FACTOR = 1000000000
|
|
||||||
|
|
||||||
|
|
||||||
def check_uint32_safe(value, colname):
|
def check_uint32_safe(value, colname):
|
||||||
@@ -131,7 +130,6 @@ def check_uint32_safe(value, colname):
|
|||||||
"for uint32" % (value, colname)
|
"for uint32" % (value, colname)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_uint64_safe(value, colname):
|
def check_uint64_safe(value, colname):
|
||||||
if value >= UINT64_MAX:
|
if value >= UINT64_MAX:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -441,13 +439,11 @@ class BcolzDailyBarWriter(object):
|
|||||||
return raw_data
|
return raw_data
|
||||||
|
|
||||||
winsorise_uint64(raw_data, invalid_data_behavior, 'volume', *OHLC)
|
winsorise_uint64(raw_data, invalid_data_behavior, 'volume', *OHLC)
|
||||||
processed = (raw_data[list(OHLC)]
|
processed = (raw_data[list(OHLC)] * PRICE_ADJUSTMENT_FACTOR).astype('uint64')
|
||||||
* PRICE_ADJUSTMENT_FACTOR).astype('uint64')
|
|
||||||
dates = raw_data.index.values.astype('datetime64[s]')
|
dates = raw_data.index.values.astype('datetime64[s]')
|
||||||
check_uint32_safe(dates.max().view(np.int64), 'day')
|
check_uint32_safe(dates.max().view(np.int64), 'day')
|
||||||
processed['day'] = dates.astype('uint32')
|
processed['day'] = dates.astype('uint32')
|
||||||
processed['volume'] = (raw_data.volume
|
processed['volume'] = (raw_data.volume * PRICE_ADJUSTMENT_FACTOR).astype('uint64')
|
||||||
* PRICE_ADJUSTMENT_FACTOR).astype('uint64')
|
|
||||||
return ctable.fromdataframe(processed)
|
return ctable.fromdataframe(processed)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -263,20 +263,13 @@ def analyze(context, stats):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# The execution mode: backtest or live
|
|
||||||
MODE = 'live'
|
|
||||||
if MODE == 'live':
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.1,
|
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='poloniex,bitfinex',
|
exchange_name='poloniex,bitfinex',
|
||||||
live=True,
|
live=True,
|
||||||
algo_namespace=algo_namespace,
|
algo_namespace=algo_namespace,
|
||||||
base_currency='btc',
|
quote_currency='btc',
|
||||||
live_graph=False,
|
live_graph=False
|
||||||
simulate_orders=True,
|
|
||||||
stats_output=None,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ def handle_data(context, data):
|
|||||||
context.asset,
|
context.asset,
|
||||||
target_hodl_value,
|
target_hodl_value,
|
||||||
limit_price=price * 1.1,
|
limit_price=price * 1.1,
|
||||||
|
stop_price=price * 0.9,
|
||||||
)
|
)
|
||||||
|
|
||||||
record(
|
record(
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
Run this example, by executing the following from your terminal:
|
Run this example, by executing the following from your terminal:
|
||||||
catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt
|
catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt
|
||||||
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 \
|
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
||||||
--end 2017-9-30 -o buy_btc_simple_out.pickle
|
|
||||||
|
|
||||||
If you want to run this code using another exchange, make sure that
|
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
|
the asset is available on that exchange. For example, if you were to run
|
||||||
@@ -15,35 +14,17 @@
|
|||||||
|
|
||||||
and specify exchange poloniex as follows:
|
and specify exchange poloniex as follows:
|
||||||
catalyst ingest-exchange -x poloniex -f daily -i btc_usdt
|
catalyst ingest-exchange -x poloniex -f daily -i btc_usdt
|
||||||
catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 \
|
catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
||||||
--end 2017-9-30 -o buy_btc_simple_out.pickle
|
|
||||||
|
|
||||||
To see which assets are available on each exchange, visit:
|
To see which assets are available on each exchange, visit:
|
||||||
https://www.enigma.co/catalyst/status
|
https://www.enigma.co/catalyst/status
|
||||||
'''
|
'''
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import order, record, symbol
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
|
from catalyst.api import order, record, symbol
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.asset = symbol('btc_usd')
|
context.asset = symbol('btc_usd')
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
order(context.asset, 1)
|
order(context.asset, 1)
|
||||||
record(btc = data.current(context.asset, 'price'))
|
record(btc = data.current(context.asset, 'price'))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=10000,
|
|
||||||
data_frequency='daily',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
exchange_name='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),
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
'''
|
'''
|
||||||
This algorithm requires an additional library (ta-lib) beyond those
|
This algorithm requires an additional library (ta-lib) beyond those required by catalyst.
|
||||||
required by catalyst. Install it first by running:
|
Install it first by running:
|
||||||
$ pip install TA-Lib
|
$ pip install TA-Lib
|
||||||
|
|
||||||
If you get build errors like:
|
If you get build errors like "fatal error: ta-lib/ta_libc.h: No such file or directory"
|
||||||
"fatal error: ta-lib/ta_libc.h: No such file or directory"
|
it typically means that it can't find the underlying TA-Lib library and needs to be installed.
|
||||||
it typically means that it can't find the underlying TA-Lib library and it
|
See https://mrjbq7.github.io/ta-lib/install.html for instructions on how to install
|
||||||
needs to be installed. See https://mrjbq7.github.io/ta-lib/install.html for
|
the required dependencies.
|
||||||
instructions on how to install the required dependencies.
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import talib
|
import talib
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import (
|
from catalyst.api import (
|
||||||
order,
|
order,
|
||||||
order_target_percent,
|
order_target_percent,
|
||||||
@@ -22,7 +20,6 @@ from catalyst.api import (
|
|||||||
get_open_orders,
|
get_open_orders,
|
||||||
)
|
)
|
||||||
from catalyst.exchange.stats_utils import get_pretty_stats
|
from catalyst.exchange.stats_utils import get_pretty_stats
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
algo_namespace = 'buy_low_sell_high_xrp'
|
algo_namespace = 'buy_low_sell_high_xrp'
|
||||||
log = Logger(algo_namespace)
|
log = Logger(algo_namespace)
|
||||||
@@ -103,8 +100,8 @@ def _handle_data(context, data):
|
|||||||
|
|
||||||
if price < cost_basis:
|
if price < cost_basis:
|
||||||
is_buy = True
|
is_buy = True
|
||||||
elif (position.amount > 0
|
elif position.amount > 0 and \
|
||||||
and price > cost_basis * (1 + context.PROFIT_TARGET)):
|
price > cost_basis * (1 + context.PROFIT_TARGET):
|
||||||
profit = (price * position.amount) - (cost_basis * position.amount)
|
profit = (price * position.amount) - (cost_basis * position.amount)
|
||||||
log.info('closing position, taking profit: {}'.format(profit))
|
log.info('closing position, taking profit: {}'.format(profit))
|
||||||
order_target_percent(
|
order_target_percent(
|
||||||
@@ -159,18 +156,3 @@ def handle_data(context, data):
|
|||||||
def analyze(context, stats):
|
def analyze(context, stats):
|
||||||
log.info('the daily stats:\n{}'.format(get_pretty_stats(stats)))
|
log.info('the daily stats:\n{}'.format(get_pretty_stats(stats)))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=10000,
|
|
||||||
data_frequency='daily',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='poloniex',
|
|
||||||
algo_namespace='buy_and_hodl',
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2015-03-01', utc=True),
|
|
||||||
end=pd.to_datetime('2017-10-31', utc=True),
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def _handle_data(context, data):
|
|||||||
context.asset,
|
context.asset,
|
||||||
fields='price',
|
fields='price',
|
||||||
bar_count=20,
|
bar_count=20,
|
||||||
frequency='1D'
|
frequency='1d'
|
||||||
)
|
)
|
||||||
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
||||||
log.info('got rsi: {}'.format(rsi))
|
log.info('got rsi: {}'.format(rsi))
|
||||||
@@ -88,8 +88,8 @@ def _handle_data(context, data):
|
|||||||
|
|
||||||
if price < cost_basis:
|
if price < cost_basis:
|
||||||
is_buy = True
|
is_buy = True
|
||||||
elif (position.amount > 0
|
elif position.amount > 0 and \
|
||||||
and price > cost_basis * (1 + context.PROFIT_TARGET)):
|
price > cost_basis * (1 + context.PROFIT_TARGET):
|
||||||
profit = (price * position.amount) - (cost_basis * position.amount)
|
profit = (price * position.amount) - (cost_basis * position.amount)
|
||||||
log.info('closing position, taking profit: {}'.format(profit))
|
log.info('closing position, taking profit: {}'.format(profit))
|
||||||
order_target_percent(
|
order_target_percent(
|
||||||
@@ -146,15 +146,23 @@ def analyze(context, stats):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.001,
|
capital_base=100000,
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='binance',
|
exchange_name='poloniex',
|
||||||
live=True,
|
start=pd.to_datetime('2017-5-01', utc=True),
|
||||||
algo_namespace=algo_namespace,
|
end=pd.to_datetime('2017-10-16', utc=True),
|
||||||
base_currency='btc',
|
base_currency='usdt',
|
||||||
simulate_orders=True,
|
data_frequency='daily'
|
||||||
)
|
)
|
||||||
|
# run_algorithm(
|
||||||
|
# initialize=initialize,
|
||||||
|
# handle_data=handle_data,
|
||||||
|
# analyze=analyze,
|
||||||
|
# exchange_name='poloniex',
|
||||||
|
# live=True,
|
||||||
|
# algo_namespace=algo_namespace,
|
||||||
|
# base_currency='btc'
|
||||||
|
# )
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ from logbook import Logger
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import (record, symbol, order_target_percent,
|
from catalyst.api import (order, record, symbol, order_target_percent,
|
||||||
get_open_orders)
|
get_open_orders)
|
||||||
from catalyst.exchange.stats_utils import extract_transactions
|
from catalyst.exchange.stats_utils import extract_transactions
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
NAMESPACE = 'dual_moving_average'
|
||||||
log = Logger(NAMESPACE)
|
log = Logger(NAMESPACE)
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.i = 0
|
context.i = 0
|
||||||
context.asset = symbol('ltc_usd')
|
context.asset = symbol('ltc_usd')
|
||||||
@@ -32,16 +31,10 @@ def handle_data(context, data):
|
|||||||
# moving average with the appropriate parameters. We choose to use
|
# moving average with the appropriate parameters. We choose to use
|
||||||
# minute bars for this simulation -> freq="1m"
|
# minute bars for this simulation -> freq="1m"
|
||||||
# Returns a pandas dataframe.
|
# Returns a pandas dataframe.
|
||||||
short_mavg = data.history(context.asset,
|
short_mavg = data.history(context.asset, 'price',
|
||||||
'price',
|
bar_count=short_window, frequency="1m").mean()
|
||||||
bar_count=short_window,
|
long_mavg = data.history(context.asset, 'price',
|
||||||
frequency="1m",
|
bar_count=long_window, frequency="1m").mean()
|
||||||
).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
|
# Let's keep the price of our asset in a more handy variable
|
||||||
price = data.current(context.asset, 'price')
|
price = data.current(context.asset, 'price')
|
||||||
@@ -96,9 +89,7 @@ def analyze(context, perf):
|
|||||||
|
|
||||||
# Second chart: Plot asset price, moving averages and buys/sells
|
# Second chart: Plot asset price, moving averages and buys/sells
|
||||||
ax2 = plt.subplot(412, sharex=ax1)
|
ax2 = plt.subplot(412, sharex=ax1)
|
||||||
perf.loc[:, ['price', 'short_mavg', 'long_mavg']].plot(
|
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
||||||
ax=ax2,
|
|
||||||
label='Price')
|
|
||||||
ax2.legend_.remove()
|
ax2.legend_.remove()
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
ax2.set_ylabel('{asset}\n({base})'.format(
|
||||||
asset = context.asset.symbol,
|
asset = context.asset.symbol,
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright 2017 Enigma MPC, Inc.
|
||||||
|
# Copyright 2014 Quantopian, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from catalyst.api import (
|
||||||
|
order_target_percent,
|
||||||
|
record,
|
||||||
|
symbol,
|
||||||
|
get_open_orders,
|
||||||
|
set_max_leverage,
|
||||||
|
schedule_function,
|
||||||
|
date_rules,
|
||||||
|
attach_pipeline,
|
||||||
|
pipeline_output,
|
||||||
|
)
|
||||||
|
|
||||||
|
from catalyst.pipeline import Pipeline
|
||||||
|
from catalyst.pipeline.data import CryptoPricing
|
||||||
|
from catalyst.pipeline.factors.crypto import VWAP
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
context.ASSET_NAME = 'USDT_BTC'
|
||||||
|
context.TARGET_INVESTMENT_RATIO = 0.8
|
||||||
|
context.SHORT_WINDOW = 30
|
||||||
|
context.LONG_WINDOW = 100
|
||||||
|
|
||||||
|
# For all trading pairs in the poloniex bundle, the default denomination
|
||||||
|
# currently supported by Catalyst is 1/1000th of a full coin. Use this
|
||||||
|
# constant to scale the price of up to that of a full coin if desired.
|
||||||
|
context.TICK_SIZE = 1000.0
|
||||||
|
|
||||||
|
context.i = 0
|
||||||
|
context.asset = symbol(context.ASSET_NAME)
|
||||||
|
|
||||||
|
set_max_leverage(1.0)
|
||||||
|
|
||||||
|
attach_pipeline(make_pipeline(context), 'vwap_pipeline')
|
||||||
|
|
||||||
|
schedule_function(
|
||||||
|
rebalance,
|
||||||
|
time_rules=times_rules.every_minute(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def before_trading_start(context, data):
|
||||||
|
context.pipeline_data = pipeline_output('vwap_pipeline')
|
||||||
|
|
||||||
|
def make_pipeline(context):
|
||||||
|
return Pipeline(
|
||||||
|
columns={
|
||||||
|
'price': CryptoPricing.open.latest,
|
||||||
|
'volume': CryptoPricing.volume.latest,
|
||||||
|
'short_mavg': VWAP(window_length=context.SHORT_WINDOW),
|
||||||
|
'long_mavg': VWAP(window_length=context.LONG_WINDOW),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def rebalance(context, data):
|
||||||
|
context.i += 1
|
||||||
|
|
||||||
|
# skip first LONG_WINDOW bars to fill windows
|
||||||
|
if context.i < context.LONG_WINDOW:
|
||||||
|
return
|
||||||
|
|
||||||
|
# get pipeline data for asset of interest
|
||||||
|
pipeline_data = context.pipeline_data
|
||||||
|
pipeline_data = pipeline_data[pipeline_data.index == context.asset].iloc[0]
|
||||||
|
|
||||||
|
# retrieve long and short moving averages from pipeline
|
||||||
|
short_mavg = pipeline_data.short_mavg
|
||||||
|
long_mavg = pipeline_data.long_mavg
|
||||||
|
price = pipeline_data.price
|
||||||
|
volume = pipeline_data.volume
|
||||||
|
|
||||||
|
# check that order has not already been placed
|
||||||
|
open_orders = get_open_orders()
|
||||||
|
if context.asset not in open_orders:
|
||||||
|
# check that the asset of interest can currently be traded
|
||||||
|
if data.can_trade(context.asset):
|
||||||
|
# adjust portfolio based on comparison of long and short vwap
|
||||||
|
if short_mavg > long_mavg:
|
||||||
|
order_target_percent(
|
||||||
|
context.asset,
|
||||||
|
context.TARGET_INVESTMENT_RATIO,
|
||||||
|
)
|
||||||
|
elif short_mavg < long_mavg:
|
||||||
|
order_target_percent(
|
||||||
|
context.asset,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
record(
|
||||||
|
price=price,
|
||||||
|
cash=context.portfolio.cash,
|
||||||
|
leverage=context.account.leverage,
|
||||||
|
short_mavg=short_mavg,
|
||||||
|
long_mavg=long_mavg,
|
||||||
|
volume=volume,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(context=None, results=None):
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
# Plot the portfolio and asset data.
|
||||||
|
ax1 = plt.subplot(611)
|
||||||
|
results[['portfolio_value']].plot(ax=ax1)
|
||||||
|
ax1.set_ylabel('Portfolio value (USD)')
|
||||||
|
|
||||||
|
ax2 = plt.subplot(612, sharex=ax1)
|
||||||
|
ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME))
|
||||||
|
(context.TICK_SIZE*results[['price', 'short_mavg', 'long_mavg']]).plot(ax=ax2)
|
||||||
|
|
||||||
|
trans = results.ix[[t != [] for t in results.transactions]]
|
||||||
|
amounts = [t[0]['amount'] for t in trans.transactions]
|
||||||
|
|
||||||
|
buys = trans.ix[
|
||||||
|
[t[0]['amount'] > 0 for t in trans.transactions]
|
||||||
|
]
|
||||||
|
sells = trans.ix[
|
||||||
|
[t[0]['amount'] < 0 for t in trans.transactions]
|
||||||
|
]
|
||||||
|
|
||||||
|
ax2.plot(
|
||||||
|
buys.index,
|
||||||
|
context.TICK_SIZE * results.price[buys.index],
|
||||||
|
'^',
|
||||||
|
markersize=10,
|
||||||
|
color='g',
|
||||||
|
)
|
||||||
|
ax2.plot(
|
||||||
|
sells.index,
|
||||||
|
context.TICK_SIZE * results.price[sells.index],
|
||||||
|
'v',
|
||||||
|
markersize=10,
|
||||||
|
color='r',
|
||||||
|
)
|
||||||
|
|
||||||
|
ax3 = plt.subplot(613, sharex=ax1)
|
||||||
|
results[['leverage', 'alpha', 'beta']].plot(ax=ax3)
|
||||||
|
ax3.set_ylabel('Leverage (USD)')
|
||||||
|
|
||||||
|
ax4 = plt.subplot(614, sharex=ax1)
|
||||||
|
results[['cash']].plot(ax=ax4)
|
||||||
|
ax4.set_ylabel('Cash (USD)')
|
||||||
|
|
||||||
|
results[[
|
||||||
|
'treasury',
|
||||||
|
'algorithm',
|
||||||
|
'benchmark',
|
||||||
|
]] = results[[
|
||||||
|
'treasury_period_return',
|
||||||
|
'algorithm_period_return',
|
||||||
|
'benchmark_period_return',
|
||||||
|
]]
|
||||||
|
|
||||||
|
ax5 = plt.subplot(615, sharex=ax1)
|
||||||
|
results[[
|
||||||
|
'treasury',
|
||||||
|
'algorithm',
|
||||||
|
'benchmark',
|
||||||
|
]].plot(ax=ax5)
|
||||||
|
ax5.set_ylabel('Percent Change')
|
||||||
|
|
||||||
|
ax6 = plt.subplot(616, sharex=ax1)
|
||||||
|
results[['volume']].plot(ax=ax6)
|
||||||
|
ax6.set_ylabel('Volume (mBTC/day)')
|
||||||
|
|
||||||
|
plt.legend(loc=3)
|
||||||
|
|
||||||
|
# Show the plot.
|
||||||
|
plt.gcf().set_size_inches(18, 8)
|
||||||
|
plt.show()
|
||||||
@@ -13,7 +13,6 @@ from logbook import Logger
|
|||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import symbol, record, order_target_percent, get_open_orders
|
from catalyst.api import symbol, record, order_target_percent, get_open_orders
|
||||||
from catalyst.exchange.stats_utils import extract_transactions
|
from catalyst.exchange.stats_utils import extract_transactions
|
||||||
|
|
||||||
# We give a name to the algorithm which Catalyst will use to persist its state.
|
# 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`
|
# In this example, Catalyst will create the `.catalyst/data/live_algos`
|
||||||
# directory. If we stop and start the algorithm, Catalyst will resume its
|
# directory. If we stop and start the algorithm, Catalyst will resume its
|
||||||
@@ -34,19 +33,16 @@ def initialize(context):
|
|||||||
# parameters or values you're going to use.
|
# parameters or values you're going to use.
|
||||||
|
|
||||||
# In our example, we're looking at Neo in Ether.
|
# In our example, we're looking at Neo in Ether.
|
||||||
context.market = symbol('neo_eth')
|
context.neo_eth = symbol('neo_eth')
|
||||||
context.base_price = None
|
context.base_price = None
|
||||||
context.current_day = None
|
context.current_day = None
|
||||||
|
|
||||||
context.RSI_OVERSOLD = 30
|
context.RSI_OVERSOLD = 55
|
||||||
context.RSI_OVERBOUGHT = 80
|
context.RSI_OVERBOUGHT = 82
|
||||||
context.CANDLE_SIZE = '5T'
|
context.CANDLE_SIZE = '5T'
|
||||||
|
|
||||||
context.start_time = time.time()
|
context.start_time = time.time()
|
||||||
|
|
||||||
# context.set_commission(maker=0.1, taker=0.2)
|
|
||||||
context.set_slippage(spread=0.0001)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
# This handle_data function is where the real work is done. Our data is
|
# This handle_data function is where the real work is done. Our data is
|
||||||
@@ -63,14 +59,14 @@ def handle_data(context, data):
|
|||||||
context.current_day = today
|
context.current_day = today
|
||||||
|
|
||||||
# We're computing the volume-weighted-average-price of the security
|
# We're computing the volume-weighted-average-price of the security
|
||||||
# defined above, in the context.market variable. For this example, we're
|
# defined above, in the context.neo_eth variable. For this example, we're
|
||||||
# using three bars on the 15 min bars.
|
# using three bars on the 15 min bars.
|
||||||
|
|
||||||
# The frequency attribute determine the bar size. We use this convention
|
# The frequency attribute determine the bar size. We use this convention
|
||||||
# for the frequency alias:
|
# for the frequency alias:
|
||||||
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
|
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
|
||||||
prices = data.history(
|
prices = data.history(
|
||||||
context.market,
|
context.neo_eth,
|
||||||
fields='close',
|
fields='close',
|
||||||
bar_count=50,
|
bar_count=50,
|
||||||
frequency=context.CANDLE_SIZE
|
frequency=context.CANDLE_SIZE
|
||||||
@@ -85,7 +81,7 @@ def handle_data(context, data):
|
|||||||
# We need a variable for the current price of the security to compare to
|
# We need a variable for the current price of the security to compare to
|
||||||
# the average. Since we are requesting two fields, data.current()
|
# the average. Since we are requesting two fields, data.current()
|
||||||
# returns a DataFrame with
|
# returns a DataFrame with
|
||||||
current = data.current(context.market, fields=['close', 'volume'])
|
current = data.current(context.neo_eth, fields=['close', 'volume'])
|
||||||
price = current['close']
|
price = current['close']
|
||||||
|
|
||||||
# If base_price is not set, we use the current value. This is the
|
# If base_price is not set, we use the current value. This is the
|
||||||
@@ -99,36 +95,34 @@ def handle_data(context, data):
|
|||||||
# Now that we've collected all current data for this frame, we use
|
# 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
|
# the record() method to save it. This data will be available as
|
||||||
# a parameter of the analyze() function for further analysis.
|
# a parameter of the analyze() function for further analysis.
|
||||||
|
|
||||||
record(
|
record(
|
||||||
volume=current['volume'],
|
|
||||||
price=price,
|
price=price,
|
||||||
|
volume=current['volume'],
|
||||||
price_change=price_change,
|
price_change=price_change,
|
||||||
rsi=rsi[-1],
|
rsi=rsi[-1],
|
||||||
cash=cash
|
cash=cash
|
||||||
)
|
)
|
||||||
|
|
||||||
# We are trying to avoid over-trading by limiting our trades to
|
# We are trying to avoid over-trading by limiting our trades to
|
||||||
# one per day.
|
# one per day.
|
||||||
if context.traded_today:
|
if context.traded_today:
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: retest with open orders
|
|
||||||
# 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.market)
|
orders = get_open_orders(context.neo_eth)
|
||||||
if len(orders) > 0:
|
if len(orders) > 0:
|
||||||
log.info('exiting because orders are open: {}'.format(orders))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Exit if we cannot trade
|
# Exit if we cannot trade
|
||||||
if not data.can_trade(context.market):
|
if not data.can_trade(context.neo_eth):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Another powerful built-in feature of the Catalyst backtester is the
|
# Another powerful built-in feature of the Catalyst backtester is the
|
||||||
# portfolio object. The portfolio object tracks your positions, cash,
|
# portfolio object. The portfolio object tracks your positions, cash,
|
||||||
# cost basis of specific holdings, and more. In this line, we calculate
|
# cost basis of specific holdings, and more. In this line, we calculate
|
||||||
# how long or short our position is at this minute.
|
# how long or short our position is at this minute.
|
||||||
pos_amount = context.portfolio.positions[context.market].amount
|
pos_amount = context.portfolio.positions[context.neo_eth].amount
|
||||||
|
|
||||||
if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0:
|
if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0:
|
||||||
log.info(
|
log.info(
|
||||||
@@ -139,7 +133,7 @@ def handle_data(context, data):
|
|||||||
# Set a style for limit orders,
|
# Set a style for limit orders,
|
||||||
limit_price = price * 1.005
|
limit_price = price * 1.005
|
||||||
order_target_percent(
|
order_target_percent(
|
||||||
context.market, 1, limit_price=limit_price
|
context.neo_eth, 1, limit_price=limit_price
|
||||||
)
|
)
|
||||||
context.traded_today = True
|
context.traded_today = True
|
||||||
|
|
||||||
@@ -151,7 +145,7 @@ def handle_data(context, data):
|
|||||||
)
|
)
|
||||||
limit_price = price * 0.995
|
limit_price = price * 0.995
|
||||||
order_target_percent(
|
order_target_percent(
|
||||||
context.market, 0, limit_price=limit_price
|
context.neo_eth, 0, limit_price=limit_price
|
||||||
)
|
)
|
||||||
context.traded_today = True
|
context.traded_today = True
|
||||||
|
|
||||||
@@ -174,7 +168,7 @@ def analyze(context=None, perf=None):
|
|||||||
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
|
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
|
||||||
|
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
ax2.set_ylabel('{asset}\n({base})'.format(
|
||||||
asset=context.market.symbol, base=base_currency
|
asset=context.neo_eth.symbol, base=base_currency
|
||||||
))
|
))
|
||||||
|
|
||||||
transaction_df = extract_transactions(perf)
|
transaction_df = extract_transactions(perf)
|
||||||
@@ -245,7 +239,7 @@ def analyze(context=None, perf=None):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# The execution mode: backtest or live
|
# The execution mode: backtest or live
|
||||||
MODE = 'backtest'
|
MODE = 'live'
|
||||||
|
|
||||||
if MODE == 'backtest':
|
if MODE == 'backtest':
|
||||||
folder = os.path.join(
|
folder = os.path.join(
|
||||||
@@ -255,9 +249,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
timestr = time.strftime('%Y%m%d-%H%M%S')
|
timestr = time.strftime('%Y%m%d-%H%M%S')
|
||||||
out = os.path.join(folder, '{}.p'.format(timestr))
|
out = os.path.join(folder, '{}.p'.format(timestr))
|
||||||
# catalyst run -f catalyst/examples/mean_reversion_simple.py \
|
# 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
|
||||||
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
|
|
||||||
# --data-frequency minute --capital-base 10000
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.1,
|
capital_base=0.1,
|
||||||
data_frequency='minute',
|
data_frequency='minute',
|
||||||
@@ -275,15 +267,13 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
elif MODE == 'live':
|
elif MODE == 'live':
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.05,
|
capital_base=0.1,
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='binance',
|
exchange_name='bittrex',
|
||||||
live=True,
|
live=True,
|
||||||
algo_namespace=NAMESPACE,
|
algo_namespace=NAMESPACE,
|
||||||
base_currency='eth',
|
base_currency='eth',
|
||||||
live_graph=False,
|
live_graph=False
|
||||||
simulate_orders=True,
|
|
||||||
stats_output=None
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import os
|
|||||||
import pytz
|
import pytz
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from scipy.optimize import minimize
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from catalyst.api import record, symbols, order_target_percent
|
from catalyst.api import record, symbol, symbols, order_target_percent
|
||||||
from catalyst.utils.run_algo import run_algorithm
|
from catalyst.utils.run_algo import run_algorithm
|
||||||
|
|
||||||
np.set_printoptions(threshold='nan', suppress=True)
|
np.set_printoptions(threshold='nan', suppress=True)
|
||||||
@@ -71,8 +72,7 @@ def handle_data(context, data):
|
|||||||
weights /= np.sum(weights)
|
weights /= np.sum(weights)
|
||||||
w = np.asmatrix(weights)
|
w = np.asmatrix(weights)
|
||||||
p_r = np.sum(np.dot(w,np.transpose(m)))*365
|
p_r = np.sum(np.dot(w,np.transpose(m)))*365
|
||||||
p_std = np.sqrt(np.dot(np.dot(w, cov_m),
|
p_std = np.sqrt(np.dot(np.dot(w,cov_m),np.transpose(w)))*np.sqrt(365)
|
||||||
np.transpose(w))) * np.sqrt(365)
|
|
||||||
|
|
||||||
#store results in results array
|
#store results in results array
|
||||||
results_array[0,p] = p_r
|
results_array[0,p] = p_r
|
||||||
@@ -87,12 +87,11 @@ def handle_data(context, data):
|
|||||||
|
|
||||||
#convert results array to Pandas DataFrame
|
#convert results array to Pandas DataFrame
|
||||||
results_frame = pd.DataFrame(np.transpose(results_array),
|
results_frame = pd.DataFrame(np.transpose(results_array),
|
||||||
columns=['r', 'stdev', 'sharpe']
|
columns=['r','stdev','sharpe']+context.assets)
|
||||||
+ context.assets)
|
|
||||||
#locate position of portfolio with highest Sharpe Ratio
|
#locate position of portfolio with highest Sharpe Ratio
|
||||||
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
|
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
|
||||||
#locate positon of portfolio with minimum standard deviation
|
#locate positon of portfolio with minimum standard deviation
|
||||||
# min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
|
min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
|
||||||
|
|
||||||
#order optimal weights for each asset
|
#order optimal weights for each asset
|
||||||
for asset in context.assets:
|
for asset in context.assets:
|
||||||
@@ -100,43 +99,28 @@ def handle_data(context, data):
|
|||||||
order_target_percent(asset, max_sharpe_port[asset])
|
order_target_percent(asset, max_sharpe_port[asset])
|
||||||
|
|
||||||
#create scatter plot coloured by Sharpe Ratio
|
#create scatter plot coloured by Sharpe Ratio
|
||||||
plt.scatter(results_frame.stdev,
|
plt.scatter(results_frame.stdev,results_frame.r,c=results_frame.sharpe,cmap='RdYlGn')
|
||||||
results_frame.r,
|
|
||||||
c=results_frame.sharpe,
|
|
||||||
cmap='RdYlGn')
|
|
||||||
plt.xlabel('Volatility')
|
plt.xlabel('Volatility')
|
||||||
plt.ylabel('Returns')
|
plt.ylabel('Returns')
|
||||||
plt.colorbar()
|
plt.colorbar()
|
||||||
# plot red star to highlight position of portfolio
|
#plot red star to highlight position of portfolio with highest Sharpe Ratio
|
||||||
# with highest Sharpe Ratio
|
plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker='o',color='b',s=200)
|
||||||
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
|
#plot green star to highlight position of minimum variance portfolio
|
||||||
plt.show()
|
plt.show()
|
||||||
print(max_sharpe_port)
|
print(max_sharpe_port)
|
||||||
record(pr=pr,
|
record(pr=pr,r=r, m=m, stds=stds ,max_sharpe_port=max_sharpe_port, corr_m=corr_m)
|
||||||
r=r,
|
|
||||||
m=m,
|
|
||||||
stds=stds,
|
|
||||||
max_sharpe_port=max_sharpe_port,
|
|
||||||
corr_m=corr_m)
|
|
||||||
context.i += 1
|
context.i += 1
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
def analyze(context=None, results=None):
|
||||||
# Form DataFrame with selected data
|
# Form DataFrame with selected data
|
||||||
data = results[['pr', 'r', 'm', 'stds', 'max_sharpe_port', 'corr_m',
|
data = results[['pr','r','m','stds','max_sharpe_port','corr_m','portfolio_value']]
|
||||||
'portfolio_value']]
|
|
||||||
|
|
||||||
# Save results in CSV file
|
# Save results in CSV file
|
||||||
filename = os.path.splitext(os.path.basename(__file__))[0]
|
filename = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
data.to_csv(filename + '.csv')
|
data.to_csv(filename + '.csv')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Bitcoin data is available from 2015-3-2. Dates vary for other tokens.
|
# Bitcoin data is available from 2015-3-2. Dates vary for other tokens.
|
||||||
start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc)
|
start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc)
|
||||||
end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc)
|
end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from catalyst.api import (
|
|||||||
record,
|
record,
|
||||||
get_open_orders,
|
get_open_orders,
|
||||||
)
|
)
|
||||||
|
from catalyst.exchange.stats_utils import crossover, crossunder
|
||||||
from catalyst.utils.run_algo import run_algorithm
|
from catalyst.utils.run_algo import run_algorithm
|
||||||
|
|
||||||
algo_namespace = 'rsi'
|
algo_namespace = 'rsi'
|
||||||
@@ -54,7 +55,7 @@ def _handle_buy_sell_decision(context, data, signal, price):
|
|||||||
stop=None
|
stop=None
|
||||||
)
|
)
|
||||||
|
|
||||||
# action = None
|
action = None
|
||||||
if context.position is not None:
|
if context.position is not None:
|
||||||
cost_basis = context.position['cost_basis']
|
cost_basis = context.position['cost_basis']
|
||||||
amount = context.position['amount']
|
amount = context.position['amount']
|
||||||
@@ -79,7 +80,7 @@ def _handle_buy_sell_decision(context, data, signal, price):
|
|||||||
amount=-amount,
|
amount=-amount,
|
||||||
limit_price=price * (1 - context.SLIPPAGE_ALLOWED),
|
limit_price=price * (1 - context.SLIPPAGE_ALLOWED),
|
||||||
)
|
)
|
||||||
# action = 0
|
action = 0
|
||||||
context.position = None
|
context.position = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -96,7 +97,7 @@ def _handle_buy_sell_decision(context, data, signal, price):
|
|||||||
amount=buy_amount,
|
amount=buy_amount,
|
||||||
stop=None
|
stop=None
|
||||||
)
|
)
|
||||||
# action = 0
|
action = 0
|
||||||
|
|
||||||
|
|
||||||
def _handle_data_rsi_only(context, data):
|
def _handle_data_rsi_only(context, data):
|
||||||
@@ -114,7 +115,7 @@ def _handle_data_rsi_only(context, data):
|
|||||||
prices = data.history(
|
prices = data.history(
|
||||||
context.asset,
|
context.asset,
|
||||||
fields='price',
|
fields='price',
|
||||||
bar_count=20,
|
bar_count=17,
|
||||||
frequency='30T'
|
frequency='30T'
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -249,7 +250,17 @@ def analyze(context=None, results=None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
# run_algorithm(
|
||||||
|
# initialize=initialize,
|
||||||
|
# handle_data=handle_data,
|
||||||
|
# analyze=analyze,
|
||||||
|
# exchange_name='bittrex',
|
||||||
|
# live=True,
|
||||||
|
# algo_namespace=algo_namespace,
|
||||||
|
# base_currency='btc',
|
||||||
|
# live_graph=False
|
||||||
|
# )
|
||||||
|
|
||||||
# Backtest
|
# Backtest
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.5,
|
capital_base=0.5,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@ from catalyst.exchange.stats_utils import get_pretty_stats, \
|
|||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
print('initializing')
|
print('initializing')
|
||||||
context.asset = symbol('eth_btc')
|
context.asset = symbol('neo_eth')
|
||||||
context.base_price = None
|
context.base_price = None
|
||||||
|
|
||||||
|
|
||||||
@@ -23,11 +23,8 @@ def handle_data(context, data):
|
|||||||
context.asset,
|
context.asset,
|
||||||
fields='price',
|
fields='price',
|
||||||
bar_count=20,
|
bar_count=20,
|
||||||
frequency='30T'
|
frequency='15T'
|
||||||
)
|
)
|
||||||
last_traded = prices.index[-1]
|
|
||||||
print('last candle date: {}'.format(last_traded))
|
|
||||||
|
|
||||||
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
||||||
print('got rsi: {}'.format(rsi))
|
print('got rsi: {}'.format(rsi))
|
||||||
|
|
||||||
@@ -110,16 +107,25 @@ def analyze(context, perf):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=1,
|
capital_base=250,
|
||||||
|
start=pd.to_datetime('2017-11-1 0:00', utc=True),
|
||||||
|
end=pd.to_datetime('2017-11-10 23:59', utc=True),
|
||||||
|
data_frequency='daily',
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=None,
|
analyze=analyze,
|
||||||
exchange_name='poloniex',
|
exchange_name='bitfinex',
|
||||||
live=True,
|
|
||||||
algo_namespace='simple_loop',
|
algo_namespace='simple_loop',
|
||||||
base_currency='eth',
|
base_currency='usd'
|
||||||
live_graph=False,
|
|
||||||
simulate_orders=True
|
|
||||||
)
|
)
|
||||||
|
# run_algorithm(
|
||||||
|
# initialize=initialize,
|
||||||
|
# handle_data=handle_data,
|
||||||
|
# analyze=None,
|
||||||
|
# exchange_name='binance',
|
||||||
|
# live=True,
|
||||||
|
# algo_namespace='simple_loop',
|
||||||
|
# base_currency='eth',
|
||||||
|
# live_graph=False,
|
||||||
|
# )
|
||||||
|
|||||||
@@ -2,117 +2,73 @@
|
|||||||
Requires Catalyst version 0.3.0 or above
|
Requires Catalyst version 0.3.0 or above
|
||||||
Tested on Catalyst version 0.3.3
|
Tested on Catalyst version 0.3.3
|
||||||
|
|
||||||
This example aims to provide an easy way for users to learn how to
|
These example aims to provide and easy way for users to learn how to collect data from the different exchanges.
|
||||||
collect data from any given exchange and select a subset of the available
|
You simply need to specify the exchange and the market that you want to focus on.
|
||||||
currency pairs for trading. You simply need to specify the exchange and
|
You will all see how to create a universe and filter it base on the exchange and the market you desire.
|
||||||
the market (base_currency) that you want to focus on. You will then see
|
|
||||||
how to create a universe of assets, and filter it based the market you
|
|
||||||
desire.
|
|
||||||
|
|
||||||
The example prints out the closing price of all the pairs for a given
|
The example prints out the closing price of all the pairs for a given market-exchange every 30 minutes.
|
||||||
market in a given exchange every 30 minutes. The example also contains
|
The example also contains the ohlcv minute data for the past seven days which could be used to create indicators
|
||||||
the OHLCV data with minute-resolution for the past seven days which
|
Use this as the backbone to create your own trading strategies.
|
||||||
could be used to create indicators. Use this code as the backbone to
|
|
||||||
create your own trading strategy.
|
|
||||||
|
|
||||||
The lookback_date variable is used to ensure data for a coin existed on
|
|
||||||
the lookback period specified.
|
|
||||||
|
|
||||||
To run, execute the following two commands in a terminal (inside catalyst
|
|
||||||
environment). The first one retrieves all the pricing data needed for this
|
|
||||||
script to run (only needs to be run once), and the second one executes this
|
|
||||||
script with the parameters specified in the run_algorithm() call at the end
|
|
||||||
of the file:
|
|
||||||
|
|
||||||
catalyst ingest-exchange -x bitfinex -f minute
|
|
||||||
|
|
||||||
python simple_universe.py
|
|
||||||
|
|
||||||
|
Variables lookback date and date are used to ensure data for a coin existed on the lookback period specified.
|
||||||
"""
|
"""
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from datetime import timedelta
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols
|
from catalyst.exchange.exchange_utils import get_exchange_symbols
|
||||||
from catalyst.api import (symbols, )
|
|
||||||
|
from catalyst.api import (
|
||||||
|
symbols,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.i = -1 # minute counter
|
context.i = -1 # counts the minutes
|
||||||
context.exchange = context.exchanges.values()[0].name.lower()
|
context.exchange = context.exchanges.values()[0].name.lower() # exchange name
|
||||||
context.base_currency = context.exchanges.values()[0].base_currency.lower()
|
context.base_currency = context.exchanges.values()[0].base_currency.lower() # market base currency
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
context.i += 1
|
context.i += 1
|
||||||
lookback_days = 7 # 7 days
|
lookback_days = 7 # 7 days
|
||||||
|
|
||||||
# current date & time in each iteration formatted into a string
|
# current date formatted into a string
|
||||||
now = data.current_dt
|
today = data.current_dt
|
||||||
date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ')
|
date, time = today.strftime('%Y-%m-%d %H:%M:%S').split(' ')
|
||||||
lookback_date = now - timedelta(days=lookback_days)
|
lookback_date = today - timedelta(days=lookback_days) # subtract the amount of days specified in lookback
|
||||||
# keep only the date as a string, discard the time
|
lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] # get only the date as a string
|
||||||
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
|
||||||
# update universe everyday at midnight
|
new_day = 60 * 24 # assuming data_frequency='minute'
|
||||||
if not context.i % one_day_in_minutes:
|
if not context.i % new_day:
|
||||||
context.universe = universe(context, lookback_date, date)
|
context.universe = universe(context, lookback_date, date)
|
||||||
|
|
||||||
# get data every 30 minutes
|
# get data every 30 minutes
|
||||||
minutes = 30
|
minutes = 30
|
||||||
|
one_day_in_minutes = 1440 # 1440 assumes data_frequency='minute'
|
||||||
# get lookback_days of history data: that is 'lookback' number of bins
|
lookback = one_day_in_minutes / minutes * lookback_days # get N lookback_days of history data
|
||||||
lookback = one_day_in_minutes / minutes * lookback_days
|
|
||||||
if not context.i % minutes and context.universe:
|
if not context.i % minutes and context.universe:
|
||||||
# we iterate for every pair in the current universe
|
# we iterate for every pair in the current universe
|
||||||
for coin in context.coins:
|
for coin in context.coins:
|
||||||
pair = str(coin.symbol)
|
pair = str(coin.symbol)
|
||||||
|
|
||||||
# Get 30 minute interval OHLCV data. This is the standard data
|
# 30 minute interval ohlcv data (the standard data required for candlestick or indicators/signals)
|
||||||
# required for candlestick or indicators/signals. Return Pandas
|
# 30T means 30 minutes re-sampling of one minute data. change to your desire time interval.
|
||||||
# DataFrames. 30T means 30-minute re-sampling of one minute data.
|
opened = fill(data.history(coin, 'open', bar_count=lookback, frequency='30T')).values
|
||||||
# Adjust it to your desired time interval as needed.
|
high = fill(data.history(coin, 'high', bar_count=lookback, frequency='30T')).values
|
||||||
opened = fill(data.history(coin,
|
low = fill(data.history(coin, 'low', bar_count=lookback, frequency='30T')).values
|
||||||
'open',
|
close = fill(data.history(coin, 'price', bar_count=lookback, frequency='30T')).values
|
||||||
bar_count=lookback,
|
volume = fill(data.history(coin, 'volume', bar_count=lookback, frequency='30T')).values
|
||||||
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
|
# close[-1] is the equivalent to current price
|
||||||
# to current price (as in the most recent value)
|
|
||||||
# displays the minute price for each pair every 30 minutes
|
# displays the minute price for each pair every 30 minutes
|
||||||
print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},'
|
print(today, pair, opened[-1], high[-1], low[-1], close[-1], volume[-1])
|
||||||
'\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 -------------------
|
# -------------------------------------- Insert Your Strategy Here -----------------------------------------
|
||||||
# -------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
def analyze(context=None, results=None):
|
||||||
@@ -122,24 +78,23 @@ def analyze(context=None, results=None):
|
|||||||
# Get the universe for a given exchange and a given base_currency market
|
# Get the universe for a given exchange and a given base_currency market
|
||||||
# Example: Poloniex BTC Market
|
# Example: Poloniex BTC Market
|
||||||
def universe(context, lookback_date, current_date):
|
def universe(context, lookback_date, current_date):
|
||||||
# get all the pairs for the given exchange
|
json_symbols = get_exchange_symbols(context.exchange) # get all the pairs for the exchange
|
||||||
json_symbols = get_exchange_symbols(context.exchange)
|
universe_df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) # convert into a dataframe
|
||||||
# convert into a DataFrame for easier processing
|
universe_df['base_currency'] = universe_df.apply(lambda row: row.symbol.split('_')[1],
|
||||||
df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str)
|
|
||||||
df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],
|
|
||||||
axis=1)
|
axis=1)
|
||||||
df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],
|
universe_df['market_currency'] = universe_df.apply(lambda row: row.symbol.split('_')[0],
|
||||||
axis=1)
|
axis=1)
|
||||||
|
|
||||||
# Filter all the pairs to get only the ones for a given base_currency
|
# Filter all the exchange pairs to only the ones for a give base currency
|
||||||
df = df[df['base_currency'] == context.base_currency]
|
universe_df = universe_df[universe_df['base_currency'] == context.base_currency]
|
||||||
|
|
||||||
# Filter all pairs to ensure that pair existed in the current date range
|
# Filter all the pairs to ensure that pair existed in the current date range
|
||||||
df = df[df.start_date < lookback_date]
|
universe_df = universe_df[universe_df.start_date < lookback_date]
|
||||||
df = df[df.end_daily >= current_date]
|
universe_df = universe_df[universe_df.end_daily >= current_date]
|
||||||
context.coins = symbols(*df.symbol) # convert all the pairs to symbols
|
context.coins = symbols(*universe_df.symbol) # convert all the pairs to symbols
|
||||||
|
|
||||||
return df.symbol.tolist()
|
# print(universe_df.symbol.tolist())
|
||||||
|
return universe_df.symbol.tolist()
|
||||||
|
|
||||||
|
|
||||||
# Replace all NA, NAN or infinite values with its nearest value
|
# Replace all NA, NAN or infinite values with its nearest value
|
||||||
@@ -147,9 +102,7 @@ def fill(series):
|
|||||||
if isinstance(series, pd.Series):
|
if isinstance(series, pd.Series):
|
||||||
return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
|
return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
|
||||||
elif isinstance(series, np.ndarray):
|
elif isinstance(series, np.ndarray):
|
||||||
return pd.Series(series).replace(
|
return pd.Series(series).replace([np.inf, -np.inf], np.nan).ffill().bfill().values
|
||||||
[np.inf, -np.inf], np.nan
|
|
||||||
).ffill().bfill().values
|
|
||||||
else:
|
else:
|
||||||
return series
|
return series
|
||||||
|
|
||||||
@@ -159,13 +112,18 @@ if __name__ == '__main__':
|
|||||||
end_date = pd.to_datetime('2017-11-13', utc=True)
|
end_date = pd.to_datetime('2017-11-13', utc=True)
|
||||||
|
|
||||||
performance = run_algorithm(start=start_date, end=end_date,
|
performance = run_algorithm(start=start_date, end=end_date,
|
||||||
capital_base=100.0, # amount of base_currency
|
capital_base=100.0, # amount of base_currency, not always in dollars unless usd
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='poloniex',
|
exchange_name='bitfinex',
|
||||||
data_frequency='minute',
|
data_frequency='minute',
|
||||||
base_currency='btc',
|
base_currency='btc',
|
||||||
live=False,
|
live=False,
|
||||||
live_graph=False,
|
live_graph=False,
|
||||||
algo_namespace='simple_universe')
|
algo_namespace='simple_universe')
|
||||||
|
|
||||||
|
"""
|
||||||
|
Run in Terminal (inside catalyst environment):
|
||||||
|
python simple_universe.py
|
||||||
|
"""
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
# Run Command
|
# Run Command
|
||||||
# catalyst run --start 2017-1-1 --end 2017-11-1 -o talib_simple.pickle \
|
# catalyst run --start 2017-1-1 --end 2017-11-1 -o talib_simple.pickle -f talib_simple.py -x poloniex
|
||||||
# -f talib_simple.py -x poloniex
|
|
||||||
#
|
#
|
||||||
# Description
|
# Description
|
||||||
# Simple TALib Example showing how to use various indicators
|
# Simple TALib Example showing how to use various indicators in you strategy
|
||||||
# in you strategy. Based loosly on
|
# Based loosly on https://github.com/mellertson/talib-macd-example/blob/master/talib-macd-matplotlib-example.py
|
||||||
# https://github.com/mellertson/talib-macd-example/blob/master/talib-macd-matplotlib-example.py
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import six
|
|||||||
from catalyst.assets._assets import TradingPair
|
from catalyst.assets._assets import TradingPair
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
|
||||||
from catalyst.exchange.exchange import Exchange
|
from catalyst.exchange.exchange import Exchange
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
@@ -30,17 +29,16 @@ from catalyst.protocol import Account
|
|||||||
|
|
||||||
# Trying to account for REST api instability
|
# Trying to account for REST api instability
|
||||||
# https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request
|
# https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
|
|
||||||
requests.adapters.DEFAULT_RETRIES = 20
|
requests.adapters.DEFAULT_RETRIES = 20
|
||||||
|
|
||||||
BITFINEX_URL = 'https://api.bitfinex.com'
|
BITFINEX_URL = 'https://api.bitfinex.com'
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
log = Logger('Bitfinex', level=LOG_LEVEL)
|
log = Logger('Bitfinex', level=LOG_LEVEL)
|
||||||
warning_logger = Logger('AlgoWarning')
|
warning_logger = Logger('AlgoWarning')
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class Bitfinex(Exchange):
|
class Bitfinex(Exchange):
|
||||||
def __init__(self, key, secret, base_currency, portfolio=None):
|
def __init__(self, key, secret, base_currency, portfolio=None):
|
||||||
self.url = BITFINEX_URL
|
self.url = BITFINEX_URL
|
||||||
@@ -174,8 +172,7 @@ class Bitfinex(Exchange):
|
|||||||
|
|
||||||
executed_price = float(order_status['avg_execution_price'])
|
executed_price = float(order_status['avg_execution_price'])
|
||||||
|
|
||||||
# TODO: bitfinex does not specify comission.
|
# TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it.
|
||||||
# I could calculate it but not sure if it's worth it.
|
|
||||||
commission = None
|
commission = None
|
||||||
|
|
||||||
date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
|
date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
|
||||||
@@ -602,17 +599,17 @@ class Bitfinex(Exchange):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
start_date = cached_symbols[symbol]['start_date']
|
start_date = cached_symbols[symbol]['start_date']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
start_date = time.strftime('%Y-%m-%d')
|
start_date = time.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
end_daily = cached_symbols[symbol]['end_daily']
|
end_daily = cached_symbols[symbol]['end_daily']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_daily = 'N/A'
|
end_daily = 'N/A'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
end_minute = cached_symbols[symbol]['end_minute']
|
end_minute = cached_symbols[symbol]['end_minute']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_minute = 'N/A'
|
end_minute = 'N/A'
|
||||||
|
|
||||||
symbol_map[symbol] = dict(
|
symbol_map[symbol] = dict(
|
||||||
@@ -663,16 +660,15 @@ class Bitfinex(Exchange):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Query again with daily resolution setting the start and end around
|
Query again with daily resolution setting the start and end around
|
||||||
the startmonth we got above. Avoid end dates greater than
|
the startmonth we got above. Avoid end dates greater than now: time.time()
|
||||||
now: time.time()
|
|
||||||
"""
|
"""
|
||||||
url = ('{url}/v2/candles/trade:1D:{symbol}/hist?start={start}'
|
url = '{url}/v2/candles/trade:1D:{symbol}/hist?start={start}&end={end}'.format(
|
||||||
'&end={end}').format(
|
|
||||||
url=self.url,
|
url=self.url,
|
||||||
symbol=symbol_v2,
|
symbol=symbol_v2,
|
||||||
start=startmonth - 3600 * 24 * 31 * 1000,
|
start=startmonth - 3600 * 24 * 31 * 1000,
|
||||||
end=min(startmonth + 3600 * 24 * 31 * 1000,
|
end=min(startmonth + 3600 * 24 * 31 * 1000,
|
||||||
int(time.time() * 1000)))
|
int(time.time() * 1000))
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ask_request()
|
self.ask_request()
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ from catalyst.finance.execution import LimitOrder, StopLimitOrder
|
|||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
|
|
||||||
# TODO: consider using this: https://github.com/mondeja/bittrex_v2
|
# TODO: consider using this: https://github.com/mondeja/bittrex_v2
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
|
|
||||||
log = Logger('Bittrex', level=LOG_LEVEL)
|
log = Logger('Bittrex', level=LOG_LEVEL)
|
||||||
|
|
||||||
URL2 = 'https://bittrex.com/Api/v2.0'
|
URL2 = 'https://bittrex.com/Api/v2.0'
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class Bittrex(Exchange):
|
class Bittrex(Exchange):
|
||||||
def __init__(self, key, secret, base_currency, portfolio=None):
|
def __init__(self, key, secret, base_currency, portfolio=None):
|
||||||
self.api = Bittrex_api(key=key, secret=secret)
|
self.api = Bittrex_api(key=key, secret=secret)
|
||||||
@@ -267,7 +265,8 @@ class Bittrex(Exchange):
|
|||||||
url=URL2,
|
url=URL2,
|
||||||
symbol=self.get_symbol(asset),
|
symbol=self.get_symbol(asset),
|
||||||
frequency=frequency,
|
frequency=frequency,
|
||||||
end=end, )
|
end=end
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(urllib.request.urlopen(url).read().decode())
|
data = json.loads(urllib.request.urlopen(url).read().decode())
|
||||||
@@ -360,12 +359,12 @@ class Bittrex(Exchange):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
end_daily = cached_symbols[exchange_symbol]['end_daily']
|
end_daily = cached_symbols[exchange_symbol]['end_daily']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_daily = 'N/A'
|
end_daily = 'N/A'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
end_minute = cached_symbols[exchange_symbol]['end_minute']
|
end_minute = cached_symbols[exchange_symbol]['end_minute']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_minute = 'N/A'
|
end_minute = 'N/A'
|
||||||
|
|
||||||
symbol_map[exchange_symbol] = dict(
|
symbol_map[exchange_symbol] = dict(
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ from datetime import timedelta, datetime, date
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytz
|
import pytz
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
|
|
||||||
from catalyst.data.bundles.core import download_without_progress
|
from catalyst.data.bundles.core import download_without_progress
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_bundles_folder
|
from catalyst.exchange.exchange_utils import get_exchange_bundles_folder, \
|
||||||
|
get_exchange_symbols
|
||||||
|
|
||||||
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
|
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
|
||||||
API_URL = 'http://data.enigma.co/api/v1'
|
API_URL = 'http://data.enigma.co/api/v1'
|
||||||
@@ -79,7 +81,8 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
|
|||||||
url = 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' \
|
url = 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' \
|
||||||
'exchange-{exchange}/{name}.tar.gz'.format(
|
'exchange-{exchange}/{name}.tar.gz'.format(
|
||||||
exchange=exchange_name,
|
exchange=exchange_name,
|
||||||
name=name)
|
name=name
|
||||||
|
)
|
||||||
|
|
||||||
bytes = download_without_progress(url)
|
bytes = download_without_progress(url)
|
||||||
with tarfile.open('r', fileobj=bytes) as tar:
|
with tarfile.open('r', fileobj=bytes) as tar:
|
||||||
@@ -190,10 +193,8 @@ def get_period_label(dt, data_frequency):
|
|||||||
str
|
str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if data_frequency == 'minute':
|
return '{}-{:02d}'.format(dt.year, dt.month) if data_frequency == 'minute' \
|
||||||
return '{}-{:02d}'.format(dt.year, dt.month)
|
else '{}'.format(dt.year)
|
||||||
else:
|
|
||||||
return '{}'.format(dt.year)
|
|
||||||
|
|
||||||
|
|
||||||
def get_month_start_end(dt, first_day=None, last_day=None):
|
def get_month_start_end(dt, first_day=None, last_day=None):
|
||||||
@@ -314,7 +315,7 @@ def range_in_bundle(asset, start_dt, end_dt, reader):
|
|||||||
if np.isnan(close):
|
if np.isnan(close):
|
||||||
has_data = False
|
has_data = False
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
has_data = False
|
has_data = False
|
||||||
|
|
||||||
return has_data
|
return has_data
|
||||||
|
|||||||
@@ -3,23 +3,23 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import six
|
from ccxt import ExchangeNotAvailable
|
||||||
from ccxt import ExchangeNotAvailable, InvalidOrder
|
|
||||||
from logbook import Logger
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
|
|
||||||
from catalyst.algorithm import MarketOrder
|
from catalyst.algorithm import MarketOrder
|
||||||
from catalyst.assets._assets import TradingPair
|
from catalyst.assets._assets import TradingPair
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.exchange import Exchange
|
from catalyst.exchange.exchange import Exchange, ExchangeLimitOrder
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
||||||
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
|
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
|
||||||
ExchangeNotFoundError, CreateOrderError
|
ExchangeNotFoundError
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
|
||||||
from catalyst.exchange.exchange_utils import mixin_market_params, \
|
from catalyst.exchange.exchange_utils import mixin_market_params, \
|
||||||
from_ms_timestamp, get_epoch
|
from_ms_timestamp
|
||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
|
||||||
|
|
||||||
log = Logger('CCXT', level=LOG_LEVEL)
|
log = Logger('CCXT', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -34,7 +34,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, base_currency,
|
||||||
|
portfolio=None):
|
||||||
log.debug(
|
log.debug(
|
||||||
'finding {} in CCXT exchanges:\n{}'.format(
|
'finding {} in CCXT exchanges:\n{}'.format(
|
||||||
exchange_name, ccxt.exchanges
|
exchange_name, ccxt.exchanges
|
||||||
@@ -58,19 +59,16 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
self._symbol_maps = [None, None]
|
self._symbol_maps = [None, None]
|
||||||
|
|
||||||
try:
|
|
||||||
markets_symbols = self.api.load_markets()
|
markets_symbols = self.api.load_markets()
|
||||||
log.debug('the markets:\n{}'.format(markets_symbols))
|
log.debug('the markets:\n{}'.format(markets_symbols))
|
||||||
|
|
||||||
except ExchangeNotAvailable as e:
|
|
||||||
raise ExchangeRequestError(error=e)
|
|
||||||
|
|
||||||
self.name = exchange_name
|
self.name = exchange_name
|
||||||
|
|
||||||
self.markets = self.api.fetch_markets()
|
self.markets = self.api.fetch_markets()
|
||||||
self.load_assets()
|
self.load_assets()
|
||||||
|
|
||||||
self.base_currency = base_currency
|
self.base_currency = base_currency
|
||||||
|
self._portfolio = portfolio
|
||||||
self.transactions = defaultdict(list)
|
self.transactions = defaultdict(list)
|
||||||
|
|
||||||
self.num_candles_limit = 2000
|
self.num_candles_limit = 2000
|
||||||
@@ -184,21 +182,13 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
def get_candles(self, freq, assets, bar_count=None, start_dt=None,
|
def get_candles(self, freq, assets, bar_count=None, start_dt=None,
|
||||||
end_dt=None):
|
end_dt=None):
|
||||||
is_single = (isinstance(assets, TradingPair))
|
|
||||||
if is_single:
|
|
||||||
assets = [assets]
|
|
||||||
|
|
||||||
symbols = self.get_symbols(assets)
|
symbols = self.get_symbols(assets)
|
||||||
timeframe = self.get_timeframe(freq)
|
timeframe = self.get_timeframe(freq)
|
||||||
|
delta = start_dt - pd.to_datetime('1970-1-1', utc=True)
|
||||||
ms = None
|
|
||||||
if start_dt is not None:
|
|
||||||
delta = start_dt - get_epoch()
|
|
||||||
ms = int(delta.total_seconds()) * 1000
|
ms = int(delta.total_seconds()) * 1000
|
||||||
|
|
||||||
candles = dict()
|
candles = dict()
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
try:
|
|
||||||
ohlcvs = self.api.fetch_ohlcv(
|
ohlcvs = self.api.fetch_ohlcv(
|
||||||
symbol=symbols[0],
|
symbol=symbols[0],
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
@@ -210,9 +200,7 @@ class CCXT(Exchange):
|
|||||||
candles[asset] = []
|
candles[asset] = []
|
||||||
for ohlcv in ohlcvs:
|
for ohlcv in ohlcvs:
|
||||||
candles[asset].append(dict(
|
candles[asset].append(dict(
|
||||||
last_traded=pd.to_datetime(
|
last_traded=pd.to_datetime(ohlcv[0], unit='ms', utc=True),
|
||||||
ohlcv[0], unit='ms', utc=True
|
|
||||||
),
|
|
||||||
open=ohlcv[1],
|
open=ohlcv[1],
|
||||||
high=ohlcv[2],
|
high=ohlcv[2],
|
||||||
low=ohlcv[3],
|
low=ohlcv[3],
|
||||||
@@ -220,13 +208,6 @@ class CCXT(Exchange):
|
|||||||
volume=ohlcv[5]
|
volume=ohlcv[5]
|
||||||
))
|
))
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise ExchangeRequestError(error=e)
|
|
||||||
|
|
||||||
if is_single:
|
|
||||||
return six.next(six.itervalues(candles))
|
|
||||||
|
|
||||||
else:
|
|
||||||
return candles
|
return candles
|
||||||
|
|
||||||
def _fetch_symbol_map(self, is_local):
|
def _fetch_symbol_map(self, is_local):
|
||||||
@@ -293,7 +274,7 @@ class CCXT(Exchange):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_trading_pair(self, market, asset_def=None, is_local=False):
|
def create_trading_pair(self, market, asset_def, is_local):
|
||||||
"""
|
"""
|
||||||
Creating a TradingPair from market and asset data.
|
Creating a TradingPair from market and asset data.
|
||||||
|
|
||||||
@@ -351,7 +332,6 @@ class CCXT(Exchange):
|
|||||||
for market in self.markets:
|
for market in self.markets:
|
||||||
asset_defs = self.get_asset_defs(market)
|
asset_defs = self.get_asset_defs(market)
|
||||||
|
|
||||||
asset = None
|
|
||||||
for asset_def in asset_defs:
|
for asset_def in asset_defs:
|
||||||
if asset_def[0] is not None or not asset_defs[1]:
|
if asset_def[0] is not None or not asset_defs[1]:
|
||||||
try:
|
try:
|
||||||
@@ -362,12 +342,8 @@ class CCXT(Exchange):
|
|||||||
)
|
)
|
||||||
self.assets.append(asset)
|
self.assets.append(asset)
|
||||||
|
|
||||||
except TypeError as e:
|
except TypeError:
|
||||||
log.warn('unable to add asset: {}'.format(e))
|
pass
|
||||||
|
|
||||||
if asset is None:
|
|
||||||
asset = self.create_trading_pair(market=market)
|
|
||||||
self.assets.append(asset)
|
|
||||||
|
|
||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
try:
|
try:
|
||||||
@@ -470,45 +446,26 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
side = 'buy' if amount > 0 else 'sell'
|
side = 'buy' if amount > 0 else 'sell'
|
||||||
|
|
||||||
if hasattr(self.api, 'amount_to_lots'):
|
|
||||||
adj_amount = self.api.amount_to_lots(
|
|
||||||
symbol=symbol,
|
|
||||||
amount=abs(amount),
|
|
||||||
)
|
|
||||||
if adj_amount != abs(amount):
|
|
||||||
log.info(
|
|
||||||
'adjusted order amount {} to {} based on lot size'.format(
|
|
||||||
abs(amount), adj_amount,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
adj_amount = abs(amount)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.api.create_order(
|
result = self.api.create_order(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
type=order_type,
|
type=order_type,
|
||||||
side=side,
|
side=side,
|
||||||
amount=adj_amount,
|
amount=abs(amount),
|
||||||
price=price
|
price=price
|
||||||
)
|
)
|
||||||
except ExchangeNotAvailable as e:
|
except ExchangeNotAvailable as e:
|
||||||
log.debug('unable to create order: {}'.format(e))
|
log.debug('unable to create order: {}'.format(e))
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
|
|
||||||
except InvalidOrder as e:
|
|
||||||
log.warn('the exchange rejected the order: {}'.format(e))
|
|
||||||
raise CreateOrderError(exchange=self.name, error=e)
|
|
||||||
|
|
||||||
if 'info' not in result:
|
if 'info' not in result:
|
||||||
raise ValueError('cannot use order without info attribute')
|
raise ValueError('cannot use order without info attribute')
|
||||||
|
|
||||||
final_amount = adj_amount if side == 'buy' else -adj_amount
|
|
||||||
order_id = result['id']
|
order_id = result['id']
|
||||||
order = Order(
|
order = Order(
|
||||||
dt=pd.Timestamp.utcnow(),
|
dt=pd.Timestamp.utcnow(),
|
||||||
asset=asset,
|
asset=asset,
|
||||||
amount=final_amount,
|
amount=amount,
|
||||||
stop=style.get_stop_price(is_buy),
|
stop=style.get_stop_price(is_buy),
|
||||||
limit=style.get_limit_price(is_buy),
|
limit=style.get_limit_price(is_buy),
|
||||||
id=order_id
|
id=order_id
|
||||||
@@ -535,7 +492,18 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
return orders
|
return orders
|
||||||
|
|
||||||
|
def _get_asset_from_order(self, order_id):
|
||||||
|
open_orders = self.portfolio.open_orders
|
||||||
|
order = next(
|
||||||
|
(open_orders[id] for id in open_orders if id == order_id),
|
||||||
|
None
|
||||||
|
) # type: Order
|
||||||
|
return order.asset if order is not None else None
|
||||||
|
|
||||||
def get_order(self, order_id, asset_or_symbol=None):
|
def get_order(self, order_id, asset_or_symbol=None):
|
||||||
|
if asset_or_symbol is None and self.portfolio is not None:
|
||||||
|
asset_or_symbol = self._get_asset_from_order(order_id)
|
||||||
|
|
||||||
if asset_or_symbol is None:
|
if asset_or_symbol is None:
|
||||||
log.debug(
|
log.debug(
|
||||||
'order not found in memory, the request might fail '
|
'order not found in memory, the request might fail '
|
||||||
@@ -556,6 +524,9 @@ class CCXT(Exchange):
|
|||||||
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
|
||||||
|
|
||||||
|
if asset_or_symbol is None and self.portfolio is not None:
|
||||||
|
asset_or_symbol = self._get_asset_from_order(order_id)
|
||||||
|
|
||||||
if asset_or_symbol is None:
|
if asset_or_symbol is None:
|
||||||
log.debug(
|
log.debug(
|
||||||
'order not found in memory, cancelling order might fail '
|
'order not found in memory, cancelling order might fail '
|
||||||
@@ -584,7 +555,6 @@ class CCXT(Exchange):
|
|||||||
"""
|
"""
|
||||||
tickers = dict()
|
tickers = dict()
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
try:
|
|
||||||
ccxt_symbol = self.get_symbol(asset)
|
ccxt_symbol = self.get_symbol(asset)
|
||||||
ticker = self.api.fetch_ticker(ccxt_symbol)
|
ticker = self.api.fetch_ticker(ccxt_symbol)
|
||||||
|
|
||||||
@@ -600,14 +570,6 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
tickers[asset] = ticker
|
tickers[asset] = ticker
|
||||||
|
|
||||||
except ExchangeNotAvailable as e:
|
|
||||||
log.warn(
|
|
||||||
'unable to fetch ticker: {} {}'.format(
|
|
||||||
self.name, asset.symbol
|
|
||||||
)
|
|
||||||
)
|
|
||||||
raise ExchangeRequestError(error=e)
|
|
||||||
|
|
||||||
return tickers
|
return tickers
|
||||||
|
|
||||||
def get_account(self):
|
def get_account(self):
|
||||||
|
|||||||
+134
-63
@@ -5,8 +5,10 @@ from time import sleep
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
from catalyst.algorithm import MarketOrder
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.data.data_portal import BASE_FIELDS
|
from catalyst.data.data_portal import BASE_FIELDS
|
||||||
from catalyst.exchange.bundle_utils import get_start_dt, \
|
from catalyst.exchange.bundle_utils import get_start_dt, \
|
||||||
@@ -15,9 +17,14 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
|||||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||||
BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||||
PricingDataNotLoadedError, \
|
PricingDataNotLoadedError, \
|
||||||
NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError
|
NoDataAvailableOnExchange, NoValueForField
|
||||||
|
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
||||||
|
ExchangeLimitOrder, ExchangeStopOrder
|
||||||
|
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols, \
|
from catalyst.exchange.exchange_utils import get_exchange_symbols, \
|
||||||
get_frequency, resample_history_df
|
get_frequency, resample_history_df
|
||||||
|
from catalyst.finance.order import ORDER_STATUS
|
||||||
|
from catalyst.finance.transaction import Transaction
|
||||||
|
|
||||||
log = Logger('Exchange', level=LOG_LEVEL)
|
log = Logger('Exchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -29,6 +36,7 @@ class Exchange:
|
|||||||
self.name = None
|
self.name = None
|
||||||
self.assets = []
|
self.assets = []
|
||||||
self._symbol_maps = [None, None]
|
self._symbol_maps = [None, None]
|
||||||
|
self._portfolio = None
|
||||||
self.minute_writer = None
|
self.minute_writer = None
|
||||||
self.minute_reader = None
|
self.minute_reader = None
|
||||||
self.base_currency = None
|
self.base_currency = None
|
||||||
@@ -38,6 +46,27 @@ class Exchange:
|
|||||||
self.request_cpt = None
|
self.request_cpt = None
|
||||||
self.bundle = ExchangeBundle(self.name)
|
self.bundle = ExchangeBundle(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def positions(self):
|
||||||
|
return self.portfolio.positions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def portfolio(self):
|
||||||
|
"""
|
||||||
|
The exchange portfolio
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ExchangePortfolio
|
||||||
|
"""
|
||||||
|
if self._portfolio is None:
|
||||||
|
self._portfolio = ExchangePortfolio(
|
||||||
|
start_date=pd.Timestamp.utcnow()
|
||||||
|
)
|
||||||
|
self.synchronize_portfolio()
|
||||||
|
|
||||||
|
return self._portfolio
|
||||||
|
|
||||||
@abstractproperty
|
@abstractproperty
|
||||||
def account(self):
|
def account(self):
|
||||||
pass
|
pass
|
||||||
@@ -176,18 +205,10 @@ class Exchange:
|
|||||||
|
|
||||||
assets = []
|
assets = []
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
try:
|
|
||||||
asset = self.get_asset(
|
asset = self.get_asset(
|
||||||
symbol, data_frequency, is_exchange_symbol, is_local
|
symbol, data_frequency, is_exchange_symbol, is_local
|
||||||
)
|
)
|
||||||
assets.append(asset)
|
assets.append(asset)
|
||||||
|
|
||||||
except SymbolNotFoundOnExchange:
|
|
||||||
log.debug(
|
|
||||||
'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,
|
||||||
@@ -235,10 +256,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 (data_frequency == 'daily' and a.end_daily is not None)
|
||||||
or (
|
|
||||||
data_frequency == 'daily' and a.end_daily is not None)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -251,7 +270,9 @@ class Exchange:
|
|||||||
asset = a
|
asset = a
|
||||||
|
|
||||||
if asset is None:
|
if asset is None:
|
||||||
supported_symbols = sorted([a.symbol for a in self.assets])
|
supported_symbols = sorted([
|
||||||
|
asset.symbol for asset in self.assets
|
||||||
|
])
|
||||||
|
|
||||||
raise SymbolNotFoundOnExchange(
|
raise SymbolNotFoundOnExchange(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
@@ -292,6 +313,54 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def check_open_orders(self):
|
||||||
|
"""
|
||||||
|
Loop through the list of open orders in the Portfolio object.
|
||||||
|
For each executed order found, create a transaction and apply to the
|
||||||
|
Portfolio.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[Transaction]
|
||||||
|
|
||||||
|
"""
|
||||||
|
transactions = list()
|
||||||
|
if self.portfolio.open_orders:
|
||||||
|
for order_id in list(self.portfolio.open_orders):
|
||||||
|
log.debug('found open order: {}'.format(order_id))
|
||||||
|
|
||||||
|
order, executed_price = self.get_order(order_id)
|
||||||
|
log.debug(
|
||||||
|
'got updated order {} {}'.format(
|
||||||
|
order, executed_price
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if order.status == ORDER_STATUS.FILLED:
|
||||||
|
transaction = Transaction(
|
||||||
|
asset=order.asset,
|
||||||
|
amount=order.amount,
|
||||||
|
dt=pd.Timestamp.utcnow(),
|
||||||
|
price=executed_price,
|
||||||
|
order_id=order.id,
|
||||||
|
commission=order.commission
|
||||||
|
)
|
||||||
|
transactions.append(transaction)
|
||||||
|
|
||||||
|
self.portfolio.execute_order(order, transaction)
|
||||||
|
|
||||||
|
elif order.status == ORDER_STATUS.CANCELLED:
|
||||||
|
self.portfolio.remove_order(order)
|
||||||
|
|
||||||
|
else:
|
||||||
|
delta = pd.Timestamp.utcnow() - order.dt
|
||||||
|
log.info(
|
||||||
|
'order {order_id} still open after {delta}'.format(
|
||||||
|
order_id=order_id,
|
||||||
|
delta=delta
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return transactions
|
||||||
|
|
||||||
def get_spot_value(self, assets, field, dt=None, data_frequency='minute'):
|
def get_spot_value(self, assets, field, dt=None, data_frequency='minute'):
|
||||||
"""
|
"""
|
||||||
Public API method that returns a scalar value representing the value
|
Public API method that returns a scalar value representing the value
|
||||||
@@ -409,7 +478,7 @@ class Exchange:
|
|||||||
method='ffill',
|
method='ffill',
|
||||||
fill_value=previous_value,
|
fill_value=previous_value,
|
||||||
)
|
)
|
||||||
series.sort_index(inplace=True)
|
|
||||||
return series
|
return series
|
||||||
|
|
||||||
def get_history_window(self,
|
def get_history_window(self,
|
||||||
@@ -419,7 +488,7 @@ class Exchange:
|
|||||||
frequency,
|
frequency,
|
||||||
field,
|
field,
|
||||||
data_frequency=None,
|
data_frequency=None,
|
||||||
is_current=False):
|
ffill=True):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Public API method that returns a dataframe containing the requested
|
Public API method that returns a dataframe containing the requested
|
||||||
@@ -446,15 +515,10 @@ class Exchange:
|
|||||||
The frequency of the data to query; i.e. whether the data is
|
The frequency of the data to query; i.e. whether the data is
|
||||||
'daily' or 'minute' bars.
|
'daily' or 'minute' bars.
|
||||||
|
|
||||||
is_current: bool
|
# TODO: fill how?
|
||||||
Skip date filters when current data is requested (last few bars
|
ffill: boolean
|
||||||
until now).
|
Forward-fill missing values. Only has effect if field
|
||||||
|
is 'price'.
|
||||||
Notes
|
|
||||||
-----
|
|
||||||
Catalysts requires an end data with bar count both CCXT wants a
|
|
||||||
start data with bar count. Since we have to make calculations here,
|
|
||||||
we ensure that the last candle match the end_dt parameter.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -466,7 +530,6 @@ class Exchange:
|
|||||||
frequency, data_frequency
|
frequency, data_frequency
|
||||||
)
|
)
|
||||||
adj_bar_count = candle_size * bar_count
|
adj_bar_count = candle_size * bar_count
|
||||||
|
|
||||||
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
||||||
|
|
||||||
# The get_history method supports multiple asset
|
# The get_history method supports multiple asset
|
||||||
@@ -474,8 +537,8 @@ class Exchange:
|
|||||||
freq=freq,
|
freq=freq,
|
||||||
assets=assets,
|
assets=assets,
|
||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
start_dt=start_dt if not is_current else None,
|
start_dt=start_dt,
|
||||||
end_dt=end_dt if not is_current else None,
|
end_dt=end_dt
|
||||||
)
|
)
|
||||||
|
|
||||||
series = dict()
|
series = dict()
|
||||||
@@ -487,17 +550,6 @@ class Exchange:
|
|||||||
data_frequency=frequency,
|
data_frequency=frequency,
|
||||||
field=field,
|
field=field,
|
||||||
)
|
)
|
||||||
if end_dt is not None:
|
|
||||||
delta = get_delta(candle_size, data_frequency)
|
|
||||||
adj_end_dt = end_dt - delta
|
|
||||||
last_traded = asset_series.index[-1]
|
|
||||||
|
|
||||||
if last_traded < adj_end_dt:
|
|
||||||
raise LastCandleTooEarlyError(
|
|
||||||
last_traded=last_traded,
|
|
||||||
end_dt=adj_end_dt,
|
|
||||||
exchange=self.name,
|
|
||||||
)
|
|
||||||
series[asset] = asset_series
|
series[asset] = asset_series
|
||||||
|
|
||||||
df = pd.DataFrame(series)
|
df = pd.DataFrame(series)
|
||||||
@@ -555,7 +607,6 @@ class Exchange:
|
|||||||
frequency, data_frequency
|
frequency, data_frequency
|
||||||
)
|
)
|
||||||
adj_bar_count = candle_size * bar_count
|
adj_bar_count = candle_size * bar_count
|
||||||
|
|
||||||
try:
|
try:
|
||||||
series = self.bundle.get_history_window_series_and_load(
|
series = self.bundle.get_history_window_series_and_load(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
@@ -565,7 +616,6 @@ class Exchange:
|
|||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
force_auto_ingest=force_auto_ingest
|
force_auto_ingest=force_auto_ingest
|
||||||
)
|
)
|
||||||
|
|
||||||
except (PricingDataNotLoadedError, NoDataAvailableOnExchange):
|
except (PricingDataNotLoadedError, NoDataAvailableOnExchange):
|
||||||
series = dict()
|
series = dict()
|
||||||
|
|
||||||
@@ -619,48 +669,51 @@ class Exchange:
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def calculate_totals(self, check_cash=False, positions=None):
|
def synchronize_portfolio(self):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
||||||
|
|
||||||
cash = None
|
|
||||||
if check_cash:
|
|
||||||
balances = self.get_balances()
|
balances = self.get_balances()
|
||||||
|
|
||||||
cash = balances[self.base_currency]['free'] \
|
base_position_available = balances[self.base_currency]['free'] \
|
||||||
if self.base_currency in balances else None
|
if self.base_currency in balances else None
|
||||||
|
|
||||||
if cash is None:
|
if base_position_available is None:
|
||||||
raise BaseCurrencyNotFoundError(
|
raise BaseCurrencyNotFoundError(
|
||||||
base_currency=self.base_currency,
|
base_currency=self.base_currency,
|
||||||
exchange=self.name
|
exchange=self.name.title()
|
||||||
)
|
)
|
||||||
log.debug('found base currency balance: {}'.format(cash))
|
|
||||||
|
|
||||||
positions_value = 0.0
|
portfolio = self._portfolio
|
||||||
if positions:
|
portfolio.cash = base_position_available
|
||||||
assets = set([position.asset for position in positions])
|
log.debug('found base currency balance: {}'.format(portfolio.cash))
|
||||||
|
|
||||||
|
if portfolio.starting_cash is None:
|
||||||
|
portfolio.starting_cash = portfolio.cash
|
||||||
|
|
||||||
|
if portfolio.positions:
|
||||||
|
assets = list(portfolio.positions.keys())
|
||||||
tickers = self.tickers(assets)
|
tickers = self.tickers(assets)
|
||||||
log.debug('got tickers for positions: {}'.format(tickers))
|
|
||||||
|
|
||||||
|
portfolio.positions_value = 0.0
|
||||||
for asset in tickers:
|
for asset in tickers:
|
||||||
|
# TODO: convert if the position is not in the base currency
|
||||||
ticker = tickers[asset]
|
ticker = tickers[asset]
|
||||||
positions = [p for p in positions if p.asset == asset]
|
position = portfolio.positions[asset]
|
||||||
|
|
||||||
for position in positions:
|
|
||||||
position.last_sale_price = ticker['last_price']
|
position.last_sale_price = ticker['last_price']
|
||||||
position.last_sale_date = ticker['last_traded']
|
position.last_sale_date = ticker['last_traded']
|
||||||
|
|
||||||
positions_value += \
|
portfolio.positions_value += \
|
||||||
position.amount * position.last_sale_price
|
position.amount * position.last_sale_price
|
||||||
|
portfolio.portfolio_value = \
|
||||||
|
portfolio.positions_value + portfolio.cash
|
||||||
|
|
||||||
return cash, positions_value
|
def order(self, asset, amount, limit_price=None, stop_price=None,
|
||||||
|
style=None):
|
||||||
def order(self, asset, amount, style):
|
|
||||||
"""Place an order.
|
"""Place an order.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -719,11 +772,24 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
|
|
||||||
is_buy = (amount > 0)
|
is_buy = (amount > 0)
|
||||||
display_price = style.get_limit_price(is_buy)
|
|
||||||
|
|
||||||
|
if limit_price is not None and stop_price is not None:
|
||||||
|
style = ExchangeStopLimitOrder(
|
||||||
|
limit_price, stop_price, exchange=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
elif limit_price is not None:
|
||||||
|
style = ExchangeLimitOrder(limit_price, exchange=self.name)
|
||||||
|
|
||||||
|
elif stop_price is not None:
|
||||||
|
style = ExchangeStopOrder(stop_price, exchange=self.name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
style = MarketOrder(exchange=self.name)
|
||||||
|
|
||||||
|
display_price = limit_price if limit_price is not None else stop_price
|
||||||
log.debug(
|
log.debug(
|
||||||
'issuing {side} order of {amount} {symbol} for {type}:'
|
'issuing {side} order of {amount} {symbol} for {type}: {price}'.format(
|
||||||
' {price}'.format(
|
|
||||||
side='buy' if is_buy else 'sell',
|
side='buy' if is_buy else 'sell',
|
||||||
amount=amount,
|
amount=amount,
|
||||||
symbol=asset.symbol,
|
symbol=asset.symbol,
|
||||||
@@ -732,7 +798,12 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.create_order(asset, amount, is_buy, style)
|
order = self.create_order(asset, amount, is_buy, style)
|
||||||
|
if order:
|
||||||
|
self._portfolio.create_order(order)
|
||||||
|
return order.id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
# The methods below must be implemented for each exchange.
|
# The methods below must be implemented for each exchange.
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import pickle
|
import pickle
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from collections import deque
|
||||||
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
|
||||||
@@ -20,32 +21,34 @@ from time import sleep
|
|||||||
|
|
||||||
import logbook
|
import logbook
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
|
|
||||||
import catalyst.protocol as zp
|
import catalyst.protocol as zp
|
||||||
from catalyst.algorithm import TradingAlgorithm
|
from catalyst.algorithm import TradingAlgorithm
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
from catalyst.errors import OrderInBeforeTradingStart
|
||||||
from catalyst.exchange.exchange_blotter import ExchangeBlotter
|
from catalyst.exchange.exchange_blotter import ExchangeBlotter
|
||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
ExchangePortfolioDataError,
|
ExchangePortfolioDataError,
|
||||||
OrderTypeNotSupported, )
|
ExchangeTransactionError,
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
OrphanOrderError)
|
||||||
from catalyst.exchange.exchange_utils import (
|
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
||||||
save_algo_object,
|
ExchangeLimitOrder, ExchangeStopOrder
|
||||||
get_algo_object,
|
from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, \
|
||||||
get_algo_folder,
|
get_algo_folder, get_algo_df, \
|
||||||
get_algo_df,
|
save_algo_df
|
||||||
save_algo_df,
|
|
||||||
group_assets_by_exchange, )
|
|
||||||
from catalyst.exchange.live_graph_clock import LiveGraphClock
|
from catalyst.exchange.live_graph_clock import LiveGraphClock
|
||||||
from catalyst.exchange.simple_clock import SimpleClock
|
from catalyst.exchange.simple_clock import SimpleClock
|
||||||
from catalyst.exchange.stats_utils import get_pretty_stats, stats_to_s3, \
|
from catalyst.exchange.stats_utils import get_pretty_stats
|
||||||
stats_to_algo_folder
|
|
||||||
from catalyst.finance.execution import MarketOrder
|
from catalyst.finance.execution import MarketOrder
|
||||||
from catalyst.finance.performance.period import calc_period_stats
|
from catalyst.finance.performance.period import calc_period_stats
|
||||||
from catalyst.gens.tradesimulation import AlgorithmSimulator
|
from catalyst.gens.tradesimulation import AlgorithmSimulator
|
||||||
from catalyst.utils.api_support import api_method
|
from catalyst.utils.api_support import (
|
||||||
from catalyst.utils.input_validation import error_keywords, ensure_upper_case
|
api_method,
|
||||||
|
disallowed_in_before_trading_start)
|
||||||
|
from catalyst.utils.input_validation import error_keywords, ensure_upper_case, \
|
||||||
|
expect_types
|
||||||
from catalyst.utils.math_utils import round_nearest
|
from catalyst.utils.math_utils import round_nearest
|
||||||
from catalyst.utils.preprocess import preprocess
|
from catalyst.utils.preprocess import preprocess
|
||||||
|
|
||||||
@@ -60,90 +63,9 @@ class ExchangeAlgorithmExecutor(AlgorithmSimulator):
|
|||||||
class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.exchanges = kwargs.pop('exchanges', None)
|
self.exchanges = kwargs.pop('exchanges', None)
|
||||||
self.simulate_orders = kwargs.pop('simulate_orders', None)
|
|
||||||
|
|
||||||
super(ExchangeTradingAlgorithmBase, self).__init__(*args, **kwargs)
|
super(ExchangeTradingAlgorithmBase, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.current_day = None
|
|
||||||
|
|
||||||
if self.simulate_orders is None \
|
|
||||||
and self.sim_params.arena == 'backtest':
|
|
||||||
self.simulate_orders = True
|
|
||||||
|
|
||||||
self.blotter = ExchangeBlotter(
|
|
||||||
data_frequency=self.data_frequency,
|
|
||||||
# Default to NeverCancel in catalyst
|
|
||||||
cancel_policy=self.cancel_policy,
|
|
||||||
simulate_orders=self.simulate_orders,
|
|
||||||
exchanges=self.exchanges
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __convert_order_params_for_blotter(limit_price, stop_price, style):
|
|
||||||
"""
|
|
||||||
Helper method for converting deprecated limit_price and stop_price
|
|
||||||
arguments into ExecutionStyle instances.
|
|
||||||
|
|
||||||
This function assumes that either style == None or (limit_price,
|
|
||||||
stop_price) == (None, None).
|
|
||||||
"""
|
|
||||||
if stop_price:
|
|
||||||
raise OrderTypeNotSupported(order_type='stop')
|
|
||||||
|
|
||||||
if style:
|
|
||||||
if limit_price is not None:
|
|
||||||
raise ValueError(
|
|
||||||
'An order style and a limit price was included in the '
|
|
||||||
'order. Please pick one to avoid any possible conflict.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Currently limiting order types or limit and market to
|
|
||||||
# be in-line with CXXT and many exchanges. We'll consider
|
|
||||||
# adding more order types in the future.
|
|
||||||
if not isinstance(style, ExchangeLimitOrder) or \
|
|
||||||
not isinstance(style, MarketOrder):
|
|
||||||
raise OrderTypeNotSupported(
|
|
||||||
order_type=style.__class__.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
return style
|
|
||||||
|
|
||||||
if limit_price:
|
|
||||||
return ExchangeLimitOrder(limit_price)
|
|
||||||
else:
|
|
||||||
return MarketOrder()
|
|
||||||
|
|
||||||
@api_method
|
|
||||||
def set_commission(self, maker=None, taker=None):
|
|
||||||
key = self.blotter.commission_models.keys()[0]
|
|
||||||
if maker is not None:
|
|
||||||
self.blotter.commission_models[key].maker = maker
|
|
||||||
|
|
||||||
if taker is not None:
|
|
||||||
self.blotter.commission_models[key].taker = taker
|
|
||||||
|
|
||||||
@api_method
|
|
||||||
def set_slippage(self, spread=None):
|
|
||||||
key = self.blotter.slippage_models.keys()[0]
|
|
||||||
if spread is not None:
|
|
||||||
self.blotter.slippage_models[key].spread = spread
|
|
||||||
|
|
||||||
def _calculate_order(self, asset, amount,
|
|
||||||
limit_price=None, stop_price=None, style=None):
|
|
||||||
# Raises a ZiplineError if invalid parameters are detected.
|
|
||||||
self.validate_order_params(asset,
|
|
||||||
amount,
|
|
||||||
limit_price,
|
|
||||||
stop_price,
|
|
||||||
style)
|
|
||||||
|
|
||||||
# Convert deprecated limit_price and stop_price parameters to use
|
|
||||||
# ExecutionStyle objects.
|
|
||||||
style = self.__convert_order_params_for_blotter(limit_price,
|
|
||||||
stop_price,
|
|
||||||
style)
|
|
||||||
return amount, style
|
|
||||||
|
|
||||||
def round_order(self, amount, asset):
|
def round_order(self, amount, asset):
|
||||||
"""
|
"""
|
||||||
We need fractions with cryptocurrencies
|
We need fractions with cryptocurrencies
|
||||||
@@ -282,8 +204,50 @@ class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase):
|
|||||||
super(ExchangeTradingAlgorithmBacktest, self).__init__(*args, **kwargs)
|
super(ExchangeTradingAlgorithmBacktest, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.frame_stats = list()
|
self.frame_stats = list()
|
||||||
|
self.blotter = ExchangeBlotter(
|
||||||
|
data_frequency=self.data_frequency,
|
||||||
|
# Default to NeverCancel in catalyst
|
||||||
|
cancel_policy=self.cancel_policy,
|
||||||
|
)
|
||||||
log.info('initialized trading algorithm in backtest mode')
|
log.info('initialized trading algorithm in backtest mode')
|
||||||
|
|
||||||
|
def _calculate_order(self, asset, amount,
|
||||||
|
limit_price=None, stop_price=None, style=None):
|
||||||
|
# Raises a ZiplineError if invalid parameters are detected.
|
||||||
|
self.validate_order_params(asset,
|
||||||
|
amount,
|
||||||
|
limit_price,
|
||||||
|
stop_price,
|
||||||
|
style)
|
||||||
|
|
||||||
|
# Convert deprecated limit_price and stop_price parameters to use
|
||||||
|
# ExecutionStyle objects.
|
||||||
|
style = self.__convert_order_params_for_blotter(limit_price,
|
||||||
|
stop_price,
|
||||||
|
style)
|
||||||
|
return amount, style
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __convert_order_params_for_blotter(limit_price, stop_price, style):
|
||||||
|
"""
|
||||||
|
Helper method for converting deprecated limit_price and stop_price
|
||||||
|
arguments into ExecutionStyle instances.
|
||||||
|
|
||||||
|
This function assumes that either style == None or (limit_price,
|
||||||
|
stop_price) == (None, None).
|
||||||
|
"""
|
||||||
|
if style:
|
||||||
|
assert (limit_price, stop_price) == (None, None)
|
||||||
|
return style
|
||||||
|
if limit_price and stop_price:
|
||||||
|
return ExchangeStopLimitOrder(limit_price, stop_price)
|
||||||
|
if limit_price:
|
||||||
|
return ExchangeLimitOrder(limit_price)
|
||||||
|
if stop_price:
|
||||||
|
return ExchangeStopOrder(stop_price)
|
||||||
|
else:
|
||||||
|
return MarketOrder()
|
||||||
|
|
||||||
def is_last_frame_of_day(self, data):
|
def is_last_frame_of_day(self, data):
|
||||||
# TODO: adjust here to support more intervals
|
# TODO: adjust here to support more intervals
|
||||||
next_frame_dt = data.current_dt + timedelta(minutes=1)
|
next_frame_dt = data.current_dt + timedelta(minutes=1)
|
||||||
@@ -301,8 +265,6 @@ class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
self.frame_stats.append(frame_stats)
|
self.frame_stats.append(frame_stats)
|
||||||
|
|
||||||
self.current_day = data.current_dt.floor('1D')
|
|
||||||
|
|
||||||
def _create_stats_df(self):
|
def _create_stats_df(self):
|
||||||
stats = pd.DataFrame(self.frame_stats)
|
stats = pd.DataFrame(self.frame_stats)
|
||||||
stats.set_index('period_close', inplace=True, drop=False)
|
stats.set_index('period_close', inplace=True, drop=False)
|
||||||
@@ -327,10 +289,10 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.algo_namespace = kwargs.pop('algo_namespace', None)
|
self.algo_namespace = kwargs.pop('algo_namespace', None)
|
||||||
self.live_graph = kwargs.pop('live_graph', None)
|
self.live_graph = kwargs.pop('live_graph', None)
|
||||||
self.stats_output = kwargs.pop('stats_output', None)
|
self.simulate_orders = kwargs.pop('simulate_orders', None)
|
||||||
|
|
||||||
self._clock = None
|
self._clock = None
|
||||||
self.frame_stats = list()
|
self.frame_stats = deque(maxlen=60)
|
||||||
|
|
||||||
self.pnl_stats = get_algo_df(self.algo_namespace, 'pnl_stats')
|
self.pnl_stats = get_algo_df(self.algo_namespace, 'pnl_stats')
|
||||||
|
|
||||||
@@ -348,7 +310,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.retry_order = 2
|
self.retry_order = 2
|
||||||
self.retry_delay = 5
|
self.retry_delay = 5
|
||||||
|
|
||||||
self.stats_minutes = 10
|
self.stats_minutes = 5
|
||||||
|
|
||||||
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
@@ -416,7 +378,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
|
|
||||||
# This method is taken from TradingAlgorithm.
|
# This method is taken from TradingAlgorithm.
|
||||||
# The clock has been replaced to use RealtimeClock
|
# The clock has been replaced to use RealtimeClock
|
||||||
# TODO: should we apply time skew? not sure to understand the utility.
|
# TODO: should we apply a time skew? not sure to understand the utility.
|
||||||
|
|
||||||
log.debug('creating clock')
|
log.debug('creating clock')
|
||||||
if self.live_graph:
|
if self.live_graph:
|
||||||
@@ -454,83 +416,47 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
return self.trading_client.transform()
|
return self.trading_client.transform()
|
||||||
|
|
||||||
def updated_portfolio(self):
|
def updated_portfolio(self):
|
||||||
|
"""
|
||||||
|
We skip the entire performance tracker business and update the
|
||||||
|
portfolio directly.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ExchangePortfolio
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: build cumulative portfolio
|
||||||
return self.perf_tracker.get_portfolio(False)
|
return self.perf_tracker.get_portfolio(False)
|
||||||
|
|
||||||
def updated_account(self):
|
def updated_account(self):
|
||||||
return self.perf_tracker.get_account(False)
|
return self.perf_tracker.get_account(False)
|
||||||
|
|
||||||
def synchronize_portfolio(self, attempt_index=0):
|
def _synchronize_portfolio(self, attempt_index=0):
|
||||||
"""
|
|
||||||
Synchronizes the portfolio tracked by the algorithm to refresh
|
|
||||||
its current value.
|
|
||||||
|
|
||||||
This includes updating the last_sale_price of all tracked
|
|
||||||
positions, returning the available cash, and raising error
|
|
||||||
if the data goes out of sync.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
attempt_index: int
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float
|
|
||||||
The amount of base currency available for trading.
|
|
||||||
|
|
||||||
float
|
|
||||||
The total value of all tracked positions.
|
|
||||||
|
|
||||||
"""
|
|
||||||
tracker = self.perf_tracker.position_tracker
|
|
||||||
total_cash = 0.0
|
|
||||||
total_positions_value = 0.0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Position keys correspond to assets
|
|
||||||
positions = self.portfolio.positions
|
|
||||||
assets = list(positions)
|
|
||||||
exchange_assets = group_assets_by_exchange(assets)
|
|
||||||
for exchange_name in self.exchanges:
|
for exchange_name in self.exchanges:
|
||||||
assets = exchange_assets[exchange_name] \
|
exchange = self.exchanges[exchange_name]
|
||||||
if exchange_name in exchange_assets else []
|
|
||||||
|
|
||||||
exchange_positions = \
|
exchange.synchronize_portfolio()
|
||||||
[positions[asset] for asset in assets]
|
|
||||||
|
|
||||||
check_cash = (not self.simulate_orders)
|
# Applying the updated last_sales_price to the positions
|
||||||
|
# in the performance tracker. This seems a bit redundant
|
||||||
exchange = self.exchanges[exchange_name] # Type: Exchange
|
# but it will make sense when we have multiple exchange portfolios
|
||||||
cash, positions_value = exchange.calculate_totals(
|
# feeding into the same performance tracker.
|
||||||
positions=exchange_positions,
|
tracker = self.perf_tracker.todays_performance.position_tracker
|
||||||
check_cash=check_cash,
|
for asset in exchange.portfolio.positions:
|
||||||
)
|
position = exchange.portfolio.positions[asset]
|
||||||
total_positions_value += positions_value
|
|
||||||
|
|
||||||
if cash is not None:
|
|
||||||
total_cash += cash
|
|
||||||
|
|
||||||
for position in exchange_positions:
|
|
||||||
tracker.update_position(
|
tracker.update_position(
|
||||||
asset=position.asset,
|
asset=asset,
|
||||||
last_sale_date=position.last_sale_date,
|
last_sale_date=position.last_sale_date,
|
||||||
last_sale_price=position.last_sale_price
|
last_sale_price=position.last_sale_price
|
||||||
)
|
)
|
||||||
|
|
||||||
if cash is None:
|
|
||||||
total_cash = self.portfolio.cash
|
|
||||||
|
|
||||||
elif total_cash < self.portfolio.cash:
|
|
||||||
raise ValueError('Cash on exchanges is lower than the algo.')
|
|
||||||
|
|
||||||
return total_cash, total_positions_value
|
|
||||||
|
|
||||||
except ExchangeRequestError as e:
|
except ExchangeRequestError as e:
|
||||||
log.warn(
|
log.warn(
|
||||||
'update portfolio attempt {}: {}'.format(attempt_index, e)
|
'update portfolio attempt {}: {}'.format(attempt_index, e)
|
||||||
)
|
)
|
||||||
if attempt_index < self.retry_synchronize_portfolio:
|
if attempt_index < self.retry_synchronize_portfolio:
|
||||||
sleep(self.retry_delay)
|
sleep(self.retry_delay)
|
||||||
return self.synchronize_portfolio(attempt_index + 1)
|
self._synchronize_portfolio(attempt_index + 1)
|
||||||
else:
|
else:
|
||||||
raise ExchangePortfolioDataError(
|
raise ExchangePortfolioDataError(
|
||||||
data_type='update-portfolio',
|
data_type='update-portfolio',
|
||||||
@@ -538,6 +464,30 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
error=e
|
error=e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _check_open_orders(self, attempt_index=0):
|
||||||
|
try:
|
||||||
|
orders = list()
|
||||||
|
for exchange_name in self.exchanges:
|
||||||
|
exchange = self.exchanges[exchange_name]
|
||||||
|
exchange_orders = exchange.check_open_orders()
|
||||||
|
|
||||||
|
orders += exchange_orders
|
||||||
|
|
||||||
|
return orders
|
||||||
|
except ExchangeRequestError as e:
|
||||||
|
log.warn(
|
||||||
|
'check open orders attempt {}: {}'.format(attempt_index, e)
|
||||||
|
)
|
||||||
|
if attempt_index < self.retry_check_open_orders:
|
||||||
|
sleep(self.retry_delay)
|
||||||
|
return self._check_open_orders(attempt_index + 1)
|
||||||
|
else:
|
||||||
|
raise ExchangePortfolioDataError(
|
||||||
|
data_type='order-status',
|
||||||
|
attempts=attempt_index,
|
||||||
|
error=e
|
||||||
|
)
|
||||||
|
|
||||||
def add_pnl_stats(self, period_stats):
|
def add_pnl_stats(self, period_stats):
|
||||||
"""
|
"""
|
||||||
Save p&l stats.
|
Save p&l stats.
|
||||||
@@ -627,23 +577,15 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
if not self.is_running:
|
if not self.is_running:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Resetting the frame stats every day to minimize memory footprint
|
self._synchronize_portfolio()
|
||||||
today = data.current_dt.floor('1D')
|
|
||||||
if self.current_day is not None and today > self.current_day:
|
|
||||||
self.frame_stats = list()
|
|
||||||
|
|
||||||
new_transactions, new_commissions, closed_orders = \
|
transactions = self._check_open_orders()
|
||||||
self.blotter.get_transactions(data)
|
if len(transactions) > 0:
|
||||||
|
for transaction in transactions:
|
||||||
|
self.perf_tracker.process_transaction(transaction)
|
||||||
|
|
||||||
if len(new_transactions) > 0:
|
|
||||||
self.perf_tracker.update_performance()
|
self.perf_tracker.update_performance()
|
||||||
|
|
||||||
cash, positions_value = self.synchronize_portfolio()
|
|
||||||
log.info(
|
|
||||||
'got totals from exchanges, cash: {} positions: {}'.format(
|
|
||||||
cash, positions_value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self._handle_data:
|
if self._handle_data:
|
||||||
self._handle_data(self, data)
|
self._handle_data(self, data)
|
||||||
|
|
||||||
@@ -653,25 +595,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.validate_account_controls()
|
self.validate_account_controls()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._save_stats_csv(self._process_stats(data))
|
|
||||||
except Exception as e:
|
|
||||||
log.warn('unable to calculate performance: {}'.format(e))
|
|
||||||
|
|
||||||
# TODO: pickle does not seem to work in python 3
|
|
||||||
try:
|
|
||||||
save_algo_object(
|
|
||||||
algo_name=self.algo_namespace,
|
|
||||||
key='perf_tracker',
|
|
||||||
obj=self.perf_tracker
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
log.warn('unable to save minute perfs to disk: {}'.format(e))
|
|
||||||
|
|
||||||
self.current_day = data.current_dt.floor('1D')
|
|
||||||
|
|
||||||
def _process_stats(self, data):
|
|
||||||
today = data.current_dt.floor('1D')
|
|
||||||
|
|
||||||
# Since the clock runs 24/7, I trying to disable the daily
|
# Since the clock runs 24/7, I trying to disable the daily
|
||||||
# Performance tracker and keep only minute and cumulative
|
# Performance tracker and keep only minute and cumulative
|
||||||
self.perf_tracker.update_performance()
|
self.perf_tracker.update_performance()
|
||||||
@@ -686,28 +609,26 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
if self.recorded_vars:
|
if self.recorded_vars:
|
||||||
self.add_custom_signals_stats(frame_stats)
|
self.add_custom_signals_stats(frame_stats)
|
||||||
recorded_cols = list(self.recorded_vars.keys())
|
recorded_cols = list(self.recorded_vars.keys())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
recorded_cols = None
|
recorded_cols = None
|
||||||
|
|
||||||
self.add_exposure_stats(frame_stats)
|
self.add_exposure_stats(frame_stats)
|
||||||
|
|
||||||
|
print_df = pd.DataFrame(list(self.frame_stats))
|
||||||
log.info(
|
log.info(
|
||||||
'statistics for the last {stats_minutes} minutes:\n'
|
'statistics for the last {stats_minutes} minutes:\n{stats}'.format(
|
||||||
'{stats}'.format(
|
|
||||||
stats_minutes=self.stats_minutes,
|
stats_minutes=self.stats_minutes,
|
||||||
stats=get_pretty_stats(
|
stats=get_pretty_stats(
|
||||||
stats=self.frame_stats,
|
stats_df=print_df,
|
||||||
recorded_cols=recorded_cols,
|
recorded_cols=recorded_cols,
|
||||||
num_rows=self.stats_minutes
|
num_rows=self.stats_minutes
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
# Saving the daily stats in a format usable for performance
|
today = pd.to_datetime('today', utc=True)
|
||||||
# analysis.
|
|
||||||
daily_stats = self.prepare_period_stats(
|
daily_stats = self.prepare_period_stats(
|
||||||
start_dt=today,
|
start_dt=today,
|
||||||
end_dt=data.current_dt
|
end_dt=pd.Timestamp.utcnow()
|
||||||
)
|
)
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
@@ -716,36 +637,106 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
rel_path='daily_perf'
|
rel_path='daily_perf'
|
||||||
)
|
)
|
||||||
|
|
||||||
return recorded_cols
|
except Exception as e:
|
||||||
|
log.warn('unable to calculate performance: {}'.format(e))
|
||||||
|
|
||||||
def _save_stats_csv(self, recorded_cols):
|
# TODO: pickle does not seem to work in python 3
|
||||||
# Writing the stats output
|
|
||||||
csv_bytes = None
|
|
||||||
try:
|
try:
|
||||||
csv_bytes = stats_to_algo_folder(
|
save_algo_object(
|
||||||
stats=self.frame_stats,
|
algo_name=self.algo_namespace,
|
||||||
algo_namespace=self.algo_namespace,
|
key='perf_tracker',
|
||||||
recorded_cols=recorded_cols,
|
obj=self.perf_tracker
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn('unable save stats locally: {}'.format(e))
|
log.warn('unable to save minute perfs to disk: {}'.format(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.stats_output is not None:
|
for exchange_name in self.exchanges:
|
||||||
if 's3://' in self.stats_output:
|
exchange = self.exchanges[exchange_name]
|
||||||
stats_to_s3(
|
save_algo_object(
|
||||||
uri=self.stats_output,
|
algo_name=self.algo_namespace,
|
||||||
stats=self.frame_stats,
|
key='portfolio_{}'.format(exchange_name),
|
||||||
algo_namespace=self.algo_namespace,
|
obj=exchange.portfolio
|
||||||
recorded_cols=recorded_cols,
|
)
|
||||||
bytes_to_write=csv_bytes
|
except Exception as e:
|
||||||
|
log.warn('unable to save portfolio to disk: {}'.format(e))
|
||||||
|
|
||||||
|
def _order(self,
|
||||||
|
asset,
|
||||||
|
amount,
|
||||||
|
limit_price=None,
|
||||||
|
stop_price=None,
|
||||||
|
style=None,
|
||||||
|
attempt_index=0):
|
||||||
|
try:
|
||||||
|
exchange = self.exchanges[asset.exchange]
|
||||||
|
return exchange.order(asset, amount, limit_price,
|
||||||
|
stop_price,
|
||||||
|
style)
|
||||||
|
except ExchangeRequestError as e:
|
||||||
|
log.warn(
|
||||||
|
'order attempt {}: {}'.format(attempt_index, e)
|
||||||
|
)
|
||||||
|
if attempt_index < self.retry_order:
|
||||||
|
sleep(self.retry_delay)
|
||||||
|
return self._order(
|
||||||
|
asset, amount, limit_price, stop_price, style,
|
||||||
|
attempt_index + 1)
|
||||||
|
else:
|
||||||
|
raise ExchangeTransactionError(
|
||||||
|
transaction_type='order',
|
||||||
|
attempts=attempt_index,
|
||||||
|
error=e
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_method
|
||||||
|
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
|
||||||
|
@expect_types(asset=TradingPair)
|
||||||
|
def order(self,
|
||||||
|
asset,
|
||||||
|
amount,
|
||||||
|
limit_price=None,
|
||||||
|
stop_price=None,
|
||||||
|
style=None):
|
||||||
|
"""
|
||||||
|
We use the exchange specific portfolio to place orders.
|
||||||
|
The cumulative portfolio does not contain open orders but exchange
|
||||||
|
portfolios do.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
asset: TradingPair
|
||||||
|
amount: float
|
||||||
|
limit_price: float
|
||||||
|
stop_price: float
|
||||||
|
style: Style
|
||||||
|
order: Order
|
||||||
|
The catalyst order object or None
|
||||||
|
"""
|
||||||
|
amount, style = self._calculate_order(asset, amount,
|
||||||
|
limit_price, stop_price,
|
||||||
|
style)
|
||||||
|
|
||||||
|
order_id = self._order(asset, amount, limit_price, stop_price, style)
|
||||||
|
|
||||||
|
exchange = self.exchanges[asset.exchange]
|
||||||
|
exchange_portfolio = exchange.portfolio
|
||||||
|
if order_id is not None:
|
||||||
|
|
||||||
|
if order_id in exchange_portfolio.open_orders:
|
||||||
|
order = exchange_portfolio.open_orders[order_id]
|
||||||
|
self.perf_tracker.process_order(order)
|
||||||
|
return order
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise OrphanOrderError(
|
||||||
|
order_id=order_id,
|
||||||
|
exchange=exchange.name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
log.warn('unable to order {} {} on exchange {}'.format(
|
||||||
'Only S3 stats output is supported for now.'
|
amount, asset.symbol, asset.exchange))
|
||||||
)
|
return None
|
||||||
except Exception as e:
|
|
||||||
log.warn('unable save stats externally: {}'.format(e))
|
|
||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
def batch_market_order(self, share_counts):
|
def batch_market_order(self, share_counts):
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
from time import sleep
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from catalyst.assets._assets import TradingPair
|
from catalyst.assets._assets import TradingPair
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.exchange_errors import ExchangeRequestError, \
|
|
||||||
ExchangePortfolioDataError, ExchangeTransactionError
|
|
||||||
from catalyst.finance.blotter import Blotter
|
from catalyst.finance.blotter import Blotter
|
||||||
from catalyst.finance.commission import CommissionModel
|
from catalyst.finance.commission import CommissionModel
|
||||||
from catalyst.finance.order import ORDER_STATUS, Order
|
|
||||||
from catalyst.finance.slippage import SlippageModel
|
from catalyst.finance.slippage import SlippageModel
|
||||||
from catalyst.finance.transaction import create_transaction, Transaction
|
from catalyst.finance.transaction import create_transaction
|
||||||
from catalyst.utils.input_validation import expect_types
|
|
||||||
|
|
||||||
log = Logger('exchange_blotter', level=LOG_LEVEL)
|
log = Logger('exchange_blotter', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
# It seems like we need to accept greater slippage risk in cryptos
|
||||||
|
# Orders won't often close at Equity levels.
|
||||||
|
# TODO: should work with set_commission and set_slippage
|
||||||
|
DEFAULT_SLIPPAGE_SPREAD = 0.0001
|
||||||
|
DEFAULT_MAKER_FEE = 0.0015
|
||||||
|
DEFAULT_TAKER_FEE = 0.0025
|
||||||
|
|
||||||
|
|
||||||
class TradingPairFeeSchedule(CommissionModel):
|
class TradingPairFeeSchedule(CommissionModel):
|
||||||
"""
|
"""
|
||||||
@@ -23,24 +23,23 @@ class TradingPairFeeSchedule(CommissionModel):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
maker : float, optional
|
fee : float, optional
|
||||||
The percentage maker fee.
|
The percentage fee.
|
||||||
|
|
||||||
taker: float, optional
|
|
||||||
The percentage taker fee.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, maker=None, taker=None):
|
def __init__(self,
|
||||||
self.maker = maker
|
maker_fee=DEFAULT_MAKER_FEE,
|
||||||
self.taker = taker
|
taker_fee=DEFAULT_TAKER_FEE):
|
||||||
|
self.maker_fee = maker_fee
|
||||||
|
self.taker_fee = taker_fee
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
'{class_name}(maker={maker}, '
|
'{class_name}(maker_fee={maker_fee}, '
|
||||||
'taker={taker})'.format(
|
'taker_fee={taker_fee})'.format(
|
||||||
class_name=self.__class__.__name__,
|
class_name=self.__class__.__name__,
|
||||||
maker=self.maker,
|
maker_fee=self.maker_fee,
|
||||||
taker=self.taker,
|
taker_fee=self.taker_fee,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,25 +47,16 @@ class TradingPairFeeSchedule(CommissionModel):
|
|||||||
"""
|
"""
|
||||||
Calculate the final fee based on the order parameters.
|
Calculate the final fee based on the order parameters.
|
||||||
|
|
||||||
:param order: Order
|
:param order:
|
||||||
:param transaction: Transaction
|
:param transaction:
|
||||||
|
|
||||||
:return float:
|
:return float:
|
||||||
The total commission.
|
The total commission.
|
||||||
"""
|
"""
|
||||||
cost = abs(transaction.amount) * transaction.price
|
cost = abs(transaction.amount) * transaction.price
|
||||||
|
|
||||||
asset = order.asset
|
|
||||||
maker = self.maker if self.maker is not None else asset.maker
|
|
||||||
taker = self.taker if self.taker is not None else asset.taker
|
|
||||||
|
|
||||||
multiplier = maker \
|
|
||||||
if ((order.amount > 0 and order.limit < transaction.price)
|
|
||||||
or (order.amount < 0 and order.limit > transaction.price)) \
|
|
||||||
and order.limit_reached else taker
|
|
||||||
|
|
||||||
# Assuming just the taker fee for now
|
# Assuming just the taker fee for now
|
||||||
fee = cost * multiplier
|
fee = cost * self.taker_fee
|
||||||
return fee
|
return fee
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +70,7 @@ class TradingPairFixedSlippage(SlippageModel):
|
|||||||
spread / 2 will be added to buys and subtracted from sells.
|
spread / 2 will be added to buys and subtracted from sells.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, spread=0.0001):
|
def __init__(self, spread=DEFAULT_SLIPPAGE_SPREAD):
|
||||||
super(TradingPairFixedSlippage, self).__init__()
|
super(TradingPairFixedSlippage, self).__init__()
|
||||||
self.spread = spread
|
self.spread = spread
|
||||||
|
|
||||||
@@ -131,14 +121,6 @@ class TradingPairFixedSlippage(SlippageModel):
|
|||||||
|
|
||||||
class ExchangeBlotter(Blotter):
|
class ExchangeBlotter(Blotter):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.simulate_orders = kwargs.pop('simulate_orders', False)
|
|
||||||
|
|
||||||
self.exchanges = kwargs.pop('exchanges', None)
|
|
||||||
if not self.exchanges:
|
|
||||||
raise ValueError(
|
|
||||||
'ExchangeBlotter must have an `exchanges` attribute.'
|
|
||||||
)
|
|
||||||
|
|
||||||
super(ExchangeBlotter, self).__init__(*args, **kwargs)
|
super(ExchangeBlotter, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Using the equity models for now
|
# Using the equity models for now
|
||||||
@@ -150,148 +132,3 @@ class ExchangeBlotter(Blotter):
|
|||||||
self.commission_models = {
|
self.commission_models = {
|
||||||
TradingPair: TradingPairFeeSchedule()
|
TradingPair: TradingPairFeeSchedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.retry_delay = 5
|
|
||||||
self.retry_check_open_orders = 5
|
|
||||||
|
|
||||||
def exchange_order(self, asset, amount, style=None, attempt_index=0):
|
|
||||||
try:
|
|
||||||
exchange = self.exchanges[asset.exchange]
|
|
||||||
return exchange.order(
|
|
||||||
asset, amount, style
|
|
||||||
)
|
|
||||||
except ExchangeRequestError as e:
|
|
||||||
log.warn(
|
|
||||||
'order attempt {}: {}'.format(attempt_index, e)
|
|
||||||
)
|
|
||||||
if attempt_index < self.retry_order:
|
|
||||||
sleep(self.retry_delay)
|
|
||||||
|
|
||||||
return self.exchange_order(
|
|
||||||
asset, amount, style, attempt_index + 1
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ExchangeTransactionError(
|
|
||||||
transaction_type='order',
|
|
||||||
attempts=attempt_index,
|
|
||||||
error=e
|
|
||||||
)
|
|
||||||
|
|
||||||
@expect_types(asset=TradingPair)
|
|
||||||
def order(self, asset, amount, style, order_id=None):
|
|
||||||
log.debug('ordering {} {}'.format(amount, asset.symbol))
|
|
||||||
if amount == 0:
|
|
||||||
log.warn('skipping 0 amount orders')
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.simulate_orders:
|
|
||||||
return super(ExchangeBlotter, self).order(
|
|
||||||
asset, amount, style, order_id
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
order = self.exchange_order(
|
|
||||||
asset, amount, style
|
|
||||||
)
|
|
||||||
|
|
||||||
self.open_orders[order.asset].append(order)
|
|
||||||
self.orders[order.id] = order
|
|
||||||
self.new_orders.append(order)
|
|
||||||
|
|
||||||
return order.id
|
|
||||||
|
|
||||||
def check_open_orders(self):
|
|
||||||
"""
|
|
||||||
Loop through the list of open orders in the Portfolio object.
|
|
||||||
For each executed order found, create a transaction and apply to the
|
|
||||||
Portfolio.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
list[Transaction]
|
|
||||||
|
|
||||||
"""
|
|
||||||
for asset in self.open_orders:
|
|
||||||
exchange = self.exchanges[asset.exchange]
|
|
||||||
|
|
||||||
for order in self.open_orders[asset]:
|
|
||||||
log.debug('found open order: {}'.format(order.id))
|
|
||||||
|
|
||||||
new_order, executed_price = exchange.get_order(order.id, asset)
|
|
||||||
log.debug(
|
|
||||||
'got updated order {} {}'.format(
|
|
||||||
new_order, executed_price
|
|
||||||
)
|
|
||||||
)
|
|
||||||
order.status = new_order.status
|
|
||||||
|
|
||||||
if order.status == ORDER_STATUS.FILLED:
|
|
||||||
order.commission = new_order.commission
|
|
||||||
if order.amount != new_order.amount:
|
|
||||||
log.warn(
|
|
||||||
'executed order amount {} differs '
|
|
||||||
'from original'.format(
|
|
||||||
new_order.amount, order.amount
|
|
||||||
)
|
|
||||||
)
|
|
||||||
order.amount = new_order.amount
|
|
||||||
|
|
||||||
transaction = Transaction(
|
|
||||||
asset=order.asset,
|
|
||||||
amount=order.amount,
|
|
||||||
dt=pd.Timestamp.utcnow(),
|
|
||||||
price=executed_price,
|
|
||||||
order_id=order.id,
|
|
||||||
commission=order.commission
|
|
||||||
)
|
|
||||||
yield order, transaction
|
|
||||||
|
|
||||||
elif order.status == ORDER_STATUS.CANCELLED:
|
|
||||||
yield order, None
|
|
||||||
|
|
||||||
else:
|
|
||||||
delta = pd.Timestamp.utcnow() - order.dt
|
|
||||||
log.info(
|
|
||||||
'order {order_id} still open after {delta}'.format(
|
|
||||||
order_id=order.id,
|
|
||||||
delta=delta
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_exchange_transactions(self, attempt_index=0):
|
|
||||||
closed_orders = []
|
|
||||||
transactions = []
|
|
||||||
commissions = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
for order, txn in self.check_open_orders():
|
|
||||||
order.dt = txn.dt
|
|
||||||
|
|
||||||
transactions.append(txn)
|
|
||||||
|
|
||||||
if not order.open:
|
|
||||||
closed_orders.append(order)
|
|
||||||
|
|
||||||
return transactions, commissions, closed_orders
|
|
||||||
|
|
||||||
except ExchangeRequestError as e:
|
|
||||||
log.warn(
|
|
||||||
'check open orders attempt {}: {}'.format(attempt_index, e)
|
|
||||||
)
|
|
||||||
if attempt_index < self.retry_check_open_orders:
|
|
||||||
sleep(self.retry_delay)
|
|
||||||
return self.get_exchange_transactions(attempt_index + 1)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ExchangePortfolioDataError(
|
|
||||||
data_type='order-status',
|
|
||||||
attempts=attempt_index,
|
|
||||||
error=e
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_transactions(self, bar_data):
|
|
||||||
if self.simulate_orders:
|
|
||||||
return super(ExchangeBlotter, self).get_transactions(bar_data)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self.get_exchange_transactions()
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from operator import is_not
|
from operator import is_not
|
||||||
@@ -234,12 +234,10 @@ 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)
|
||||||
|
|
||||||
@@ -247,7 +245,8 @@ class ExchangeBundle:
|
|||||||
raise EmptyValuesInBundleError(
|
raise EmptyValuesInBundleError(
|
||||||
name=asset.symbol,
|
name=asset.symbol,
|
||||||
end_minute=end_dt,
|
end_minute=end_dt,
|
||||||
dates=dates, )
|
dates=dates
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ohlcv_df.dropna(inplace=True)
|
ohlcv_df.dropna(inplace=True)
|
||||||
@@ -292,7 +291,8 @@ class ExchangeBundle:
|
|||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ from catalyst.exchange.exchange_errors import (
|
|||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
ExchangeBarDataError,
|
ExchangeBarDataError,
|
||||||
PricingDataNotLoadedError)
|
PricingDataNotLoadedError)
|
||||||
from catalyst.exchange.exchange_utils import get_frequency, \
|
from catalyst.exchange.exchange_utils import get_frequency, resample_history_df
|
||||||
resample_history_df, group_assets_by_exchange
|
|
||||||
|
|
||||||
log = Logger('DataPortalExchange', level=LOG_LEVEL)
|
log = Logger('DataPortalExchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -39,7 +38,13 @@ class DataPortalExchangeBase(DataPortal):
|
|||||||
ffill=True,
|
ffill=True,
|
||||||
attempt_index=0):
|
attempt_index=0):
|
||||||
try:
|
try:
|
||||||
exchange_assets = group_assets_by_exchange(assets)
|
exchange_assets = dict()
|
||||||
|
for asset in assets:
|
||||||
|
if asset.exchange not in exchange_assets:
|
||||||
|
exchange_assets[asset.exchange] = list()
|
||||||
|
|
||||||
|
exchange_assets[asset.exchange].append(asset)
|
||||||
|
|
||||||
if len(exchange_assets) > 1:
|
if len(exchange_assets) > 1:
|
||||||
df_list = []
|
df_list = []
|
||||||
for exchange_name in exchange_assets:
|
for exchange_name in exchange_assets:
|
||||||
@@ -237,7 +242,6 @@ class DataPortalExchangeLive(DataPortalExchangeBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
exchange = self.exchanges[exchange_name]
|
exchange = self.exchanges[exchange_name]
|
||||||
|
|
||||||
df = exchange.get_history_window(
|
df = exchange.get_history_window(
|
||||||
assets,
|
assets,
|
||||||
end_dt,
|
end_dt,
|
||||||
@@ -245,7 +249,7 @@ class DataPortalExchangeLive(DataPortalExchangeBase):
|
|||||||
frequency,
|
frequency,
|
||||||
field,
|
field,
|
||||||
data_frequency,
|
data_frequency,
|
||||||
False)
|
ffill)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def get_exchange_spot_value(self, exchange_name, assets, field, dt,
|
def get_exchange_spot_value(self, exchange_name, assets, field, dt,
|
||||||
|
|||||||
@@ -143,8 +143,7 @@ class OrphanOrderError(ZiplineError):
|
|||||||
|
|
||||||
class OrphanOrderReverseError(ZiplineError):
|
class OrphanOrderReverseError(ZiplineError):
|
||||||
msg = (
|
msg = (
|
||||||
'Order {order_id} tracked by algorithm, but not found in exchange '
|
'Order {order_id} tracked by algorithm, but not found in exchange {exchange}.'
|
||||||
'{exchange}.'
|
|
||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
@@ -207,9 +206,8 @@ class EmptyValuesInBundleError(ZiplineError):
|
|||||||
|
|
||||||
class PricingDataBeforeTradingError(ZiplineError):
|
class PricingDataBeforeTradingError(ZiplineError):
|
||||||
msg = ('Pricing data for trading pairs {symbols} on exchange {exchange} '
|
msg = ('Pricing data for trading pairs {symbols} on exchange {exchange} '
|
||||||
'starts on {first_trading_day}, but you are either trying to trade '
|
'starts on {first_trading_day}, but you are either trying to trade or '
|
||||||
'or retrieve pricing data on {dt}. Adjust your dates accordingly.'
|
'retrieve pricing data on {dt}. Adjust your dates accordingly.').strip()
|
||||||
).strip()
|
|
||||||
|
|
||||||
|
|
||||||
class PricingDataNotLoadedError(ZiplineError):
|
class PricingDataNotLoadedError(ZiplineError):
|
||||||
@@ -219,7 +217,6 @@ class PricingDataNotLoadedError(ZiplineError):
|
|||||||
'{data_frequency} -i {symbol_list}`. See catalyst documentation '
|
'{data_frequency} -i {symbol_list}`. See catalyst documentation '
|
||||||
'for details.').strip()
|
'for details.').strip()
|
||||||
|
|
||||||
|
|
||||||
class PricingDataValueError(ZiplineError):
|
class PricingDataValueError(ZiplineError):
|
||||||
msg = ('Unable to retrieve pricing data for {exchange} {symbol} '
|
msg = ('Unable to retrieve pricing data for {exchange} {symbol} '
|
||||||
'[{start_dt} - {end_dt}]: {error}').strip()
|
'[{start_dt} - {end_dt}]: {error}').strip()
|
||||||
@@ -240,32 +237,10 @@ class ApiCandlesError(ZiplineError):
|
|||||||
|
|
||||||
class NoDataAvailableOnExchange(ZiplineError):
|
class NoDataAvailableOnExchange(ZiplineError):
|
||||||
msg = (
|
msg = (
|
||||||
'Requested data for trading pair {symbol} is not available on '
|
'Requested data for trading pair {symbol} is not available on exchange {exchange} '
|
||||||
'exchange {exchange} '
|
|
||||||
'in `{data_frequency}` frequency at this time. '
|
'in `{data_frequency}` frequency at this time. '
|
||||||
'Check `http://enigma.co/catalyst/status` for market coverage.'
|
'Check `http://enigma.co/catalyst/status` for market coverage.').strip()
|
||||||
).strip()
|
|
||||||
|
|
||||||
|
|
||||||
class NoValueForField(ZiplineError):
|
class NoValueForField(ZiplineError):
|
||||||
msg = ('Value not found for field: {field}.').strip()
|
msg = ('Value not found for field: {field}.').strip()
|
||||||
|
|
||||||
|
|
||||||
class OrderTypeNotSupported(ZiplineError):
|
|
||||||
msg = (
|
|
||||||
'Order type `{order_type}` not currencly supported by Catalyst. '
|
|
||||||
'Please use `limit` or `market` orders only.').strip()
|
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughCapitalError(ZiplineError):
|
|
||||||
msg = (
|
|
||||||
'Not enough capital on exchange {exchange} for trading. Each '
|
|
||||||
'exchange should contain at least as much {base_currency} '
|
|
||||||
'as the specified `capital_base`. The current balance {balance} is '
|
|
||||||
'lower than the `capital_base`: {capital_base}').strip()
|
|
||||||
|
|
||||||
class LastCandleTooEarlyError(ZiplineError):
|
|
||||||
msg = (
|
|
||||||
'The trade date of the last candle {last_traded} is before the '
|
|
||||||
'specified end date minus one candle {end_dt}. Please verify how '
|
|
||||||
'{exchange} calculates the start date of OHLCV candles.').strip()
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder
|
from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder, MarketOrder
|
||||||
|
|
||||||
|
|
||||||
class ExchangeLimitOrder(LimitOrder):
|
class ExchangeLimitOrder(LimitOrder):
|
||||||
|
|||||||
@@ -40,13 +40,7 @@ class ExchangePortfolio(Portfolio):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug('creating order {}'.format(order.id))
|
log.debug('creating order {}'.format(order.id))
|
||||||
|
self.open_orders[order.id] = order
|
||||||
open_orders = self.open_orders[order.asset] \
|
|
||||||
if order.asset is self.open_orders else []
|
|
||||||
|
|
||||||
open_orders.append(order)
|
|
||||||
|
|
||||||
self.open_orders[order.asset] = open_orders
|
|
||||||
|
|
||||||
order_position = self.positions[order.asset] \
|
order_position = self.positions[order.asset] \
|
||||||
if order.asset in self.positions else None
|
if order.asset in self.positions else None
|
||||||
@@ -58,17 +52,6 @@ class ExchangePortfolio(Portfolio):
|
|||||||
order_position.amount += order.amount
|
order_position.amount += order.amount
|
||||||
log.debug('open order added to portfolio')
|
log.debug('open order added to portfolio')
|
||||||
|
|
||||||
def _remove_open_order(self, order):
|
|
||||||
try:
|
|
||||||
open_orders = self.open_orders[order.asset]
|
|
||||||
if order in open_orders:
|
|
||||||
open_orders.remove(order)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
raise ValueError(
|
|
||||||
'unable to clear order not found in open order list.'
|
|
||||||
)
|
|
||||||
|
|
||||||
def execute_order(self, order, transaction):
|
def execute_order(self, order, transaction):
|
||||||
"""
|
"""
|
||||||
Update the open orders and positions to apply an executed order.
|
Update the open orders and positions to apply an executed order.
|
||||||
@@ -83,15 +66,14 @@ class ExchangePortfolio(Portfolio):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug('executing order {}'.format(order.id))
|
log.debug('executing order {}'.format(order.id))
|
||||||
self._remove_open_order(order)
|
del self.open_orders[order.id]
|
||||||
|
|
||||||
order_position = self.positions[order.asset] \
|
order_position = self.positions[order.asset] \
|
||||||
if order.asset in self.positions else None
|
if order.asset in self.positions else None
|
||||||
|
|
||||||
if order_position is None:
|
if order_position is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Trying to execute order for a position not held:'
|
'Trying to execute order for a position not held: %s' % order.id
|
||||||
' {}'.format(order.id)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.capital_used += order.amount * transaction.price
|
self.capital_used += order.amount * transaction.price
|
||||||
@@ -117,7 +99,7 @@ class ExchangePortfolio(Portfolio):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
log.info('removing cancelled order {}'.format(order.id))
|
log.info('removing cancelled order {}'.format(order.id))
|
||||||
self._remove_open_order(order)
|
del self.open_orders[order.id]
|
||||||
|
|
||||||
order_position = self.positions[order.asset] \
|
order_position = self.positions[order.asset] \
|
||||||
if order.asset in self.positions else None
|
if order.asset in self.positions else None
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None):
|
|||||||
data = json.load(data_file, object_hook=symbols_parser)
|
data = json.load(data_file, object_hook=symbols_parser)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
return dict()
|
return dict()
|
||||||
else:
|
else:
|
||||||
raise ExchangeSymbolsNotFound(
|
raise ExchangeSymbolsNotFound(
|
||||||
@@ -296,7 +296,7 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None):
|
|||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as handle:
|
with open(filename, 'rb') as handle:
|
||||||
return pickle.load(handle)
|
return pickle.load(handle)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -604,7 +604,6 @@ def mixin_market_params(exchange_name, params, market):
|
|||||||
# TODO: make this more externalized / configurable
|
# TODO: make this more externalized / configurable
|
||||||
if 'lot' in market:
|
if 'lot' in market:
|
||||||
params['min_trade_size'] = market['lot']
|
params['min_trade_size'] = market['lot']
|
||||||
params['lot'] = market['lot']
|
|
||||||
|
|
||||||
if exchange_name == 'bitfinex':
|
if exchange_name == 'bitfinex':
|
||||||
params['maker'] = 0.001
|
params['maker'] = 0.001
|
||||||
@@ -625,24 +624,6 @@ def mixin_market_params(exchange_name, params, market):
|
|||||||
if 'minimum_order_size' in info:
|
if 'minimum_order_size' in info:
|
||||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
params['min_trade_size'] = float(info['minimum_order_size'])
|
||||||
|
|
||||||
if 'lot' not in params:
|
|
||||||
params['lot'] = params['min_trade_size']
|
|
||||||
|
|
||||||
|
|
||||||
def from_ms_timestamp(ms):
|
def from_ms_timestamp(ms):
|
||||||
return pd.to_datetime(ms, unit='ms', utc=True)
|
return pd.to_datetime(ms, unit='ms', utc=True)
|
||||||
|
|
||||||
|
|
||||||
def get_epoch():
|
|
||||||
return pd.to_datetime('1970-1-1', utc=True)
|
|
||||||
|
|
||||||
|
|
||||||
def group_assets_by_exchange(assets):
|
|
||||||
exchange_assets = dict()
|
|
||||||
for asset in assets:
|
|
||||||
if asset.exchange not in exchange_assets:
|
|
||||||
exchange_assets[asset.exchange] = list()
|
|
||||||
|
|
||||||
exchange_assets[asset.exchange].append(asset)
|
|
||||||
|
|
||||||
return exchange_assets
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from catalyst.exchange.exchange_utils import get_exchange_auth, \
|
|||||||
get_exchange_folder
|
get_exchange_folder
|
||||||
|
|
||||||
|
|
||||||
def get_exchange(exchange_name, base_currency=None, must_authenticate=False):
|
def get_exchange(exchange_name, base_currency=None, portfolio=None,
|
||||||
|
must_authenticate=False):
|
||||||
exchange_auth = get_exchange_auth(exchange_name)
|
exchange_auth = get_exchange_auth(exchange_name)
|
||||||
|
|
||||||
has_auth = (exchange_auth['key'] != '' and exchange_auth['secret'] != '')
|
has_auth = (exchange_auth['key'] != '' and exchange_auth['secret'] != '')
|
||||||
@@ -23,6 +24,7 @@ def get_exchange(exchange_name, base_currency=None, must_authenticate=False):
|
|||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
secret=exchange_auth['secret'],
|
secret=exchange_auth['secret'],
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
|
portfolio=portfolio
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
@@ -17,9 +18,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
|||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
InvalidHistoryFrequencyError,
|
InvalidHistoryFrequencyError,
|
||||||
InvalidOrderStyle,
|
InvalidOrderStyle, OrphanOrderReverseError)
|
||||||
OrphanOrderError,
|
|
||||||
OrphanOrderReverseError)
|
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
||||||
ExchangeStopLimitOrder
|
ExchangeStopLimitOrder
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
||||||
@@ -28,12 +27,10 @@ from catalyst.exchange.poloniex.poloniex_api import Poloniex_api
|
|||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
from catalyst.finance.transaction import Transaction
|
from catalyst.finance.transaction import Transaction
|
||||||
from catalyst.protocol import Account
|
from catalyst.protocol import Account
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
|
|
||||||
log = Logger('Poloniex', level=LOG_LEVEL)
|
log = Logger('Poloniex', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class Poloniex(Exchange):
|
class Poloniex(Exchange):
|
||||||
def __init__(self, key, secret, base_currency, portfolio=None):
|
def __init__(self, key, secret, base_currency, portfolio=None):
|
||||||
self.api = Poloniex_api(key=key, secret=secret)
|
self.api = Poloniex_api(key=key, secret=secret)
|
||||||
@@ -90,6 +87,7 @@ class Poloniex(Exchange):
|
|||||||
# filled = -filled
|
# filled = -filled
|
||||||
|
|
||||||
price = float(order_status['rate'])
|
price = float(order_status['rate'])
|
||||||
|
order_type = order_status['type']
|
||||||
|
|
||||||
stop_price = None
|
stop_price = None
|
||||||
limit_price = None
|
limit_price = None
|
||||||
@@ -103,7 +101,7 @@ class Poloniex(Exchange):
|
|||||||
# executed_price = float(order_status['avg_execution_price'])
|
# executed_price = float(order_status['avg_execution_price'])
|
||||||
executed_price = price
|
executed_price = price
|
||||||
|
|
||||||
# TODO: Set Poloniex comission
|
# TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it.
|
||||||
commission = None
|
commission = None
|
||||||
|
|
||||||
# date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
|
# date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
|
||||||
@@ -294,8 +292,8 @@ class Poloniex(Exchange):
|
|||||||
"""
|
"""
|
||||||
exchange_symbol = self.get_symbol(asset)
|
exchange_symbol = self.get_symbol(asset)
|
||||||
|
|
||||||
if (isinstance(style, ExchangeLimitOrder)
|
if isinstance(style, ExchangeLimitOrder) or isinstance(style,
|
||||||
or isinstance(style, ExchangeStopLimitOrder)):
|
ExchangeStopLimitOrder):
|
||||||
if isinstance(style, ExchangeStopLimitOrder):
|
if isinstance(style, ExchangeStopLimitOrder):
|
||||||
log.warn('{} will ignore the stop price'.format(self.name))
|
log.warn('{} will ignore the stop price'.format(self.name))
|
||||||
|
|
||||||
@@ -367,7 +365,7 @@ class Poloniex(Exchange):
|
|||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
raise ExchangeRequestError(
|
raise ExchangeRequestError(
|
||||||
error='Unable to retrieve open orders: {}'.format(
|
error='Unable to retrieve open orders: {}'.format(
|
||||||
response['message'])
|
order_statuses['message'])
|
||||||
)
|
)
|
||||||
|
|
||||||
print(self.portfolio.open_orders)
|
print(self.portfolio.open_orders)
|
||||||
@@ -375,8 +373,8 @@ class Poloniex(Exchange):
|
|||||||
# TODO: Need to handle openOrders for 'all'
|
# TODO: Need to handle openOrders for 'all'
|
||||||
orders = list()
|
orders = list()
|
||||||
for order_status in response:
|
for order_status in response:
|
||||||
# will Throw error b/c Polo doesn't track order['symbol']
|
order, executed_price = self._create_order(
|
||||||
order, executed_price = self._create_order(order_status)
|
order_status) # will Throw error b/c Polo doesn't track order['symbol']
|
||||||
if asset is None or asset == order.sid:
|
if asset is None or asset == order.sid:
|
||||||
orders.append(order)
|
orders.append(order)
|
||||||
|
|
||||||
@@ -439,8 +437,7 @@ class Poloniex(Exchange):
|
|||||||
|
|
||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
log.info(
|
log.info(
|
||||||
'Unable to cancel order {order_id} on exchange {exchange} '
|
'Unable to cancel order {order_id} on exchange {exchange} {error}.'.format(
|
||||||
'{error}.'.format(
|
|
||||||
order_id=order.id,
|
order_id=order.id,
|
||||||
exchange=self.name,
|
exchange=self.name,
|
||||||
error=response['error']
|
error=response['error']
|
||||||
@@ -515,17 +512,17 @@ class Poloniex(Exchange):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
start_date = cached_symbols[exchange_symbol]['start_date']
|
start_date = cached_symbols[exchange_symbol]['start_date']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
start_date = time.strftime('%Y-%m-%d')
|
start_date = time.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
end_daily = cached_symbols[exchange_symbol]['end_daily']
|
end_daily = cached_symbols[exchange_symbol]['end_daily']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_daily = 'N/A'
|
end_daily = 'N/A'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
end_minute = cached_symbols[exchange_symbol]['end_minute']
|
end_minute = cached_symbols[exchange_symbol]['end_minute']
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
end_minute = 'N/A'
|
end_minute = 'N/A'
|
||||||
|
|
||||||
symbol_map[exchange_symbol] = dict(
|
symbol_map[exchange_symbol] = dict(
|
||||||
@@ -596,21 +593,19 @@ class Poloniex(Exchange):
|
|||||||
else:
|
else:
|
||||||
for tx in response:
|
for tx in response:
|
||||||
"""
|
"""
|
||||||
We maintain a list of dictionaries of transactions that
|
We maintain a list of dictionaries of transactions that correspond to
|
||||||
correspond to partially filled orders, indexed by
|
partially filled orders, indexed by order_id. Every time we query
|
||||||
order_id. Every time we query executed transactions
|
executed transactions from the exchange, we check if we had that
|
||||||
from the exchange, we check if we had that transaction
|
transaction for that order already. If not, we process it.
|
||||||
for that order already. If not, we process it.
|
|
||||||
|
|
||||||
When an order if fully filled, we flush the dict of
|
When an order if fully filled, we flush the dict of transactions
|
||||||
transactions associated with that order.
|
associated with that order.
|
||||||
"""
|
"""
|
||||||
if (not filter(
|
if (not filter(
|
||||||
lambda item: item['order_id'] == tx['tradeID'],
|
lambda item: item['order_id'] == tx['tradeID'],
|
||||||
self.transactions[order_id])):
|
self.transactions[order_id])):
|
||||||
log.debug(
|
log.debug(
|
||||||
'Got new transaction for order {}: amount {}, '
|
'Got new transaction for order {}: amount {}, price {}'.format(
|
||||||
'price {}'.format(
|
|
||||||
order_id, tx['amount'], tx['rate']))
|
order_id, tx['amount'], tx['rate']))
|
||||||
tx['amount'] = float(tx['amount'])
|
tx['amount'] = float(tx['amount'])
|
||||||
if (tx['type'] == 'sell'):
|
if (tx['type'] == 'sell'):
|
||||||
@@ -621,7 +616,7 @@ class Poloniex(Exchange):
|
|||||||
dt=pd.to_datetime(tx['date'], utc=True),
|
dt=pd.to_datetime(tx['date'], utc=True),
|
||||||
price=float(tx['rate']),
|
price=float(tx['rate']),
|
||||||
order_id=tx['tradeID'],
|
order_id=tx['tradeID'],
|
||||||
# it's a misnomer, but keep for compatibility
|
# it's a misnomer, but keeping it for compatibility
|
||||||
commission=float(tx['fee'])
|
commission=float(tx['fee'])
|
||||||
)
|
)
|
||||||
self.transactions[order_id].append(transaction)
|
self.transactions[order_id].append(transaction)
|
||||||
@@ -631,8 +626,7 @@ class Poloniex(Exchange):
|
|||||||
if (not order_open):
|
if (not order_open):
|
||||||
"""
|
"""
|
||||||
Since transactions have been executed individually
|
Since transactions have been executed individually
|
||||||
the only thing left to do is remove them from list
|
the only thing left to do is remove them from list of open_orders
|
||||||
of open_orders
|
|
||||||
"""
|
"""
|
||||||
del self.portfolio.open_orders[order_id]
|
del self.portfolio.open_orders[order_id]
|
||||||
del self.transactions[order_id]
|
del self.transactions[order_id]
|
||||||
|
|||||||
@@ -107,9 +107,8 @@ class Poloniex_api(object):
|
|||||||
data=post_data,
|
data=post_data,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
resource = urlopen(req, context=ssl._create_unverified_context())
|
return json.loads(
|
||||||
content = resource.read().decode('utf-8')
|
urlopen(req, context=ssl._create_unverified_context()).read())
|
||||||
return json.loads(content)
|
|
||||||
|
|
||||||
def returnticker(self):
|
def returnticker(self):
|
||||||
return self.query('returnTicker', {})
|
return self.query('returnTicker', {})
|
||||||
@@ -161,6 +160,10 @@ class Poloniex_api(object):
|
|||||||
def returnopenorders(self, market):
|
def returnopenorders(self, market):
|
||||||
return self.query('returnOpenOrders', {'currencyPair': market})
|
return self.query('returnOpenOrders', {'currencyPair': market})
|
||||||
|
|
||||||
|
def returntradehistory(self, market):
|
||||||
|
# TODO: optional start and/or end and limit
|
||||||
|
return self.query('returnTradeHistory', {'currencyPair': market})
|
||||||
|
|
||||||
def returnordertrades(self, ordernumber):
|
def returnordertrades(self, ordernumber):
|
||||||
return self.query('returnOrderTrades', {'orderNumber': ordernumber})
|
return self.query('returnOrderTrades', {'orderNumber': ordernumber})
|
||||||
|
|
||||||
@@ -173,7 +176,7 @@ class Poloniex_api(object):
|
|||||||
elif (immediateorcancel):
|
elif (immediateorcancel):
|
||||||
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'immediateOrCancel': immediateorcancel})
|
'immediateOrCancel': immediateorcancel, })
|
||||||
elif (postonly):
|
elif (postonly):
|
||||||
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
@@ -191,7 +194,7 @@ class Poloniex_api(object):
|
|||||||
elif (immediateorcancel):
|
elif (immediateorcancel):
|
||||||
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'immediateOrCancel': immediateorcancel})
|
'immediateOrCancel': immediateorcancel, })
|
||||||
elif (postonly):
|
elif (postonly):
|
||||||
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ class SimpleClock(object):
|
|||||||
This class is a drop-in replacement for
|
This class is a drop-in replacement for
|
||||||
:class:`zipline.gens.sim_engine.MinuteSimulationClock`.
|
:class:`zipline.gens.sim_engine.MinuteSimulationClock`.
|
||||||
|
|
||||||
This is a stripped down version because crypto exchanges run
|
This is a stripped down version because crypto exchanges run around the clock.
|
||||||
around the clock.
|
|
||||||
|
|
||||||
The :param:`time_skew` parameter represents the time difference between
|
The :param:`time_skew` parameter represents the time difference between
|
||||||
the Broker and the live trading machine's clock.
|
the Broker and the live trading machine's clock.
|
||||||
|
|||||||
@@ -1,18 +1,7 @@
|
|||||||
import csv
|
|
||||||
import numbers
|
import numbers
|
||||||
|
|
||||||
import copy
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import boto3
|
|
||||||
import time
|
|
||||||
|
|
||||||
from catalyst.assets._assets import TradingPair
|
|
||||||
|
|
||||||
from catalyst.exchange.exchange_utils import get_algo_folder
|
|
||||||
|
|
||||||
s3 = boto3.resource('s3')
|
|
||||||
|
|
||||||
|
|
||||||
def trend_direction(series):
|
def trend_direction(series):
|
||||||
@@ -130,256 +119,62 @@ def vwap(df):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def set_position_row(row, asset, asset_values=list()):
|
def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10):
|
||||||
"""
|
|
||||||
Apply the position data as individual columns.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
row: dict[str, Object]
|
|
||||||
asset: TradingPair
|
|
||||||
asset_values: list[str]
|
|
||||||
If a recorded_col contains a tuple which first value is an asset
|
|
||||||
matching a position, its value will be displayed with the
|
|
||||||
position and not in the index.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
asset_cols = ['symbol']
|
|
||||||
row['symbol'] = asset.symbol
|
|
||||||
|
|
||||||
position = next((p for p in row['positions'] if p['sid'] == asset), None)
|
|
||||||
|
|
||||||
columns = ['amount', 'cost_basis', 'last_sale_price']
|
|
||||||
for column in columns:
|
|
||||||
if position is not None:
|
|
||||||
row[column] = position[column]
|
|
||||||
|
|
||||||
else:
|
|
||||||
row[column] = 0
|
|
||||||
|
|
||||||
asset_cols.append(column)
|
|
||||||
|
|
||||||
values = asset_values[asset] if asset in asset_values else list()
|
|
||||||
for column in values:
|
|
||||||
row[column] = values[column]
|
|
||||||
|
|
||||||
asset_cols.append(column)
|
|
||||||
|
|
||||||
return asset_cols
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_stats(stats, recorded_cols=list()):
|
|
||||||
"""
|
|
||||||
Prepare the stats DataFrame for user-friendly output.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
stats: list[Object]
|
|
||||||
recorded_cols: list[str]
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
asset_cols = list()
|
|
||||||
|
|
||||||
stats = copy.deepcopy(stats)
|
|
||||||
# Using a copy since we are adding rows inside the loop.
|
|
||||||
for row_index, row_data in enumerate(list(stats)):
|
|
||||||
assets = [p['sid'] for p in row_data['positions']]
|
|
||||||
|
|
||||||
asset_values = dict()
|
|
||||||
if recorded_cols is not None:
|
|
||||||
for column in recorded_cols[:]:
|
|
||||||
value = row_data[column]
|
|
||||||
if type(value) is dict:
|
|
||||||
for asset in value:
|
|
||||||
if not isinstance(asset, TradingPair):
|
|
||||||
break
|
|
||||||
|
|
||||||
if asset not in assets:
|
|
||||||
assets.append(asset)
|
|
||||||
|
|
||||||
if asset not in asset_values:
|
|
||||||
asset_values[asset] = dict()
|
|
||||||
|
|
||||||
asset_values[asset][column] = value[asset]
|
|
||||||
|
|
||||||
if len(assets) == 1:
|
|
||||||
row = stats[row_index]
|
|
||||||
asset_cols = set_position_row(row, assets[0], asset_values)
|
|
||||||
|
|
||||||
elif len(assets) > 1:
|
|
||||||
for asset_index, asset in enumerate(assets):
|
|
||||||
if asset_index > 0:
|
|
||||||
row = copy.deepcopy(row_data)
|
|
||||||
stats.append(row)
|
|
||||||
|
|
||||||
else:
|
|
||||||
row = stats[row_index]
|
|
||||||
|
|
||||||
asset_cols = set_position_row(row, assets[asset_index],
|
|
||||||
asset_values)
|
|
||||||
|
|
||||||
df = pd.DataFrame(stats)
|
|
||||||
|
|
||||||
index_cols = [
|
|
||||||
'period_close', 'starting_cash', 'ending_cash', 'portfolio_value',
|
|
||||||
'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Removing the asset specific entries
|
|
||||||
if recorded_cols is not None:
|
|
||||||
recorded_cols = [x for x in recorded_cols if x not in asset_cols]
|
|
||||||
for column in recorded_cols:
|
|
||||||
index_cols.append(column)
|
|
||||||
|
|
||||||
df['orders'] = df['orders'].apply(lambda orders: len(orders))
|
|
||||||
df['transactions'] = df['transactions'].apply(
|
|
||||||
lambda transactions: len(transactions)
|
|
||||||
)
|
|
||||||
|
|
||||||
if asset_cols:
|
|
||||||
columns = asset_cols
|
|
||||||
df.set_index(index_cols, drop=True, inplace=True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
columns = index_cols
|
|
||||||
columns.remove('period_close')
|
|
||||||
df.set_index('period_close', drop=False, inplace=True)
|
|
||||||
|
|
||||||
df.dropna(axis=1, how='all', inplace=True)
|
|
||||||
df.sort_index(axis=0, level=0, inplace=True)
|
|
||||||
|
|
||||||
return df, columns
|
|
||||||
|
|
||||||
|
|
||||||
def get_pretty_stats(stats, recorded_cols=None, num_rows=10):
|
|
||||||
"""
|
"""
|
||||||
Format and print the last few rows of a statistics DataFrame.
|
Format and print the last few rows of a statistics DataFrame.
|
||||||
See the pyfolio project for the data structure.
|
See the pyfolio project for the data structure.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
stats: list[Object]
|
stats_df: DataFrame
|
||||||
An array of statistics for the period.
|
|
||||||
|
|
||||||
num_rows: int
|
num_rows: int
|
||||||
The number of rows to display on the screen.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str
|
str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(stats, pd.DataFrame):
|
stats_df.set_index('period_close', drop=True, inplace=True)
|
||||||
stats = stats.T.to_dict().values()
|
stats_df.dropna(axis=1, how='all', inplace=True)
|
||||||
|
|
||||||
df, columns = prepare_stats(stats, recorded_cols=recorded_cols)
|
|
||||||
|
|
||||||
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', 3)
|
||||||
pd.set_option('display.width', 1000)
|
pd.set_option('display.width', 1000)
|
||||||
pd.set_option('display.max_colwidth', 1000)
|
pd.set_option('display.max_colwidth', 1000)
|
||||||
|
|
||||||
|
columns = ['starting_cash', 'ending_cash', 'portfolio_value',
|
||||||
|
'pnl', 'long_exposure', 'short_exposure', 'orders',
|
||||||
|
'transactions', 'positions']
|
||||||
|
|
||||||
|
if recorded_cols is not None:
|
||||||
|
for column in recorded_cols:
|
||||||
|
columns.append(column)
|
||||||
|
|
||||||
|
def format_positions(positions):
|
||||||
|
parts = []
|
||||||
|
for position in positions:
|
||||||
|
msg = '{amount:.2f}{base} cost basis {cost_basis:.4f}{quote}'.format(
|
||||||
|
amount=position['amount'],
|
||||||
|
base=position['sid'].base_currency,
|
||||||
|
cost_basis=position['cost_basis'],
|
||||||
|
quote=position['sid'].quote_currency
|
||||||
|
)
|
||||||
|
parts.append(msg)
|
||||||
|
return ', '.join(parts)
|
||||||
|
|
||||||
formatters = {
|
formatters = {
|
||||||
|
'orders': lambda orders: len(orders),
|
||||||
|
'transactions': lambda transactions: len(transactions),
|
||||||
'returns': lambda returns: "{0:.4f}".format(returns),
|
'returns': lambda returns: "{0:.4f}".format(returns),
|
||||||
|
'positions': format_positions
|
||||||
}
|
}
|
||||||
|
|
||||||
return df.tail(num_rows).to_string(
|
return stats_df.tail(num_rows).to_string(
|
||||||
columns=columns,
|
columns=columns,
|
||||||
formatters=formatters
|
formatters=formatters
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_csv_stats(stats, recorded_cols=None):
|
|
||||||
"""
|
|
||||||
Create a CSV buffer from the stats DataFrame.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
path: str
|
|
||||||
stats: list[Object]
|
|
||||||
recorded_cols: list[str]
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
df, columns = prepare_stats(stats, recorded_cols=recorded_cols)
|
|
||||||
|
|
||||||
return df.to_csv(
|
|
||||||
None,
|
|
||||||
columns=columns,
|
|
||||||
# encoding='utf-8',
|
|
||||||
quoting=csv.QUOTE_NONNUMERIC
|
|
||||||
).encode()
|
|
||||||
|
|
||||||
|
|
||||||
def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None,
|
|
||||||
folder='catalyst/stats', bytes_to_write=None):
|
|
||||||
"""
|
|
||||||
Uploads the performance stats to a S3 bucket.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
uri: str
|
|
||||||
stats: list[Object]
|
|
||||||
algo_namespace: str
|
|
||||||
recorded_cols: list[str]
|
|
||||||
folder: str
|
|
||||||
bytes_to_write: str
|
|
||||||
Option to reuse bytes instead of re-computing the csv
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
if bytes_to_write is None:
|
|
||||||
bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols)
|
|
||||||
|
|
||||||
now = pd.Timestamp.utcnow()
|
|
||||||
timestr = now.strftime('%Y%m%d')
|
|
||||||
pid = os.getpid()
|
|
||||||
|
|
||||||
parts = uri.split('//')
|
|
||||||
obj = s3.Object(parts[1], '{}/{}-{}-{}.csv'.format(
|
|
||||||
folder, timestr, algo_namespace, pid
|
|
||||||
))
|
|
||||||
obj.put(Body=bytes_to_write)
|
|
||||||
|
|
||||||
|
|
||||||
def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
|
||||||
"""
|
|
||||||
Saves the performance stats to the algo local folder.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
stats: list[Object]
|
|
||||||
algo_namespace: str
|
|
||||||
recorded_cols: list[str]
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
str
|
|
||||||
|
|
||||||
"""
|
|
||||||
bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols)
|
|
||||||
|
|
||||||
timestr = time.strftime('%Y%m%d')
|
|
||||||
folder = get_algo_folder(algo_namespace)
|
|
||||||
|
|
||||||
filename = os.path.join(folder, '{}-{}.csv'.format(timestr, 'frames'))
|
|
||||||
|
|
||||||
with open(filename, 'wb') as handle:
|
|
||||||
handle.write(bytes_to_write)
|
|
||||||
|
|
||||||
return bytes_to_write
|
|
||||||
|
|
||||||
|
|
||||||
def df_to_string(df):
|
def df_to_string(df):
|
||||||
"""
|
"""
|
||||||
Create a formatted str representation of the DataFrame.
|
Create a formatted str representation of the DataFrame.
|
||||||
|
|||||||
@@ -15,9 +15,14 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from numpy import isfinite
|
from sys import float_info
|
||||||
|
|
||||||
from six import with_metaclass
|
from six import with_metaclass
|
||||||
|
|
||||||
|
import catalyst.utils.math_utils as zp_math
|
||||||
|
|
||||||
|
from numpy import isfinite
|
||||||
|
|
||||||
from catalyst.errors import BadOrderParameters
|
from catalyst.errors import BadOrderParameters
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -154,8 +154,8 @@ class RiskMetricsPeriod(object):
|
|||||||
self.algorithm_returns.values,
|
self.algorithm_returns.values,
|
||||||
self.benchmark_returns.values,
|
self.benchmark_returns.values,
|
||||||
)
|
)
|
||||||
self.excess_return = self.algorithm_period_returns \
|
self.excess_return = self.algorithm_period_returns - \
|
||||||
- self.treasury_period_return
|
self.treasury_period_return
|
||||||
self.max_drawdown = max_drawdown(self.algorithm_returns.values)
|
self.max_drawdown = max_drawdown(self.algorithm_returns.values)
|
||||||
self.max_leverage = self.calculate_max_leverage()
|
self.max_leverage = self.calculate_max_leverage()
|
||||||
|
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ def choose_treasury(select_treasury, treasury_curves, start_session,
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Supress warning for 'OPEN' calendar
|
if search_day and trading_calendar.name != 'OPEN': # Supress warning for 'OPEN' calendar
|
||||||
if search_day and trading_calendar.name != 'OPEN':
|
|
||||||
if (search_dist is None or search_dist > 1) and \
|
if (search_dist is None or search_dist > 1) and \
|
||||||
search_days[0] <= end_session <= search_days[-1]:
|
search_days[0] <= end_session <= search_days[-1]:
|
||||||
message = "No rate within 1 trading day of end date = \
|
message = "No rate within 1 trading day of end date = \
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT = 0.025
|
|||||||
DEFAULT_FUTURE_VOLUME_SLIPPAGE_BAR_LIMIT = 0.05
|
DEFAULT_FUTURE_VOLUME_SLIPPAGE_BAR_LIMIT = 0.05
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LiquidityExceeded(Exception):
|
class LiquidityExceeded(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from .statistical import (
|
from .statistical import (
|
||||||
|
RollingPearson,
|
||||||
|
RollingLinearRegression,
|
||||||
RollingLinearRegressionOfReturns,
|
RollingLinearRegressionOfReturns,
|
||||||
RollingPearsonOfReturns,
|
RollingPearsonOfReturns,
|
||||||
|
RollingSpearman,
|
||||||
RollingSpearmanOfReturns,
|
RollingSpearmanOfReturns,
|
||||||
)
|
)
|
||||||
from .technical import (
|
from .technical import (
|
||||||
|
|||||||
@@ -38,11 +38,9 @@ class USEquityPricingLoader(PipelineLoader):
|
|||||||
|
|
||||||
def __init__(self, bundle, data_frequency, dataset):
|
def __init__(self, bundle, data_frequency, dataset):
|
||||||
|
|
||||||
# TODO: This is currently broken, No Pipeline support for Catalyst
|
if data_frequency == 'daily':
|
||||||
# if data_frequency == 'daily':
|
reader = bundle.daily_bar_reader
|
||||||
# reader = bundle.daily_bar_reader
|
elif daily_bar_reader == 'minute':
|
||||||
# elif daily_bar_reader == 'minute':
|
|
||||||
if data_frequency == 'minute':
|
|
||||||
reader = bundle.minute_bar_reader
|
reader = bundle.minute_bar_reader
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -53,9 +51,7 @@ class USEquityPricingLoader(PipelineLoader):
|
|||||||
|
|
||||||
if data_frequency == 'daily':
|
if data_frequency == 'daily':
|
||||||
all_sessions = cal.all_sessions
|
all_sessions = cal.all_sessions
|
||||||
# TODO: this cannot be right, but no pipeline support at the moment
|
elif daily_bar_reader == 'minute':
|
||||||
# elif daily_bar_reader == 'minute':
|
|
||||||
elif data_frequency == 'minute':
|
|
||||||
reader = bundle.minute_bar_reader
|
reader = bundle.minute_bar_reader
|
||||||
all_sessions = cal.all_minutes
|
all_sessions = cal.all_minutes
|
||||||
|
|
||||||
|
|||||||
@@ -180,3 +180,4 @@ class DataFrameLoader(PipelineLoader):
|
|||||||
@property
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
return self._columns
|
return self._columns
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.exchange.exchange_utils import get_exchange_symbols
|
||||||
|
|
||||||
|
from catalyst.api import (
|
||||||
|
symbols,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
context.i = -1
|
||||||
|
context.base_currency = 'btc'
|
||||||
|
|
||||||
|
|
||||||
|
def handle_data(context, data):
|
||||||
|
lookback = 60 * 24 * 7 # (minutes, hours, days)
|
||||||
|
context.i += 1
|
||||||
|
if context.i < lookback:
|
||||||
|
return
|
||||||
|
|
||||||
|
today = context.blotter.current_dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# update universe everyday
|
||||||
|
new_day = 60 * 24
|
||||||
|
if not context.i % new_day:
|
||||||
|
context.universe = universe(context, today)
|
||||||
|
|
||||||
|
# get data every 30 minutes
|
||||||
|
minutes = 30
|
||||||
|
if not context.i % minutes and context.universe:
|
||||||
|
for coin in context.coins:
|
||||||
|
pair = str(coin.symbol)
|
||||||
|
|
||||||
|
# ohlcv data
|
||||||
|
open = data.history(coin, 'open', lookback,
|
||||||
|
'1m').ffill().bfill().resample(
|
||||||
|
'30T').first()
|
||||||
|
high = data.history(coin, 'high', lookback,
|
||||||
|
'1m').ffill().bfill().resample('30T').max()
|
||||||
|
low = data.history(coin, 'low', lookback,
|
||||||
|
'1m').ffill().bfill().resample('30T').min()
|
||||||
|
close = data.history(coin, 'price', lookback,
|
||||||
|
'1m').ffill().bfill().resample(
|
||||||
|
'30T').last()
|
||||||
|
volume = data.history(coin, 'volume', lookback,
|
||||||
|
'1m').ffill().bfill().resample(
|
||||||
|
'30T').sum()
|
||||||
|
|
||||||
|
print(today, pair, close[-1])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(context=None, results=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def universe(context, today):
|
||||||
|
json_symbols = get_exchange_symbols('poloniex')
|
||||||
|
poloniex_universe_df = pd.DataFrame.from_dict(
|
||||||
|
json_symbols).transpose().astype(str)
|
||||||
|
poloniex_universe_df['base_currency'] = poloniex_universe_df.apply(
|
||||||
|
lambda row: row.symbol.split('_')[1],
|
||||||
|
axis=1)
|
||||||
|
poloniex_universe_df['market_currency'] = poloniex_universe_df.apply(
|
||||||
|
lambda row: row.symbol.split('_')[0],
|
||||||
|
axis=1)
|
||||||
|
poloniex_universe_df = poloniex_universe_df[
|
||||||
|
poloniex_universe_df['base_currency'] == context.base_currency]
|
||||||
|
poloniex_universe_df = poloniex_universe_df[
|
||||||
|
poloniex_universe_df.symbol != 'gas_btc']
|
||||||
|
|
||||||
|
# Markets currently not working on Catalyst 0.3.1
|
||||||
|
# 2017-01-01
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'bcn_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'burst_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'dgb_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'doge_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'emc2_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'pink_btc']
|
||||||
|
# poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'sc_btc']
|
||||||
|
print(poloniex_universe_df.head())
|
||||||
|
|
||||||
|
date = str(today).split(' ')[0]
|
||||||
|
|
||||||
|
poloniex_universe_df = poloniex_universe_df[
|
||||||
|
poloniex_universe_df.start_date < date]
|
||||||
|
context.coins = symbols(*poloniex_universe_df.symbol)
|
||||||
|
print(len(poloniex_universe_df))
|
||||||
|
return poloniex_universe_df.symbol.tolist()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
start_date = pd.to_datetime('2017-01-01', utc=True)
|
||||||
|
end_date = pd.to_datetime('2017-10-15', utc=True)
|
||||||
|
|
||||||
|
performance = run_algorithm(start=start_date, end=end_date,
|
||||||
|
capital_base=10000.0,
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='poloniex',
|
||||||
|
data_frequency='minute',
|
||||||
|
base_currency='btc',
|
||||||
|
live=False,
|
||||||
|
live_graph=False,
|
||||||
|
algo_namespace='test')
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
"""
|
||||||
|
Requires Catalyst version 0.3.0 or above
|
||||||
|
Tested on Catalyst version 0.3.3
|
||||||
|
|
||||||
|
These example aims to provide and easy way for users to learn how to collect data from the different exchanges.
|
||||||
|
You simply need to specify the exchange and the market that you want to focus on.
|
||||||
|
You will all see how to create a universe and filter it base on the exchange and the market you desire.
|
||||||
|
|
||||||
|
The example prints out the closing price of all the pairs for a given market-exchange every 30 minutes.
|
||||||
|
The example also contains the ohlcv minute data for the past seven days which could be used to create indicators
|
||||||
|
Use this as the backbone to create your own trading strategies.
|
||||||
|
|
||||||
|
Variables lookback date and date are used to ensure data for a coin existed on the lookback period specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import timedelta
|
||||||
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.exchange.exchange_utils import get_exchange_symbols
|
||||||
|
|
||||||
|
from catalyst.api import (
|
||||||
|
symbols,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
context.i = -1 # counts the minutes
|
||||||
|
context.exchange = 'poloniex' # must match the exchange specified in run_algorithm
|
||||||
|
context.base_currency = 'btc' # must match the base currency specified in run_algorithm
|
||||||
|
|
||||||
|
|
||||||
|
def handle_data(context, data):
|
||||||
|
lookback = 60 * 24 * 7 # (minutes, hours, days) of how far to lookback in the data history
|
||||||
|
context.i += 1
|
||||||
|
|
||||||
|
# current date formatted into a string
|
||||||
|
today = context.blotter.current_dt
|
||||||
|
date, time = today.strftime('%Y-%m-%d %H:%M:%S').split(' ')
|
||||||
|
lookback_date = today - timedelta(days=(
|
||||||
|
lookback / (60 * 24))) # subtract the amount of days specified in lookback
|
||||||
|
lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[
|
||||||
|
0] # get only the date as a string
|
||||||
|
|
||||||
|
# update universe everyday
|
||||||
|
new_day = 60 * 24
|
||||||
|
if not context.i % new_day:
|
||||||
|
context.universe = universe(context, lookback_date, date)
|
||||||
|
|
||||||
|
# get data every 30 minutes
|
||||||
|
minutes = 30
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 30 minute interval ohlcv data (the standard data required for candlestick or indicators/signals)
|
||||||
|
# 30T means 30 minutes re-sampling of one minute data. change to your desire time interval.
|
||||||
|
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 equivalent to current price
|
||||||
|
# displays the minute price for each pair every 30 minutes
|
||||||
|
print(
|
||||||
|
today, pair, opened[-1], high[-1], low[-1], close[-1], 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):
|
||||||
|
json_symbols = get_exchange_symbols(
|
||||||
|
context.exchange) # get all the pairs for the exchange
|
||||||
|
universe_df = pd.DataFrame.from_dict(json_symbols).transpose().astype(
|
||||||
|
str) # convert into a dataframe
|
||||||
|
universe_df['base_currency'] = universe_df.apply(
|
||||||
|
lambda row: row.symbol.split('_')[1],
|
||||||
|
axis=1)
|
||||||
|
universe_df['market_currency'] = universe_df.apply(
|
||||||
|
lambda row: row.symbol.split('_')[0],
|
||||||
|
axis=1)
|
||||||
|
# Filter all the exchange pairs to only the ones for a give base currency
|
||||||
|
universe_df = universe_df[
|
||||||
|
universe_df['base_currency'] == context.base_currency]
|
||||||
|
|
||||||
|
# Filter all the pairs to ensure that pair existed in the current date range
|
||||||
|
universe_df = universe_df[universe_df.start_date < lookback_date]
|
||||||
|
universe_df = universe_df[universe_df.end_daily >= current_date]
|
||||||
|
context.coins = symbols(
|
||||||
|
*universe_df.symbol) # convert all the pairs to symbols
|
||||||
|
return universe_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-01-08', utc=True)
|
||||||
|
end_date = pd.to_datetime('2017-11-13', utc=True)
|
||||||
|
|
||||||
|
performance = run_algorithm(start=start_date, end=end_date,
|
||||||
|
capital_base=10000.0,
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='poloniex',
|
||||||
|
data_frequency='minute',
|
||||||
|
base_currency='btc',
|
||||||
|
live=False,
|
||||||
|
live_graph=False,
|
||||||
|
algo_namespace='simple_universe')
|
||||||
|
|
||||||
|
"""
|
||||||
|
Run in Terminal (inside catalyst environment):
|
||||||
|
python simple_universe.py
|
||||||
|
"""
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import talib
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import talib
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.api import symbol
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
print('initializing')
|
||||||
|
context.asset = symbol('btc_usdt')
|
||||||
|
|
||||||
|
|
||||||
|
def handle_data(context, data):
|
||||||
|
print('handling bar: {}'.format(data.current_dt))
|
||||||
|
|
||||||
|
price = data.current(context.asset, 'close')
|
||||||
|
print('got price {price}'.format(price=price))
|
||||||
|
|
||||||
|
try:
|
||||||
|
prices = data.history(
|
||||||
|
context.asset,
|
||||||
|
fields='close',
|
||||||
|
bar_count=60,
|
||||||
|
frequency='1D'
|
||||||
|
)
|
||||||
|
print('got {} price entries\n'.format(len(prices), prices))
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
|
run_algorithm(
|
||||||
|
capital_base=1,
|
||||||
|
start=pd.to_datetime('2016-2-11', utc=True),
|
||||||
|
end=pd.to_datetime('2017-8-31', utc=True),
|
||||||
|
data_frequency='daily',
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=None,
|
||||||
|
exchange_name='bittrex',
|
||||||
|
algo_namespace='issue_57',
|
||||||
|
base_currency='btc'
|
||||||
|
<<<<<<< HEAD
|
||||||
|
)
|
||||||
|
=======
|
||||||
|
)
|
||||||
|
>>>>>>> develop
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
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='daily')
|
||||||
|
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, )
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from logbook import Logger, DEBUG
|
||||||
|
|
||||||
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.api import (schedule_function, order_target_percent, symbol,
|
||||||
|
date_rules, get_open_orders, cancel_order, record,
|
||||||
|
set_commission, set_slippage)
|
||||||
|
|
||||||
|
log = Logger('rodrigo_1', level=DEBUG)
|
||||||
|
"""
|
||||||
|
The initialize function sets any data or variables that
|
||||||
|
you'll use in your algorithm.
|
||||||
|
It's only called once at the beginning of your algorithm.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
# Select asset of interest
|
||||||
|
context.asset = symbol('BTC_USD')
|
||||||
|
|
||||||
|
# set_commission(TradingPairFeeSchedule(maker_fee=0.5, taker_fee=0.5))
|
||||||
|
# set_slippage(TradingPairFixedSlippage(spread=0.5))
|
||||||
|
# Set up a rebalance method to run every day
|
||||||
|
schedule_function(rebalance, date_rule=date_rules.every_day())
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Rebalance function scheduled to run once per day.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def rebalance(context, data):
|
||||||
|
# To make market decisions, we're calculating the token's
|
||||||
|
# moving average for the last 5 days.
|
||||||
|
|
||||||
|
# We get the price history for the last 5 days.
|
||||||
|
price_history = data.history(context.asset, fields='price', bar_count=5,
|
||||||
|
frequency='1d')
|
||||||
|
|
||||||
|
# Then we take an average of those 5 days.
|
||||||
|
average_price = price_history.mean()
|
||||||
|
|
||||||
|
# We also get the coin's current price.
|
||||||
|
price = data.current(context.asset, 'price')
|
||||||
|
|
||||||
|
# Cancel any outstanding orders
|
||||||
|
orders = get_open_orders(context.asset) or []
|
||||||
|
for order in orders:
|
||||||
|
cancel_order(order)
|
||||||
|
|
||||||
|
# If our coin is currently listed on a major exchange
|
||||||
|
if data.can_trade(context.asset):
|
||||||
|
# If the current price is 1% above the 5-day average price,
|
||||||
|
# we open a long position. If the current price is below the
|
||||||
|
# average price, then we want to close our position to 0 shares.
|
||||||
|
if price > (1.01 * average_price):
|
||||||
|
# Place the buy order (positive means buy, negative means sell)
|
||||||
|
order_target_percent(context.asset, .99)
|
||||||
|
log.info("Buying %s" % (context.asset.symbol))
|
||||||
|
elif price < average_price:
|
||||||
|
# Sell all of our shares by setting the target position to zero
|
||||||
|
order_target_percent(context.asset, 0)
|
||||||
|
log.info("Selling %s" % (context.asset.symbol))
|
||||||
|
|
||||||
|
# Use the record() method to track up to five custom signals.
|
||||||
|
# Record Apple's current price and the average price over the last
|
||||||
|
# five days.
|
||||||
|
cash = context.portfolio.cash
|
||||||
|
leverage = context.account.leverage
|
||||||
|
|
||||||
|
record(price=price, average_price=average_price, cash=cash,
|
||||||
|
leverage=leverage)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(context=None, results=None):
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
# Plot the portfolio and asset data.
|
||||||
|
ax1 = plt.subplot(511)
|
||||||
|
results[['portfolio_value']].plot(ax=ax1)
|
||||||
|
ax1.set_ylabel('Portfolio Value (USD)')
|
||||||
|
|
||||||
|
ax2 = plt.subplot(512, sharex=ax1)
|
||||||
|
ax2.set_ylabel('{asset} (USD)'.format(asset=context.asset))
|
||||||
|
(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]
|
||||||
|
]
|
||||||
|
sells = trans.ix[
|
||||||
|
[t[0]['amount'] < 0 for t in trans.transactions]
|
||||||
|
]
|
||||||
|
|
||||||
|
ax2.plot(
|
||||||
|
buys.index,
|
||||||
|
results.price[buys.index],
|
||||||
|
'^',
|
||||||
|
markersize=10,
|
||||||
|
color='g',
|
||||||
|
)
|
||||||
|
ax2.plot(
|
||||||
|
sells.index,
|
||||||
|
results.price[sells.index],
|
||||||
|
'v',
|
||||||
|
markersize=10,
|
||||||
|
color='r',
|
||||||
|
)
|
||||||
|
|
||||||
|
ax3 = plt.subplot(513, sharex=ax1)
|
||||||
|
results[['leverage']].plot(ax=ax3)
|
||||||
|
ax3.set_ylabel('Leverage ')
|
||||||
|
|
||||||
|
ax4 = plt.subplot(514, sharex=ax1)
|
||||||
|
results[['cash']].plot(ax=ax4)
|
||||||
|
ax4.set_ylabel('Cash (USD)')
|
||||||
|
|
||||||
|
results[[
|
||||||
|
'algorithm',
|
||||||
|
'benchmark',
|
||||||
|
]] = results[[
|
||||||
|
'algorithm_period_return',
|
||||||
|
'benchmark_period_return',
|
||||||
|
]]
|
||||||
|
|
||||||
|
ax5 = plt.subplot(515, sharex=ax1)
|
||||||
|
results[[
|
||||||
|
'algorithm',
|
||||||
|
'benchmark',
|
||||||
|
]].plot(ax=ax5)
|
||||||
|
ax5.set_ylabel('Percent Change')
|
||||||
|
|
||||||
|
plt.legend(loc=3)
|
||||||
|
|
||||||
|
# Show the plot.
|
||||||
|
plt.gcf().set_size_inches(18, 8)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
run_algorithm(
|
||||||
|
capital_base=100000,
|
||||||
|
start=pd.to_datetime('2017-1-1', utc=True),
|
||||||
|
end=pd.to_datetime('2017-10-22', utc=True),
|
||||||
|
data_frequency='minute',
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=None,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
algo_namespace='rodrigo_1',
|
||||||
|
base_currency='usd'
|
||||||
|
)
|
||||||
@@ -55,4 +55,4 @@ from .core import ( # noqa
|
|||||||
write_bcolz_minute_data,
|
write_bcolz_minute_data,
|
||||||
write_compressed,
|
write_compressed,
|
||||||
)
|
)
|
||||||
from .fixtures import ZiplineTestCase # noqa
|
from .fixtures import CatalystTestCase # noqa
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ def create_data_portal_from_trade_history(asset_finder, trading_calendar,
|
|||||||
return DataPortal(
|
return DataPortal(
|
||||||
asset_finder, trading_calendar,
|
asset_finder, trading_calendar,
|
||||||
first_trading_day=equity_daily_reader.first_trading_day,
|
first_trading_day=equity_daily_reader.first_trading_day,
|
||||||
equity_daily_reader=equity_daily_reader,
|
daily_reader=equity_daily_reader,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
minutes = trading_calendar.minutes_in_range(
|
minutes = trading_calendar.minutes_in_range(
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ from catalyst.utils.paths import ensure_directory
|
|||||||
catalyst_dir = os.path.dirname(catalyst.__file__)
|
catalyst_dir = os.path.dirname(catalyst.__file__)
|
||||||
|
|
||||||
|
|
||||||
class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
|
class CatalystTestCase(with_metaclass(FinalMeta, TestCase)):
|
||||||
"""
|
"""
|
||||||
Shared extensions to core unittest.TestCase.
|
Shared extensions to core unittest.TestCase.
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
|
|||||||
cls._base_init_fixtures_was_called = False
|
cls._base_init_fixtures_was_called = False
|
||||||
cls.init_class_fixtures()
|
cls.init_class_fixtures()
|
||||||
assert cls._base_init_fixtures_was_called, (
|
assert cls._base_init_fixtures_was_called, (
|
||||||
"ZiplineTestCase.init_class_fixtures() was not called.\n"
|
"CatalystTestCase.init_class_fixtures() was not called.\n"
|
||||||
"This probably means that you overrode init_class_fixtures"
|
"This probably means that you overrode init_class_fixtures"
|
||||||
" without calling super()."
|
" without calling super()."
|
||||||
)
|
)
|
||||||
@@ -170,7 +170,7 @@ class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
|
|||||||
self._init_instance_fixtures_was_called = False
|
self._init_instance_fixtures_was_called = False
|
||||||
self.init_instance_fixtures()
|
self.init_instance_fixtures()
|
||||||
assert self._init_instance_fixtures_was_called, (
|
assert self._init_instance_fixtures_was_called, (
|
||||||
"ZiplineTestCase.init_instance_fixtures() was not"
|
"CatalystTestCase.init_instance_fixtures() was not"
|
||||||
" called.\n"
|
" called.\n"
|
||||||
"This probably means that you overrode"
|
"This probably means that you overrode"
|
||||||
" init_instance_fixtures without calling super()."
|
" init_instance_fixtures without calling super()."
|
||||||
@@ -251,7 +251,7 @@ def alias(attr_name):
|
|||||||
|
|
||||||
class WithDefaultDateBounds(object):
|
class WithDefaultDateBounds(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin which makes it possible to synchronize date bounds
|
CatalystTestCase mixin which makes it possible to synchronize date bounds
|
||||||
across fixtures.
|
across fixtures.
|
||||||
|
|
||||||
This fixture should always be the last fixture in bases of any fixture or
|
This fixture should always be the last fixture in bases of any fixture or
|
||||||
@@ -264,13 +264,13 @@ class WithDefaultDateBounds(object):
|
|||||||
The date bounds to be used for fixtures that want to have consistent
|
The date bounds to be used for fixtures that want to have consistent
|
||||||
dates.
|
dates.
|
||||||
"""
|
"""
|
||||||
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
|
START_DATE = pd.Timestamp('2016-01-03', tz='utc')
|
||||||
END_DATE = pd.Timestamp('2006-12-29', tz='utc')
|
END_DATE = pd.Timestamp('2016-12-29', tz='utc')
|
||||||
|
|
||||||
|
|
||||||
class WithLogger(object):
|
class WithLogger(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.log_handler as an instance-level
|
CatalystTestCase mixin providing cls.log_handler as an instance-level
|
||||||
fixture.
|
fixture.
|
||||||
|
|
||||||
After init_instance_fixtures has been called `self.log_handler` will be a
|
After init_instance_fixtures has been called `self.log_handler` will be a
|
||||||
@@ -295,7 +295,7 @@ class WithLogger(object):
|
|||||||
|
|
||||||
class WithAssetFinder(WithDefaultDateBounds):
|
class WithAssetFinder(WithDefaultDateBounds):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.asset_finder as a class-level fixture.
|
CatalystTestCase mixin providing cls.asset_finder as a class-level fixture.
|
||||||
|
|
||||||
After init_class_fixtures has been called, `cls.asset_finder` is populated
|
After init_class_fixtures has been called, `cls.asset_finder` is populated
|
||||||
with an AssetFinder.
|
with an AssetFinder.
|
||||||
@@ -402,7 +402,7 @@ class WithAssetFinder(WithDefaultDateBounds):
|
|||||||
|
|
||||||
class WithTradingCalendars(object):
|
class WithTradingCalendars(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.trading_calendar,
|
CatalystTestCase mixin providing cls.trading_calendar,
|
||||||
cls.all_trading_calendars, cls.trading_calendar_for_asset_type as a
|
cls.all_trading_calendars, cls.trading_calendar_for_asset_type as a
|
||||||
class-level fixture.
|
class-level fixture.
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ class WithTradingCalendars(object):
|
|||||||
with that asset type.
|
with that asset type.
|
||||||
"""
|
"""
|
||||||
TRADING_CALENDAR_STRS = ('NYSE',)
|
TRADING_CALENDAR_STRS = ('NYSE',)
|
||||||
TRADING_CALENDAR_FOR_ASSET_TYPE = {Equity: 'NYSE', Future: 'us_futures'}
|
TRADING_CALENDAR_FOR_ASSET_TYPE = {Equity: 'NYSE', Future: 'us_futures', }
|
||||||
TRADING_CALENDAR_FOR_EXCHANGE = {}
|
TRADING_CALENDAR_FOR_EXCHANGE = {}
|
||||||
# For backwards compatibility, exisitng tests and fixtures refer to
|
# For backwards compatibility, exisitng tests and fixtures refer to
|
||||||
# `trading_calendar` with the assumption that the value is the NYSE
|
# `trading_calendar` with the assumption that the value is the NYSE
|
||||||
@@ -460,7 +460,7 @@ class WithTradingEnvironment(WithAssetFinder,
|
|||||||
WithTradingCalendars,
|
WithTradingCalendars,
|
||||||
WithDefaultDateBounds):
|
WithDefaultDateBounds):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.env as a class-level fixture.
|
CatalystTestCase mixin providing cls.env as a class-level fixture.
|
||||||
|
|
||||||
After ``init_class_fixtures`` has been called, `cls.env` is populated
|
After ``init_class_fixtures`` has been called, `cls.env` is populated
|
||||||
with a trading environment whose `asset_finder` is the result of
|
with a trading environment whose `asset_finder` is the result of
|
||||||
@@ -560,7 +560,7 @@ class WithTradingEnvironment(WithAssetFinder,
|
|||||||
|
|
||||||
class WithSimParams(WithTradingEnvironment):
|
class WithSimParams(WithTradingEnvironment):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.sim_params as a class level fixture.
|
CatalystTestCase mixin providing cls.sim_params as a class level fixture.
|
||||||
|
|
||||||
The arguments used to construct the trading environment may be overridded
|
The arguments used to construct the trading environment may be overridded
|
||||||
by putting ``SIM_PARAMS_{argname}`` in the class dict except for the
|
by putting ``SIM_PARAMS_{argname}`` in the class dict except for the
|
||||||
@@ -615,7 +615,7 @@ class WithSimParams(WithTradingEnvironment):
|
|||||||
|
|
||||||
class WithTradingSessions(WithTradingCalendars, WithDefaultDateBounds):
|
class WithTradingSessions(WithTradingCalendars, WithDefaultDateBounds):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.trading_days, cls.all_trading_sessions
|
CatalystTestCase mixin providing cls.trading_days, cls.all_trading_sessions
|
||||||
as a class-level fixture.
|
as a class-level fixture.
|
||||||
|
|
||||||
After init_class_fixtures has been called, `cls.all_trading_sessions`
|
After init_class_fixtures has been called, `cls.all_trading_sessions`
|
||||||
@@ -668,7 +668,7 @@ class WithTradingSessions(WithTradingCalendars, WithDefaultDateBounds):
|
|||||||
|
|
||||||
class WithTmpDir(object):
|
class WithTmpDir(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixing providing cls.tmpdir as a class-level fixture.
|
CatalystTestCase mixing providing cls.tmpdir as a class-level fixture.
|
||||||
|
|
||||||
After init_class_fixtures has been called, `cls.tmpdir` is populated with
|
After init_class_fixtures has been called, `cls.tmpdir` is populated with
|
||||||
a `testfixtures.TempDirectory` object whose path is `cls.TMP_DIR_PATH`.
|
a `testfixtures.TempDirectory` object whose path is `cls.TMP_DIR_PATH`.
|
||||||
@@ -691,7 +691,7 @@ class WithTmpDir(object):
|
|||||||
|
|
||||||
class WithInstanceTmpDir(object):
|
class WithInstanceTmpDir(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixing providing self.tmpdir as an instance-level fixture.
|
CatalystTestCase mixing providing self.tmpdir as an instance-level fixture.
|
||||||
|
|
||||||
After init_instance_fixtures has been called, `self.tmpdir` is populated
|
After init_instance_fixtures has been called, `self.tmpdir` is populated
|
||||||
with a `testfixtures.TempDirectory` object whose path is
|
with a `testfixtures.TempDirectory` object whose path is
|
||||||
@@ -714,7 +714,7 @@ class WithInstanceTmpDir(object):
|
|||||||
|
|
||||||
class WithEquityDailyBarData(WithTradingEnvironment):
|
class WithEquityDailyBarData(WithTradingEnvironment):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.make_equity_daily_bar_data.
|
CatalystTestCase mixin providing cls.make_equity_daily_bar_data.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
@@ -810,7 +810,7 @@ class WithEquityDailyBarData(WithTradingEnvironment):
|
|||||||
|
|
||||||
class WithBcolzEquityDailyBarReader(WithEquityDailyBarData, WithTmpDir):
|
class WithBcolzEquityDailyBarReader(WithEquityDailyBarData, WithTmpDir):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.bcolz_daily_bar_path,
|
CatalystTestCase mixin providing cls.bcolz_daily_bar_path,
|
||||||
cls.bcolz_daily_bar_ctable, and cls.bcolz_equity_daily_bar_reader
|
cls.bcolz_daily_bar_ctable, and cls.bcolz_equity_daily_bar_reader
|
||||||
class level fixtures.
|
class level fixtures.
|
||||||
|
|
||||||
@@ -895,7 +895,7 @@ class WithBcolzEquityDailyBarReader(WithEquityDailyBarData, WithTmpDir):
|
|||||||
|
|
||||||
class WithBcolzEquityDailyBarReaderFromCSVs(WithBcolzEquityDailyBarReader):
|
class WithBcolzEquityDailyBarReaderFromCSVs(WithBcolzEquityDailyBarReader):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin that provides
|
CatalystTestCase mixin that provides
|
||||||
cls.bcolz_equity_daily_bar_reader from a mapping of sids to CSV
|
cls.bcolz_equity_daily_bar_reader from a mapping of sids to CSV
|
||||||
file paths.
|
file paths.
|
||||||
"""
|
"""
|
||||||
@@ -925,7 +925,7 @@ class _WithMinuteBarDataBase(WithTradingEnvironment):
|
|||||||
|
|
||||||
class WithEquityMinuteBarData(_WithMinuteBarDataBase):
|
class WithEquityMinuteBarData(_WithMinuteBarDataBase):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.equity_minute_bar_days.
|
CatalystTestCase mixin providing cls.equity_minute_bar_days.
|
||||||
|
|
||||||
After init_class_fixtures has been called:
|
After init_class_fixtures has been called:
|
||||||
- `cls.equity_minute_bar_days` has the range over which data has been
|
- `cls.equity_minute_bar_days` has the range over which data has been
|
||||||
@@ -984,7 +984,7 @@ class WithEquityMinuteBarData(_WithMinuteBarDataBase):
|
|||||||
|
|
||||||
class WithFutureMinuteBarData(_WithMinuteBarDataBase):
|
class WithFutureMinuteBarData(_WithMinuteBarDataBase):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.future_minute_bar_days.
|
CatalystTestCase mixin providing cls.future_minute_bar_days.
|
||||||
|
|
||||||
After init_class_fixtures has been called:
|
After init_class_fixtures has been called:
|
||||||
- `cls.future_minute_bar_days` has the range over which data has been
|
- `cls.future_minute_bar_days` has the range over which data has been
|
||||||
@@ -1044,7 +1044,7 @@ class WithFutureMinuteBarData(_WithMinuteBarDataBase):
|
|||||||
|
|
||||||
class WithBcolzEquityMinuteBarReader(WithEquityMinuteBarData, WithTmpDir):
|
class WithBcolzEquityMinuteBarReader(WithEquityMinuteBarData, WithTmpDir):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.bcolz_minute_bar_path,
|
CatalystTestCase mixin providing cls.bcolz_minute_bar_path,
|
||||||
cls.bcolz_minute_bar_ctable, and cls.bcolz_equity_minute_bar_reader
|
cls.bcolz_minute_bar_ctable, and cls.bcolz_equity_minute_bar_reader
|
||||||
class level fixtures.
|
class level fixtures.
|
||||||
|
|
||||||
@@ -1103,7 +1103,7 @@ class WithBcolzEquityMinuteBarReader(WithEquityMinuteBarData, WithTmpDir):
|
|||||||
|
|
||||||
class WithBcolzFutureMinuteBarReader(WithFutureMinuteBarData, WithTmpDir):
|
class WithBcolzFutureMinuteBarReader(WithFutureMinuteBarData, WithTmpDir):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.bcolz_minute_bar_path,
|
CatalystTestCase mixin providing cls.bcolz_minute_bar_path,
|
||||||
cls.bcolz_minute_bar_ctable, and cls.bcolz_equity_minute_bar_reader
|
cls.bcolz_minute_bar_ctable, and cls.bcolz_equity_minute_bar_reader
|
||||||
class level fixtures.
|
class level fixtures.
|
||||||
|
|
||||||
@@ -1227,7 +1227,7 @@ class WithConstantFutureMinuteBarData(WithFutureMinuteBarData):
|
|||||||
|
|
||||||
class WithAdjustmentReader(WithBcolzEquityDailyBarReader):
|
class WithAdjustmentReader(WithBcolzEquityDailyBarReader):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing cls.adjustment_reader as a class level
|
CatalystTestCase mixin providing cls.adjustment_reader as a class level
|
||||||
fixture.
|
fixture.
|
||||||
|
|
||||||
After init_class_fixtures has been called, `cls.adjustment_reader` will be
|
After init_class_fixtures has been called, `cls.adjustment_reader` will be
|
||||||
@@ -1359,7 +1359,7 @@ class WithEquityPricingPipelineEngine(WithAdjustmentReader,
|
|||||||
|
|
||||||
class WithSeededRandomPipelineEngine(WithTradingSessions, WithAssetFinder):
|
class WithSeededRandomPipelineEngine(WithTradingSessions, WithAssetFinder):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing class-level fixtures for running pipelines
|
CatalystTestCase mixin providing class-level fixtures for running pipelines
|
||||||
against deterministically-generated random data.
|
against deterministically-generated random data.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
@@ -1434,7 +1434,7 @@ class WithDataPortal(WithAdjustmentReader,
|
|||||||
WithBcolzEquityMinuteBarReader,
|
WithBcolzEquityMinuteBarReader,
|
||||||
WithBcolzFutureMinuteBarReader):
|
WithBcolzFutureMinuteBarReader):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin providing self.data_portal as an instance level
|
CatalystTestCase mixin providing self.data_portal as an instance level
|
||||||
fixture.
|
fixture.
|
||||||
|
|
||||||
After init_instance_fixtures has been called, `self.data_portal` will be
|
After init_instance_fixtures has been called, `self.data_portal` will be
|
||||||
@@ -1485,12 +1485,12 @@ class WithDataPortal(WithAdjustmentReader,
|
|||||||
self.env.asset_finder,
|
self.env.asset_finder,
|
||||||
self.trading_calendar,
|
self.trading_calendar,
|
||||||
first_trading_day=self.DATA_PORTAL_FIRST_TRADING_DAY,
|
first_trading_day=self.DATA_PORTAL_FIRST_TRADING_DAY,
|
||||||
equity_daily_reader=(
|
daily_reader=(
|
||||||
self.bcolz_equity_daily_bar_reader
|
self.bcolz_equity_daily_bar_reader
|
||||||
if self.DATA_PORTAL_USE_DAILY_DATA else
|
if self.DATA_PORTAL_USE_DAILY_DATA else
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
equity_minute_reader=(
|
minute_reader=(
|
||||||
self.bcolz_equity_minute_bar_reader
|
self.bcolz_equity_minute_bar_reader
|
||||||
if self.DATA_PORTAL_USE_MINUTE_DATA else
|
if self.DATA_PORTAL_USE_MINUTE_DATA else
|
||||||
None
|
None
|
||||||
@@ -1526,7 +1526,7 @@ class WithDataPortal(WithAdjustmentReader,
|
|||||||
|
|
||||||
class WithResponses(object):
|
class WithResponses(object):
|
||||||
"""
|
"""
|
||||||
ZiplineTestCase mixin that provides self.responses as an instance
|
CatalystTestCase mixin that provides self.responses as an instance
|
||||||
fixture.
|
fixture.
|
||||||
|
|
||||||
After init_instance_fixtures has been called, `self.responses` will be
|
After init_instance_fixtures has been called, `self.responses` will be
|
||||||
|
|||||||
@@ -31,5 +31,4 @@ class OpenExchangeCalendar(TradingCalendar):
|
|||||||
return DateOffset(days=1)
|
return DateOffset(days=1)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(OpenExchangeCalendar, self).__init__(
|
super(OpenExchangeCalendar, self).__init__(start=Timestamp('2015-3-1', tz='UTC'), **kwargs)
|
||||||
start=Timestamp('2015-3-1', tz='UTC'), **kwargs)
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ DEFAULT_BAR_TEMPLATE = ' [%(bar)s] %(label)s: %(info)s'
|
|||||||
DEFAULT_EMPTY_CHAR = ' '
|
DEFAULT_EMPTY_CHAR = ' '
|
||||||
DEFAULT_FILL_CHAR = '='
|
DEFAULT_FILL_CHAR = '='
|
||||||
|
|
||||||
|
|
||||||
def item_show_count(total=None):
|
def item_show_count(total=None):
|
||||||
def maybe_show_total(index):
|
def maybe_show_total(index):
|
||||||
if total is not None:
|
if total is not None:
|
||||||
@@ -18,13 +17,12 @@ def item_show_count(total=None):
|
|||||||
|
|
||||||
def item_show_func(item, _it=iter(count())):
|
def item_show_func(item, _it=iter(count())):
|
||||||
if item is not None:
|
if item is not None:
|
||||||
# starting = False
|
starting = False
|
||||||
return maybe_show_total(next(_it))
|
return maybe_show_total(next(_it))
|
||||||
return 'DONE'
|
return 'DONE'
|
||||||
|
|
||||||
return item_show_func
|
return item_show_func
|
||||||
|
|
||||||
|
|
||||||
def maybe_show_progress(it,
|
def maybe_show_progress(it,
|
||||||
show_progress,
|
show_progress,
|
||||||
empty_char=DEFAULT_EMPTY_CHAR,
|
empty_char=DEFAULT_EMPTY_CHAR,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from catalyst.utils.input_validation import expect_types
|
|||||||
__all__ = ['load_from_yahoo', 'load_bars_from_yahoo']
|
__all__ = ['load_from_yahoo', 'load_bars_from_yahoo']
|
||||||
|
|
||||||
|
|
||||||
def create_simulation_parameters(year=2006, start=None, end=None,
|
def create_simulation_parameters(year=2016, start=None, end=None,
|
||||||
capital_base=float("1.0e5"),
|
capital_base=float("1.0e5"),
|
||||||
num_days=None,
|
num_days=None,
|
||||||
data_frequency='daily',
|
data_frequency='daily',
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ import math
|
|||||||
|
|
||||||
from numpy import isnan
|
from numpy import isnan
|
||||||
|
|
||||||
|
|
||||||
def round_nearest(x, a):
|
def round_nearest(x, a):
|
||||||
return round(round(x / a) * a, -int(math.floor(math.log10(a))))
|
return round(round(x / a) * a, -int(math.floor(math.log10(a))))
|
||||||
|
|
||||||
|
|
||||||
def tolerant_equals(a, b, atol=10e-7, rtol=10e-7, equal_nan=False):
|
def tolerant_equals(a, b, atol=10e-7, rtol=10e-7, equal_nan=False):
|
||||||
"""Check if a and b are equal with some tolerance.
|
"""Check if a and b are equal with some tolerance.
|
||||||
|
|
||||||
|
|||||||
+40
-36
@@ -8,7 +8,6 @@ from time import sleep
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from logbook import Logger
|
|
||||||
|
|
||||||
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
|
||||||
@@ -31,16 +30,17 @@ from catalyst.utils.factory import create_simulation_parameters
|
|||||||
from catalyst.data.loader import load_crypto_market_data
|
from catalyst.data.loader import load_crypto_market_data
|
||||||
import catalyst.utils.paths as pth
|
import catalyst.utils.paths as pth
|
||||||
|
|
||||||
from catalyst.exchange.exchange_algorithm import (
|
from catalyst.exchange.exchange_algorithm import ExchangeTradingAlgorithmLive, \
|
||||||
ExchangeTradingAlgorithmLive,
|
ExchangeTradingAlgorithmBacktest
|
||||||
ExchangeTradingAlgorithmBacktest,
|
|
||||||
)
|
|
||||||
from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \
|
from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \
|
||||||
DataPortalExchangeBacktest
|
DataPortalExchangeBacktest
|
||||||
from catalyst.exchange.asset_finder_exchange import AssetFinderExchange
|
from catalyst.exchange.asset_finder_exchange import AssetFinderExchange
|
||||||
|
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
|
||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError, ExchangeRequestErrorTooManyAttempts,
|
ExchangeRequestError, ExchangeRequestErrorTooManyAttempts,
|
||||||
BaseCurrencyNotFoundError, NotEnoughCapitalError)
|
BaseCurrencyNotFoundError)
|
||||||
|
from catalyst.exchange.exchange_utils import get_algo_object
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
@@ -91,8 +91,7 @@ def _run(handle_data,
|
|||||||
algo_namespace,
|
algo_namespace,
|
||||||
base_currency,
|
base_currency,
|
||||||
live_graph,
|
live_graph,
|
||||||
simulate_orders,
|
simulate_orders):
|
||||||
stats_output):
|
|
||||||
"""Run a backtest for the given algorithm.
|
"""Run a backtest for the given algorithm.
|
||||||
|
|
||||||
This is shared between the cli and :func:`catalyst.run_algo`.
|
This is shared between the cli and :func:`catalyst.run_algo`.
|
||||||
@@ -141,8 +140,7 @@ def _run(handle_data,
|
|||||||
else:
|
else:
|
||||||
click.echo(algotext)
|
click.echo(algotext)
|
||||||
|
|
||||||
mode = 'paper-trading' if simulate_orders else 'live-trading' \
|
mode = 'live' if live else 'backtest'
|
||||||
if live else 'backtest'
|
|
||||||
log.info('running algo in {mode} mode'.format(mode=mode))
|
log.info('running algo in {mode} mode'.format(mode=mode))
|
||||||
|
|
||||||
exchange_name = exchange
|
exchange_name = exchange
|
||||||
@@ -153,10 +151,24 @@ def _run(handle_data,
|
|||||||
|
|
||||||
exchanges = dict()
|
exchanges = dict()
|
||||||
for exchange_name in exchange_list:
|
for exchange_name in exchange_list:
|
||||||
|
|
||||||
|
# Looking for the portfolio from the cache first
|
||||||
|
portfolio = get_algo_object(
|
||||||
|
algo_name=algo_namespace,
|
||||||
|
key='portfolio_{}'.format(exchange_name),
|
||||||
|
environ=environ
|
||||||
|
)
|
||||||
|
|
||||||
|
if portfolio is None:
|
||||||
|
portfolio = ExchangePortfolio(
|
||||||
|
start if start is not None else pd.Timestamp.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
exchanges[exchange_name] = get_exchange(
|
exchanges[exchange_name] = get_exchange(
|
||||||
exchange_name=exchange_name,
|
exchange_name=exchange_name,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
must_authenticate=(live and not simulate_orders),
|
portfolio=portfolio,
|
||||||
|
must_authenticate=live,
|
||||||
)
|
)
|
||||||
|
|
||||||
open_calendar = get_calendar('OPEN')
|
open_calendar = get_calendar('OPEN')
|
||||||
@@ -173,7 +185,7 @@ def _run(handle_data,
|
|||||||
asset_db_path=None # We don't need an asset db, we have exchanges
|
asset_db_path=None # We don't need an asset db, we have exchanges
|
||||||
)
|
)
|
||||||
env.asset_finder = AssetFinderExchange()
|
env.asset_finder = AssetFinderExchange()
|
||||||
choose_loader = None # TODO: use the DataPortal in the algo class for this
|
choose_loader = None # TODO: use the DataPortal for in the algorithm class for this
|
||||||
|
|
||||||
if live:
|
if live:
|
||||||
start = pd.Timestamp.utcnow()
|
start = pd.Timestamp.utcnow()
|
||||||
@@ -228,25 +240,28 @@ def _run(handle_data,
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return base_currency_available
|
if capital_base is not None \
|
||||||
|
and capital_base < base_currency_available:
|
||||||
|
log.info(
|
||||||
|
'using capital base limit: {} {}'.format(
|
||||||
|
capital_base, base_currency
|
||||||
|
)
|
||||||
|
)
|
||||||
|
amount = capital_base
|
||||||
|
else:
|
||||||
|
amount = base_currency_available
|
||||||
|
|
||||||
|
return amount
|
||||||
else:
|
else:
|
||||||
raise BaseCurrencyNotFoundError(
|
raise BaseCurrencyNotFoundError(
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
exchange=exchange_name
|
exchange=exchange_name
|
||||||
)
|
)
|
||||||
|
|
||||||
if not simulate_orders:
|
combined_capital_base = 0
|
||||||
for exchange_name in exchanges:
|
for exchange_name in exchanges:
|
||||||
exchange = exchanges[exchange_name]
|
exchange = exchanges[exchange_name]
|
||||||
balance = fetch_capital_base(exchange)
|
combined_capital_base += fetch_capital_base(exchange)
|
||||||
|
|
||||||
if balance < capital_base:
|
|
||||||
raise NotEnoughCapitalError(
|
|
||||||
exchange=exchange_name,
|
|
||||||
base_currency=base_currency,
|
|
||||||
balance=balance,
|
|
||||||
capital_base=capital_base,
|
|
||||||
)
|
|
||||||
|
|
||||||
sim_params = create_simulation_parameters(
|
sim_params = create_simulation_parameters(
|
||||||
start=start,
|
start=start,
|
||||||
@@ -264,8 +279,7 @@ def _run(handle_data,
|
|||||||
exchanges=exchanges,
|
exchanges=exchanges,
|
||||||
algo_namespace=algo_namespace,
|
algo_namespace=algo_namespace,
|
||||||
live_graph=live_graph,
|
live_graph=live_graph,
|
||||||
simulate_orders=simulate_orders,
|
simulate_orders=simulate_orders
|
||||||
stats_output=stats_output,
|
|
||||||
)
|
)
|
||||||
elif exchanges:
|
elif exchanges:
|
||||||
# Removed the existing Poloniex fork to keep things simple
|
# Removed the existing Poloniex fork to keep things simple
|
||||||
@@ -428,7 +442,6 @@ def run_algorithm(initialize,
|
|||||||
algo_namespace=None,
|
algo_namespace=None,
|
||||||
live_graph=False,
|
live_graph=False,
|
||||||
simulate_orders=True,
|
simulate_orders=True,
|
||||||
stats_output=None,
|
|
||||||
output=os.devnull):
|
output=os.devnull):
|
||||||
"""Run a trading algorithm.
|
"""Run a trading algorithm.
|
||||||
|
|
||||||
@@ -503,14 +516,6 @@ def run_algorithm(initialize,
|
|||||||
default_extension, extensions, strict_extensions, environ
|
default_extension, extensions, strict_extensions, environ
|
||||||
)
|
)
|
||||||
|
|
||||||
if capital_base is None:
|
|
||||||
raise ValueError(
|
|
||||||
'Please specify a `capital_base` parameter which is the maximum '
|
|
||||||
'amount of base currency available for trading. For example, '
|
|
||||||
'if the `capital_base` is 5ETH, the '
|
|
||||||
'`order_target_percent(asset, 1)` command will order 5ETH worth '
|
|
||||||
'of the specified asset.'
|
|
||||||
)
|
|
||||||
# I'm not sure that we need this since the modified DataPortal
|
# I'm not sure that we need this since the modified DataPortal
|
||||||
# does not require extensions to be explicitly loaded.
|
# does not require extensions to be explicitly loaded.
|
||||||
|
|
||||||
@@ -559,6 +564,5 @@ def run_algorithm(initialize,
|
|||||||
algo_namespace=algo_namespace,
|
algo_namespace=algo_namespace,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
live_graph=live_graph,
|
live_graph=live_graph,
|
||||||
simulate_orders=simulate_orders,
|
simulate_orders=simulate_orders
|
||||||
stats_output=stats_output
|
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-15805
File diff suppressed because it is too large
Load Diff
@@ -31,11 +31,6 @@ Overview
|
|||||||
`two-part video tutorial <videos.html#backtesting-a-strategy>`_ to show how
|
`two-part video tutorial <videos.html#backtesting-a-strategy>`_ to show how
|
||||||
to get started in backtesting and live trading with Catalyst.
|
to get started in backtesting and live trading with Catalyst.
|
||||||
|
|
||||||
- :ref:`Simple Universe <simple_universe>`: This code provides the 'universe'
|
|
||||||
of available trading pairs on a given exchange on any given day. You can use
|
|
||||||
this code to dynamically select which currency pairs you want to trade each
|
|
||||||
day of your strategy. This example does not make any trades.
|
|
||||||
|
|
||||||
- :ref:`Portfolio Optimization <portfolio_optimization>`: Use this code to
|
- :ref:`Portfolio Optimization <portfolio_optimization>`: Use this code to
|
||||||
execute a portfolio optimization model. This strategy will select the
|
execute a portfolio optimization model. This strategy will select the
|
||||||
portfolio with the maximum Sharpe Ratio. The parameters are set to use 180
|
portfolio with the maximum Sharpe Ratio. The parameters are set to use 180
|
||||||
@@ -758,177 +753,6 @@ implemented after the video was recorded, which executes the orders at slighlty
|
|||||||
different prices, but resulting in significant changes in performance of our
|
different prices, but resulting in significant changes in performance of our
|
||||||
strategy.
|
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
|
|
||||||
collect data from any given exchange and select a subset of the available
|
|
||||||
currency pairs for trading. You simply need to specify the exchange and
|
|
||||||
the market (base_currency) that you want to focus on. You will then see
|
|
||||||
how to create a universe of assets, and filter it based the market you
|
|
||||||
desire.
|
|
||||||
|
|
||||||
The example prints out the closing price of all the pairs for a given
|
|
||||||
market in a given exchange every 30 minutes. The example also contains
|
|
||||||
the OHLCV data with minute-resolution for the past seven days which
|
|
||||||
could be used to create indicators. Use this code as the backbone to
|
|
||||||
create your own trading strategy.
|
|
||||||
|
|
||||||
The lookback_date variable is used to ensure data for a coin existed on
|
|
||||||
the lookback period specified.
|
|
||||||
|
|
||||||
To run, execute the following two commands in a terminal (inside catalyst
|
|
||||||
environment). The first one retrieves all the pricing data needed for this
|
|
||||||
script to run (only needs to be run once), and the second one executes this
|
|
||||||
script with the parameters specified in the run_algorithm() call at the end
|
|
||||||
of the file:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
catalyst ingest-exchange -x bitfinex -f minute
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python simple_universe.py
|
|
||||||
|
|
||||||
Credits: This code was originally submitted by `Abner Ayala-Acevedo
|
|
||||||
<https://github.com/abnera>`_. Thank you!
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols
|
|
||||||
from catalyst.api import (symbols, )
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.i = -1 # minute counter
|
|
||||||
context.exchange = context.exchanges.values()[0].name.lower()
|
|
||||||
context.base_currency = context.exchanges.values()[0].base_currency.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
context.i += 1
|
|
||||||
lookback_days = 7 # 7 days
|
|
||||||
|
|
||||||
# current date & time in each iteration formatted into a string
|
|
||||||
now = data.current_dt
|
|
||||||
date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ')
|
|
||||||
lookback_date = now - timedelta(days=lookback_days)
|
|
||||||
# keep only the date as a string, discard the time
|
|
||||||
lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0]
|
|
||||||
|
|
||||||
one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute'
|
|
||||||
# update universe everyday at midnight
|
|
||||||
if not context.i % one_day_in_minutes:
|
|
||||||
context.universe = universe(context, lookback_date, date)
|
|
||||||
|
|
||||||
# get data every 30 minutes
|
|
||||||
minutes = 30
|
|
||||||
# get lookback_days of history data: that is 'lookback' number of bins
|
|
||||||
lookback = one_day_in_minutes / minutes * lookback_days
|
|
||||||
if not context.i % minutes and context.universe:
|
|
||||||
# we iterate for every pair in the current universe
|
|
||||||
for coin in context.coins:
|
|
||||||
pair = str(coin.symbol)
|
|
||||||
|
|
||||||
# Get 30 minute interval OHLCV data. This is the standard data
|
|
||||||
# required for candlestick or indicators/signals. Return Pandas
|
|
||||||
# DataFrames. 30T means 30-minute re-sampling of one minute data.
|
|
||||||
# Adjust it to your desired time interval as needed.
|
|
||||||
opened = fill(data.history(coin, 'open',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
high = fill(data.history(coin, 'high',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
low = fill(data.history(coin, 'low',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
close = fill(data.history(coin, 'price',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
volume = fill(data.history(coin, 'volume',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
|
|
||||||
# close[-1] is the last value in the set, which is the equivalent
|
|
||||||
# to current price (as in the most recent value)
|
|
||||||
# displays the minute price for each pair every 30 minutes
|
|
||||||
print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},\tV:{v}'.format(
|
|
||||||
now=now,
|
|
||||||
pair=pair,
|
|
||||||
o=opened[-1],
|
|
||||||
h=high[-1],
|
|
||||||
l=low[-1],
|
|
||||||
c=close[-1],
|
|
||||||
v=volume[-1],
|
|
||||||
))
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
# --------------- Insert Your Strategy Here -------------------
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Get the universe for a given exchange and a given base_currency market
|
|
||||||
# Example: Poloniex BTC Market
|
|
||||||
def universe(context, lookback_date, current_date):
|
|
||||||
# get all the pairs for the given exchange
|
|
||||||
json_symbols = get_exchange_symbols(context.exchange)
|
|
||||||
# convert into a DataFrame for easier processing
|
|
||||||
df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str)
|
|
||||||
df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],axis=1)
|
|
||||||
df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],axis=1)
|
|
||||||
|
|
||||||
# Filter all the pairs to get only the ones for a given base_currency
|
|
||||||
df = df[df['base_currency'] == context.base_currency]
|
|
||||||
|
|
||||||
# Filter all the pairs to ensure that pair existed in the current date range
|
|
||||||
df = df[df.start_date < lookback_date]
|
|
||||||
df = df[df.end_daily >= current_date]
|
|
||||||
context.coins = symbols(*df.symbol) # convert all the pairs to symbols
|
|
||||||
|
|
||||||
return df.symbol.tolist()
|
|
||||||
|
|
||||||
|
|
||||||
# Replace all NA, NAN or infinite values with its nearest value
|
|
||||||
def fill(series):
|
|
||||||
if isinstance(series, pd.Series):
|
|
||||||
return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
|
|
||||||
elif isinstance(series, np.ndarray):
|
|
||||||
return pd.Series(series).replace(
|
|
||||||
[np.inf, -np.inf], np.nan
|
|
||||||
).ffill().bfill().values
|
|
||||||
else:
|
|
||||||
return series
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
start_date = pd.to_datetime('2017-11-10', utc=True)
|
|
||||||
end_date = pd.to_datetime('2017-11-13', utc=True)
|
|
||||||
|
|
||||||
performance = run_algorithm(start=start_date, end=end_date,
|
|
||||||
capital_base=100.0, # amount of base_currency
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
data_frequency='minute',
|
|
||||||
base_currency='btc',
|
|
||||||
live=False,
|
|
||||||
live_graph=False,
|
|
||||||
algo_namespace='simple_universe')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _portfolio_optimization:
|
.. _portfolio_optimization:
|
||||||
|
|
||||||
Portfolio Optimization
|
Portfolio Optimization
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.. include:: ../../README.rst
|
.. include:: welcome.rst
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
@@ -9,8 +9,9 @@ Table of Contents
|
|||||||
|
|
||||||
install
|
install
|
||||||
beginner-tutorial
|
beginner-tutorial
|
||||||
|
jupyter
|
||||||
live-trading
|
live-trading
|
||||||
features
|
naming-convention
|
||||||
example-algos
|
example-algos
|
||||||
utilities
|
utilities
|
||||||
videos
|
videos
|
||||||
@@ -18,6 +19,7 @@ Table of Contents
|
|||||||
development-guidelines
|
development-guidelines
|
||||||
releases
|
releases
|
||||||
.. bundles
|
.. bundles
|
||||||
|
.. development-guidelines
|
||||||
.. appendix
|
.. appendix
|
||||||
.. release-process
|
.. release-process
|
||||||
|
|
||||||
|
|||||||
+15794
File diff suppressed because it is too large
Load Diff
@@ -106,10 +106,6 @@ What differs are the arguments provided to the catalyst client or
|
|||||||
Here is the breakdown of the new arguments:
|
Here is the breakdown of the new arguments:
|
||||||
|
|
||||||
- ``live``: Boolean flag which enables live trading.
|
- ``live``: Boolean flag which enables live trading.
|
||||||
- ``capital_base``: The amount of base_currency assigned to the strategy.
|
|
||||||
It has to be lower or equal to the amount of base currency available for
|
|
||||||
trading on the exchange. For illustration, order_target_percent(asset, 1)
|
|
||||||
will order the capital_base amount specified here of the specified asset.
|
|
||||||
- ``exchange_name``: The name of the targeted exchange
|
- ``exchange_name``: The name of the targeted exchange
|
||||||
(supported values: *bitfinex*, *bittrex*).
|
(supported values: *bitfinex*, *bittrex*).
|
||||||
- ``algo_namespace``: A arbitrary label assigned to your algorithm for
|
- ``algo_namespace``: A arbitrary label assigned to your algorithm for
|
||||||
@@ -117,8 +113,6 @@ Here is the breakdown of the new arguments:
|
|||||||
- ``base_currency``: The base currency used to calculate the
|
- ``base_currency``: The base currency used to calculate the
|
||||||
statistics of your algorithm. Currently, the base currency of all
|
statistics of your algorithm. Currently, the base currency of all
|
||||||
trading pairs of your algorithm must match this value.
|
trading pairs of your algorithm must match this value.
|
||||||
- ``simulate_orders``: Enables the paper trading mode, in which orders are
|
|
||||||
simulated in Catalyst instead of processed on the exchange.
|
|
||||||
|
|
||||||
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>`_
|
||||||
|
|||||||
@@ -1,61 +1,5 @@
|
|||||||
Features
|
|
||||||
========
|
|
||||||
|
|
||||||
This page describes the features that Catalyst provides in the current version,
|
|
||||||
and what is planned for future releases.
|
|
||||||
|
|
||||||
Current Functionality
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* Backtesting and live-trading modes to run your trading algorithms, with a
|
|
||||||
seamless transition between the two.
|
|
||||||
* Paper trading simulates order in live-trading mode.
|
|
||||||
* Support for 3 exchanges: Bitfinex, Bittrex and Poloniex in both modes
|
|
||||||
(backtesting and live-trading). Historical data for backtesting is provided
|
|
||||||
with daily resolution for all three exchanges, and minute resolution for
|
|
||||||
Bitfinex and Poloniex. No minute-resolution data is currently available for
|
|
||||||
Bittrex. Refer to
|
|
||||||
`Catalyst Market Coverage <https://www.enigma.co/catalyst/status>`_ for
|
|
||||||
details.
|
|
||||||
* Interface with over 90 exchanges available in live and paper trading modes.
|
|
||||||
* Granular commission models which closely simulates each exchange fee
|
|
||||||
structure in backtesting and paper trading.
|
|
||||||
* Standardized naming convention for all asset pairs trading on any exchange in
|
|
||||||
the form ``{market_currency}_{base_currency}``. See
|
|
||||||
:ref:`naming`.
|
|
||||||
* Output of performance statistics based on Pandas DataFrames to integrate
|
|
||||||
nicely into the existing PyData ecosystem.
|
|
||||||
* Support for accessing multiple exchanges per algorithm, which opens the door
|
|
||||||
to cross-exchange arbitrage opportunities.
|
|
||||||
* Support for running multiple algorithms on the same exchange independently of
|
|
||||||
one another. Catalyst performance tracker stores just enough data to allow
|
|
||||||
algorithms to run independently while still sharing critical data through
|
|
||||||
exchanges.
|
|
||||||
* Benchmark defaults to Bitcoin price (btc_usdt in Poloniex exchange) for the
|
|
||||||
purpose of comparing performance across trading algorithms. A custom benchmark
|
|
||||||
can be specified through ``set_benchmark()`` (but see
|
|
||||||
`issue #86 <https://github.com/enigmampc/catalyst/issues/86>`_).
|
|
||||||
* Support for MacOS, Linux and Windows installations.
|
|
||||||
* Support for Python2 and Python3.
|
|
||||||
|
|
||||||
For additional details on the functionality added on recent releases, see the
|
|
||||||
:doc:`Release Notes<releases>`.
|
|
||||||
|
|
||||||
Upcoming features
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* Additional datasets beyond pricing data (Dec. 2017)
|
|
||||||
* API documentation (Jan. 2017)
|
|
||||||
* Support for decentralized exchanges (Jan. 2017)
|
|
||||||
* Support for data ingestion of community-contributed data sets (Jan. 2017)
|
|
||||||
* Pipeline support (Jan. 2018)
|
|
||||||
* Web UI (Q2 2018)
|
|
||||||
|
|
||||||
|
|
||||||
.. _naming:
|
|
||||||
|
|
||||||
Naming Convention
|
Naming Convention
|
||||||
~~~~~~~~~~~~~~~~~
|
=================
|
||||||
|
|
||||||
Catalyst introduces a standardized naming convention for all asset pairs
|
Catalyst introduces a standardized naming convention for all asset pairs
|
||||||
trading on any exchange in the following form:
|
trading on any exchange in the following form:
|
||||||
@@ -2,15 +2,6 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Version 0.3.10
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
**Release Date**: 2017-12-12
|
|
||||||
|
|
||||||
Bug Fixes
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
- Fixed issue with fetching assets with daily frequency
|
|
||||||
|
|
||||||
Version 0.3.10
|
Version 0.3.10
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
**Release Date**: 2017-11-28
|
**Release Date**: 2017-11-28
|
||||||
@@ -19,16 +10,6 @@ Bug Fixes
|
|||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
- Fixed issue with fetching assets with daily frequency
|
- Fixed issue with fetching assets with daily frequency
|
||||||
- Changed Poloniex interface (should solve :issue:`95` and :issue:`94`)
|
|
||||||
- Solved issue with overriding commission and slippage (:issue:`87`)
|
|
||||||
- Fixed inefficiency with Bittrex current prices (:issue:`76`)
|
|
||||||
|
|
||||||
Build
|
|
||||||
~~~~~
|
|
||||||
- Integrated with CCXT
|
|
||||||
- Added paper trading capability (`simulate_orders=True` param in live mode)
|
|
||||||
- More granular commissions (:issue:`82`)
|
|
||||||
- Added market orders in live mode (:issue:`81`)
|
|
||||||
|
|
||||||
Version 0.3.9
|
Version 0.3.9
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
|
||||||
|
|
|
||||||
|
Catalyst is an algorithmic trading library for crypto-assets written in Python.
|
||||||
|
It allows trading strategies to be easily expressed and backtested against
|
||||||
|
historical data (with daily and minute resolution), providing analytics and
|
||||||
|
insights regarding a particular strategy's performance. Catalyst also supports
|
||||||
|
live-trading of crypto-assets starting with three exchanges (Bitfinex, Bittrex,
|
||||||
|
and Poloniex) with more being added over time. Catalyst empowers users to share
|
||||||
|
and curate data and build profitable, data-driven investment strategies. Please
|
||||||
|
visit `enigma.co <https://www.enigma.co>`_ to learn more about Catalyst, or
|
||||||
|
refer to the `whitepaper <https://www.enigma.co/enigma_catalyst.pdf>`_ for
|
||||||
|
further technical details.
|
||||||
|
|
||||||
|
Catalyst builds on top of the well-established
|
||||||
|
`Zipline <https://github.com/quantopian/zipline>`_ project. We did our best to
|
||||||
|
minimize structural changes to the general API to maximize compatibility with
|
||||||
|
existing trading algorithms, developer knowledge, and tutorials. Join us on
|
||||||
|
`Discord <https://discord.gg/SJK32GY>`_ where we have a *#catalyst_dev* channel
|
||||||
|
for questions around Catalyst, algorithmic trading and technical support.
|
||||||
|
|
||||||
|
Features
|
||||||
|
========
|
||||||
|
|
||||||
|
- Ease of use: Catalyst tries to get out of your way so that you can
|
||||||
|
focus on algorithm development. See
|
||||||
|
`examples of trading strategies <https://github.com/enigmampc/catalyst/tree/master/catalyst/examples>`_
|
||||||
|
provided.
|
||||||
|
- Support for several of the top crypto-exchanges by trading volume:
|
||||||
|
`Bitfinex <https://www.bitfinex.com>`_, `Bittrex <http://www.bittrex.com>`_,
|
||||||
|
and `Poloniex <https://www.poloniex.com>`_.
|
||||||
|
- Secure: You and only you have access to each exchange API keys for your accounts.
|
||||||
|
- Input of historical pricing data of all crypto-assets by exchange,
|
||||||
|
with daily and minute resolution. See
|
||||||
|
`Catalyst Market Coverage Overview <https://www.enigma.co/catalyst/status>`_.
|
||||||
|
- Backtesting and live-trading functionality, with a seamless transition
|
||||||
|
between the two modes.
|
||||||
|
- Output of performance statistics are based on Pandas DataFrames to
|
||||||
|
integrate nicely into the existing PyData eco-system.
|
||||||
|
- Statistic and machine learning libraries like matplotlib, scipy,
|
||||||
|
statsmodels, and sklearn support development, analysis, and
|
||||||
|
visualization of state-of-the-art trading systems.
|
||||||
|
- Addition of Bitcoin price (btc_usdt) as a benchmark for comparing
|
||||||
|
performance across trading algorithms.
|
||||||
@@ -105,7 +105,7 @@ None
|
|||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
* Adds :class:`~zipline.testing.fixtures.ZiplineTestCase` which provides hooks
|
* Adds :class:`~zipline.testing.fixtures.CatalystTestCase` which provides hooks
|
||||||
to consume test fixtures. Fixtures are things like:
|
to consume test fixtures. Fixtures are things like:
|
||||||
:class:`~zipline.testing.fixtures.WithAssetFinder` which will make
|
:class:`~zipline.testing.fixtures.WithAssetFinder` which will make
|
||||||
``self.asset_finder`` available to your test with some mock data
|
``self.asset_finder`` available to your test with some mock data
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ 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.319
|
|
||||||
- click==6.7
|
- click==6.7
|
||||||
- contextlib2==0.5.5
|
- contextlib2==0.5.5
|
||||||
- cycler==0.10.0
|
- cycler==0.10.0
|
||||||
|
|||||||
@@ -81,5 +81,5 @@ empyrical==0.2.1
|
|||||||
tables==3.3.0
|
tables==3.3.0
|
||||||
|
|
||||||
#Catalyst dependencies
|
#Catalyst dependencies
|
||||||
ccxt==1.10.283
|
ccxt==1.10.251
|
||||||
boto3==1.4.8
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ from catalyst.errors import (
|
|||||||
CyclicCalendarAlias,
|
CyclicCalendarAlias,
|
||||||
InvalidCalendarName,
|
InvalidCalendarName,
|
||||||
)
|
)
|
||||||
from catalyst.testing import ZiplineTestCase
|
from catalyst.testing import CatalystTestCase
|
||||||
from catalyst.utils.calendars.calendar_utils import TradingCalendarDispatcher
|
from catalyst.utils.calendars.calendar_utils import TradingCalendarDispatcher
|
||||||
from catalyst.utils.calendars.exchange_calendar_ice import ICEExchangeCalendar
|
from catalyst.utils.calendars.exchange_calendar_ice import ICEExchangeCalendar
|
||||||
|
|
||||||
|
|
||||||
class CalendarAliasTestCase(ZiplineTestCase):
|
class CalendarAliasTestCase(CatalystTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init_class_fixtures(cls):
|
def init_class_fixtures(cls):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from catalyst.testing import (
|
|||||||
subtest,
|
subtest,
|
||||||
str_to_seconds,
|
str_to_seconds,
|
||||||
)
|
)
|
||||||
from catalyst.testing.fixtures import WithInstanceTmpDir, ZiplineTestCase, \
|
from catalyst.testing.fixtures import WithInstanceTmpDir, CatalystTestCase, \
|
||||||
WithDefaultDateBounds
|
WithDefaultDateBounds
|
||||||
from catalyst.testing.predicates import (
|
from catalyst.testing.predicates import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
@@ -45,7 +45,7 @@ _1_ns = pd.Timedelta(1, unit='ns')
|
|||||||
|
|
||||||
class BundleCoreTestCase(WithInstanceTmpDir,
|
class BundleCoreTestCase(WithInstanceTmpDir,
|
||||||
WithDefaultDateBounds,
|
WithDefaultDateBounds,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
START_DATE = pd.Timestamp('2014-01-06', tz='utc')
|
START_DATE = pd.Timestamp('2014-01-06', tz='utc')
|
||||||
END_DATE = pd.Timestamp('2014-01-10', tz='utc')
|
END_DATE = pd.Timestamp('2014-01-10', tz='utc')
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ from catalyst.testing import (
|
|||||||
tmp_dir,
|
tmp_dir,
|
||||||
patch_read_csv,
|
patch_read_csv,
|
||||||
)
|
)
|
||||||
from catalyst.testing.fixtures import ZiplineTestCase
|
from catalyst.testing.fixtures import CatalystTestCase
|
||||||
from catalyst.testing.predicates import (
|
from catalyst.testing.predicates import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
)
|
)
|
||||||
from catalyst.utils.functional import apply
|
from catalyst.utils.functional import apply
|
||||||
|
|
||||||
|
|
||||||
class QuandlBundleTestCase(ZiplineTestCase):
|
class QuandlBundleTestCase(CatalystTestCase):
|
||||||
symbols = 'AAPL', 'BRK_A', 'MSFT', 'ZEN'
|
symbols = 'AAPL', 'BRK_A', 'MSFT', 'ZEN'
|
||||||
asset_start = pd.Timestamp('2014-01', tz='utc')
|
asset_start = pd.Timestamp('2014-01', tz='utc')
|
||||||
asset_end = pd.Timestamp('2015-01', tz='utc')
|
asset_end = pd.Timestamp('2015-01', tz='utc')
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ from catalyst.data.bundles.core import _make_bundle_core
|
|||||||
from catalyst.data.bundles import yahoo_equities
|
from catalyst.data.bundles import yahoo_equities
|
||||||
from catalyst.lib.adjustment import Float64Multiply
|
from catalyst.lib.adjustment import Float64Multiply
|
||||||
from catalyst.testing import test_resource_path, tmp_dir, read_compressed
|
from catalyst.testing import test_resource_path, tmp_dir, read_compressed
|
||||||
from catalyst.testing.fixtures import WithResponses, ZiplineTestCase
|
from catalyst.testing.fixtures import WithResponses, CatalystTestCase
|
||||||
from catalyst.testing.predicates import assert_equal
|
from catalyst.testing.predicates import assert_equal
|
||||||
from catalyst.utils.calendars import get_calendar
|
from catalyst.utils.calendars import get_calendar
|
||||||
|
|
||||||
|
|
||||||
class YahooBundleTestCase(WithResponses, ZiplineTestCase):
|
class YahooBundleTestCase(WithResponses, CatalystTestCase):
|
||||||
symbols = 'AAPL', 'IBM', 'MSFT'
|
symbols = 'AAPL', 'IBM', 'MSFT'
|
||||||
columns = 'open', 'high', 'low', 'close', 'volume'
|
columns = 'open', 'high', 'low', 'close', 'volume'
|
||||||
asset_start = pd.Timestamp('2014-01-02', tz='utc')
|
asset_start = pd.Timestamp('2014-01-02', tz='utc')
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ZIPLINE legacy test: Catalyst does not use DispatchBarReader, and thus
|
||||||
|
# this test suite is irrelevant, and is commented out in its entirety
|
||||||
|
|
||||||
from numpy import array, nan
|
from numpy import array, nan
|
||||||
from numpy.testing import assert_almost_equal
|
from numpy.testing import assert_almost_equal
|
||||||
from pandas import DataFrame, Timestamp
|
from pandas import DataFrame, Timestamp
|
||||||
@@ -31,7 +37,7 @@ from catalyst.testing.fixtures import (
|
|||||||
WithBcolzEquityDailyBarReader,
|
WithBcolzEquityDailyBarReader,
|
||||||
WithBcolzFutureMinuteBarReader,
|
WithBcolzFutureMinuteBarReader,
|
||||||
WithTradingSessions,
|
WithTradingSessions,
|
||||||
ZiplineTestCase,
|
CatalystTestCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
OHLC = ['open', 'high', 'low', 'close']
|
OHLC = ['open', 'high', 'low', 'close']
|
||||||
@@ -40,7 +46,7 @@ OHLC = ['open', 'high', 'low', 'close']
|
|||||||
class AssetDispatchSessionBarTestCase(WithBcolzEquityDailyBarReader,
|
class AssetDispatchSessionBarTestCase(WithBcolzEquityDailyBarReader,
|
||||||
WithBcolzFutureMinuteBarReader,
|
WithBcolzFutureMinuteBarReader,
|
||||||
WithTradingSessions,
|
WithTradingSessions,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
||||||
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
||||||
@@ -169,7 +175,7 @@ class AssetDispatchSessionBarTestCase(WithBcolzEquityDailyBarReader,
|
|||||||
|
|
||||||
class AssetDispatchMinuteBarTestCase(WithBcolzEquityMinuteBarReader,
|
class AssetDispatchMinuteBarTestCase(WithBcolzEquityMinuteBarReader,
|
||||||
WithBcolzFutureMinuteBarReader,
|
WithBcolzFutureMinuteBarReader,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
||||||
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
||||||
@@ -330,3 +336,4 @@ class AssetDispatchMinuteBarTestCase(WithBcolzEquityMinuteBarReader,
|
|||||||
for i, (sid, expected, msg) in enumerate(expected_per_sid):
|
for i, (sid, expected, msg) in enumerate(expected_per_sid):
|
||||||
for j, result in enumerate(results):
|
for j, result in enumerate(results):
|
||||||
assert_almost_equal(result[:, i], expected[j], err_msg=msg)
|
assert_almost_equal(result[:, i], expected[j], err_msg=msg)
|
||||||
|
'''
|
||||||
@@ -38,8 +38,8 @@ from pandas import (
|
|||||||
from catalyst.data.bar_reader import NoDataOnDate
|
from catalyst.data.bar_reader import NoDataOnDate
|
||||||
from catalyst.data.minute_bars import (
|
from catalyst.data.minute_bars import (
|
||||||
BcolzMinuteBarMetadata,
|
BcolzMinuteBarMetadata,
|
||||||
BcolzMinuteBarWriter,
|
# BcolzMinuteBarWriter,
|
||||||
BcolzMinuteBarReader,
|
# BcolzMinuteBarReader,
|
||||||
BcolzMinuteOverlappingData,
|
BcolzMinuteOverlappingData,
|
||||||
US_EQUITIES_MINUTES_PER_DAY,
|
US_EQUITIES_MINUTES_PER_DAY,
|
||||||
BcolzMinuteWriterColumnMismatch,
|
BcolzMinuteWriterColumnMismatch,
|
||||||
@@ -47,24 +47,29 @@ from catalyst.data.minute_bars import (
|
|||||||
H5MinuteBarUpdateReader,
|
H5MinuteBarUpdateReader,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from catalyst.exchange.exchange_bcolz import (
|
||||||
|
BcolzExchangeBarWriter,
|
||||||
|
BcolzExchangeBarReader,
|
||||||
|
)
|
||||||
|
|
||||||
from catalyst.testing.fixtures import (
|
from catalyst.testing.fixtures import (
|
||||||
WithAssetFinder,
|
WithAssetFinder,
|
||||||
WithInstanceTmpDir,
|
WithInstanceTmpDir,
|
||||||
WithTradingCalendars,
|
WithTradingCalendars,
|
||||||
ZiplineTestCase,
|
CatalystTestCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calendar is set to cover several half days, to check a case where half
|
# Calendar is set to cover several half days, to check a case where half
|
||||||
# days would be read out of order in cases of windows which spanned over
|
# days would be read out of order in cases of windows which spanned over
|
||||||
# multiple half days.
|
# multiple half days.
|
||||||
TEST_CALENDAR_START = Timestamp('2014-06-02', tz='UTC')
|
TEST_CALENDAR_START = Timestamp('2015-06-02', tz='UTC')
|
||||||
TEST_CALENDAR_STOP = Timestamp('2015-12-31', tz='UTC')
|
TEST_CALENDAR_STOP = Timestamp('2016-12-31', tz='UTC')
|
||||||
|
|
||||||
|
|
||||||
class BcolzMinuteBarTestCase(WithTradingCalendars,
|
class BcolzMinuteBarTestCase(WithTradingCalendars,
|
||||||
WithAssetFinder,
|
WithAssetFinder,
|
||||||
WithInstanceTmpDir,
|
WithInstanceTmpDir,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
ASSET_FINDER_EQUITY_SIDS = 1, 2
|
ASSET_FINDER_EQUITY_SIDS = 1, 2
|
||||||
|
|
||||||
@@ -87,14 +92,14 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
|
|
||||||
self.dest = self.instance_tmpdir.getpath('minute_bars')
|
self.dest = self.instance_tmpdir.getpath('minute_bars')
|
||||||
os.makedirs(self.dest)
|
os.makedirs(self.dest)
|
||||||
self.writer = BcolzMinuteBarWriter(
|
self.writer = BcolzExchangeBarWriter(
|
||||||
self.dest,
|
rootdir=self.dest,
|
||||||
self.trading_calendar,
|
calendar=self.trading_calendar,
|
||||||
TEST_CALENDAR_START,
|
start_session=TEST_CALENDAR_START,
|
||||||
TEST_CALENDAR_STOP,
|
end_session=TEST_CALENDAR_STOP,
|
||||||
US_EQUITIES_MINUTES_PER_DAY,
|
data_frequency='minute',
|
||||||
)
|
)
|
||||||
self.reader = BcolzMinuteBarReader(self.dest)
|
self.reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
metadata = self.reader._get_metadata()
|
metadata = self.reader._get_metadata()
|
||||||
@@ -152,7 +157,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create a new writer with `ohlc_ratios_per_sid` defined.
|
# Create a new writer with `ohlc_ratios_per_sid` defined.
|
||||||
writer_with_ratios = BcolzMinuteBarWriter(
|
writer_with_ratios = BcolzExchangeBarWriter(
|
||||||
self.dest,
|
self.dest,
|
||||||
self.trading_calendar,
|
self.trading_calendar,
|
||||||
TEST_CALENDAR_START,
|
TEST_CALENDAR_START,
|
||||||
@@ -161,7 +166,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
ohlc_ratios_per_sid={sid: 25},
|
ohlc_ratios_per_sid={sid: 25},
|
||||||
)
|
)
|
||||||
writer_with_ratios.write_sid(sid, data)
|
writer_with_ratios.write_sid(sid, data)
|
||||||
reader = BcolzMinuteBarReader(self.dest)
|
reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
open_price = reader.get_value(sid, minute, 'open')
|
open_price = reader.get_value(sid, minute, 'open')
|
||||||
self.assertEquals(10.0, open_price)
|
self.assertEquals(10.0, open_price)
|
||||||
@@ -449,7 +454,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
# of appending new days will be writing to an existing directory.
|
# of appending new days will be writing to an existing directory.
|
||||||
cday = self.trading_calendar.schedule.index.freq
|
cday = self.trading_calendar.schedule.index.freq
|
||||||
new_end_session = TEST_CALENDAR_STOP + cday
|
new_end_session = TEST_CALENDAR_STOP + cday
|
||||||
writer = BcolzMinuteBarWriter.open(self.dest, new_end_session)
|
writer = BcolzExchangeBarWriter.open(self.dest, new_end_session)
|
||||||
next_day_minute = dt + cday
|
next_day_minute = dt + cday
|
||||||
new_data = DataFrame(
|
new_data = DataFrame(
|
||||||
data=ohlcv,
|
data=ohlcv,
|
||||||
@@ -457,7 +462,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
writer.write_sid(sid, new_data)
|
writer.write_sid(sid, new_data)
|
||||||
|
|
||||||
# Get a new reader to test updated calendar.
|
# Get a new reader to test updated calendar.
|
||||||
reader = BcolzMinuteBarReader(self.dest)
|
reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
second_minute = dt + Timedelta(minutes=1)
|
second_minute = dt + Timedelta(minutes=1)
|
||||||
|
|
||||||
@@ -802,7 +807,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
index=minutes)
|
index=minutes)
|
||||||
self.writer.write_sid(sids[1], data_2)
|
self.writer.write_sid(sids[1], data_2)
|
||||||
|
|
||||||
reader = BcolzMinuteBarReader(self.dest)
|
reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
columns = ['open', 'high', 'low', 'close', 'volume']
|
columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
sids = [sids[0], sids[1]]
|
sids = [sids[0], sids[1]]
|
||||||
@@ -854,7 +859,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
index=minutes)
|
index=minutes)
|
||||||
self.writer.write_sid(sids[1], data_2)
|
self.writer.write_sid(sids[1], data_2)
|
||||||
|
|
||||||
reader = BcolzMinuteBarReader(self.dest)
|
reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
columns = ['open', 'high', 'low', 'close', 'volume']
|
columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
sids = [sids[0], sids[1]]
|
sids = [sids[0], sids[1]]
|
||||||
@@ -877,6 +882,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
assert_almost_equal(data[sid].loc[minutes, col],
|
assert_almost_equal(data[sid].loc[minutes, col],
|
||||||
arrays[i][j][minute_locs])
|
arrays[i][j][minute_locs])
|
||||||
|
|
||||||
|
'''
|
||||||
def test_adjust_non_trading_minutes(self):
|
def test_adjust_non_trading_minutes(self):
|
||||||
start_day = Timestamp('2015-06-01', tz='UTC')
|
start_day = Timestamp('2015-06-01', tz='UTC')
|
||||||
end_day = Timestamp('2015-06-02', tz='UTC')
|
end_day = Timestamp('2015-06-02', tz='UTC')
|
||||||
@@ -922,7 +928,9 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
Timestamp('2015-06-02 20:01:00', tz='UTC'),
|
Timestamp('2015-06-02 20:01:00', tz='UTC'),
|
||||||
'open'
|
'open'
|
||||||
)
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
def test_adjust_non_trading_minutes_half_days(self):
|
def test_adjust_non_trading_minutes_half_days(self):
|
||||||
# half day
|
# half day
|
||||||
start_day = Timestamp('2015-11-27', tz='UTC')
|
start_day = Timestamp('2015-11-27', tz='UTC')
|
||||||
@@ -978,6 +986,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
Timestamp('2015-11-30 21:01:00', tz='UTC'),
|
Timestamp('2015-11-30 21:01:00', tz='UTC'),
|
||||||
'open'
|
'open'
|
||||||
)
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
def test_set_sid_attrs(self):
|
def test_set_sid_attrs(self):
|
||||||
"""Confirm that we can set the attributes of a sid's file correctly.
|
"""Confirm that we can set the attributes of a sid's file correctly.
|
||||||
@@ -1023,13 +1032,13 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
|
|
||||||
# Open a new writer to cover `open` method, also truncating only
|
# Open a new writer to cover `open` method, also truncating only
|
||||||
# applies to an existing directory.
|
# applies to an existing directory.
|
||||||
writer = BcolzMinuteBarWriter.open(self.dest)
|
writer = BcolzExchangeBarWriter.open(self.dest)
|
||||||
|
|
||||||
# Truncate to first day with data.
|
# Truncate to first day with data.
|
||||||
writer.truncate(days[0])
|
writer.truncate(days[0])
|
||||||
|
|
||||||
# Refresh the reader since truncate update the metadata.
|
# Refresh the reader since truncate update the metadata.
|
||||||
self.reader = BcolzMinuteBarReader(self.dest)
|
self.reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
self.assertEqual(self.writer.last_date_in_output_for_sid(sid), days[0])
|
self.assertEqual(self.writer.last_date_in_output_for_sid(sid), days[0])
|
||||||
|
|
||||||
@@ -1087,7 +1096,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
self.writer.truncate(self.test_calendar_start)
|
self.writer.truncate(self.test_calendar_start)
|
||||||
|
|
||||||
# Refresh the reader since truncate update the metadata.
|
# Refresh the reader since truncate update the metadata.
|
||||||
self.reader = BcolzMinuteBarReader(self.dest)
|
self.reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.writer.last_date_in_output_for_sid(sid),
|
self.writer.last_date_in_output_for_sid(sid),
|
||||||
@@ -1198,7 +1207,7 @@ class BcolzMinuteBarTestCase(WithTradingCalendars,
|
|||||||
self.writer.write(update_reader.read(minutes, sids))
|
self.writer.write(update_reader.read(minutes, sids))
|
||||||
|
|
||||||
# Refresh the reader since truncate update the metadata.
|
# Refresh the reader since truncate update the metadata.
|
||||||
reader = BcolzMinuteBarReader(self.dest)
|
reader = BcolzExchangeBarReader(self.dest)
|
||||||
|
|
||||||
columns = ['open', 'high', 'low', 'close', 'volume']
|
columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
sids = [sids[0], sids[1]]
|
sids = [sids[0], sids[1]]
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from catalyst.testing.fixtures import (
|
|||||||
WithBcolzEquityMinuteBarReader,
|
WithBcolzEquityMinuteBarReader,
|
||||||
WithBcolzEquityDailyBarReader,
|
WithBcolzEquityDailyBarReader,
|
||||||
WithBcolzFutureMinuteBarReader,
|
WithBcolzFutureMinuteBarReader,
|
||||||
ZiplineTestCase,
|
CatalystTestCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
OHLC = ['open', 'high', 'low', 'close']
|
OHLC = ['open', 'high', 'low', 'close']
|
||||||
@@ -254,7 +254,7 @@ EXPECTED_SESSIONS = {
|
|||||||
|
|
||||||
class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader,
|
class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader,
|
||||||
WithBcolzFutureMinuteBarReader,
|
WithBcolzFutureMinuteBarReader,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
# March 2016
|
# March 2016
|
||||||
# Su Mo Tu We Th Fr Sa
|
# Su Mo Tu We Th Fr Sa
|
||||||
@@ -525,7 +525,7 @@ class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader,
|
|||||||
|
|
||||||
|
|
||||||
class TestMinuteToSession(WithEquityMinuteBarData,
|
class TestMinuteToSession(WithEquityMinuteBarData,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
# March 2016
|
# March 2016
|
||||||
# Su Mo Tu We Th Fr Sa
|
# Su Mo Tu We Th Fr Sa
|
||||||
@@ -565,7 +565,7 @@ class TestMinuteToSession(WithEquityMinuteBarData,
|
|||||||
|
|
||||||
|
|
||||||
class TestResampleSessionBars(WithBcolzFutureMinuteBarReader,
|
class TestResampleSessionBars(WithBcolzFutureMinuteBarReader,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
TRADING_CALENDAR_STRS = ('us_futures',)
|
TRADING_CALENDAR_STRS = ('us_futures',)
|
||||||
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
||||||
@@ -667,7 +667,7 @@ class TestResampleSessionBars(WithBcolzFutureMinuteBarReader,
|
|||||||
|
|
||||||
|
|
||||||
class TestReindexMinuteBars(WithBcolzEquityMinuteBarReader,
|
class TestReindexMinuteBars(WithBcolzEquityMinuteBarReader,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
||||||
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
||||||
@@ -736,7 +736,7 @@ class TestReindexMinuteBars(WithBcolzEquityMinuteBarReader,
|
|||||||
|
|
||||||
|
|
||||||
class TestReindexSessionBars(WithBcolzEquityDailyBarReader,
|
class TestReindexSessionBars(WithBcolzEquityDailyBarReader,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
|
|
||||||
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
TRADING_CALENDAR_STRS = ('us_futures', 'NYSE')
|
||||||
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ from catalyst.testing.fixtures import (
|
|||||||
WithBcolzEquityDailyBarReader,
|
WithBcolzEquityDailyBarReader,
|
||||||
WithTmpDir,
|
WithTmpDir,
|
||||||
WithTradingCalendars,
|
WithTradingCalendars,
|
||||||
ZiplineTestCase,
|
CatalystTestCase,
|
||||||
)
|
)
|
||||||
from catalyst.utils.calendars import get_calendar
|
from catalyst.utils.calendars import get_calendar
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ EQUITY_INFO['symbol'] = [chr(ord('A') + n) for n in range(len(EQUITY_INFO))]
|
|||||||
TEST_QUERY_ASSETS = EQUITY_INFO.index
|
TEST_QUERY_ASSETS = EQUITY_INFO.index
|
||||||
|
|
||||||
|
|
||||||
class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
|
class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, CatalystTestCase):
|
||||||
EQUITY_DAILY_BAR_START_DATE = TEST_CALENDAR_START
|
EQUITY_DAILY_BAR_START_DATE = TEST_CALENDAR_START
|
||||||
EQUITY_DAILY_BAR_END_DATE = TEST_CALENDAR_STOP
|
EQUITY_DAILY_BAR_END_DATE = TEST_CALENDAR_STOP
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ class BcolzDailyBarNeverReadAllTestCase(BcolzDailyBarTestCase):
|
|||||||
class BcolzDailyBarWriterMissingDataTestCase(WithAssetFinder,
|
class BcolzDailyBarWriterMissingDataTestCase(WithAssetFinder,
|
||||||
WithTmpDir,
|
WithTmpDir,
|
||||||
WithTradingCalendars,
|
WithTradingCalendars,
|
||||||
ZiplineTestCase):
|
CatalystTestCase):
|
||||||
# Sid 3 is active from 2015-06-02 to 2015-06-30.
|
# Sid 3 is active from 2015-06-02 to 2015-06-30.
|
||||||
MISSING_DATA_SID = 3
|
MISSING_DATA_SID = 3
|
||||||
# Leave out data for a day in the middle of the query range.
|
# Leave out data for a day in the middle of the query range.
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ZIPLINE legacy test: Catalyst only uses OPEN calendar, and thus
|
||||||
|
# this test suite is irrelevant, and is commented out in its entirety
|
||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@@ -41,3 +46,5 @@ class TestStatelessRulesCME(StatelessRulesTests, TestCase):
|
|||||||
|
|
||||||
class TestStatefulRulesCME(StatefulRulesTests, TestCase):
|
class TestStatefulRulesCME(StatefulRulesTests, TestCase):
|
||||||
CALENDAR_STRING = "CME"
|
CALENDAR_STRING = "CME"
|
||||||
|
|
||||||
|
'''
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ZIPLINE legacy test: Catalyst only uses OPEN calendar, and thus
|
||||||
|
# this test suite is irrelevant, and is commented out in its entirety
|
||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -162,3 +167,5 @@ class TestStatelessRulesNYSE(StatelessRulesTests, TestCase):
|
|||||||
|
|
||||||
class TestStatefulRulesNYSE(StatefulRulesTests, TestCase):
|
class TestStatefulRulesNYSE(StatefulRulesTests, TestCase):
|
||||||
CALENDAR_STRING = "NYSE"
|
CALENDAR_STRING = "NYSE"
|
||||||
|
|
||||||
|
'''
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class TestBcolzWriter(object):
|
|||||||
|
|
||||||
df = self.generate_df(exchange_name, freq, start, end)
|
df = self.generate_df(exchange_name, freq, start, end)
|
||||||
|
|
||||||
print(df.index[0], df.index[-1])
|
print df.index[0],df.index[-1]
|
||||||
|
|
||||||
writer = BcolzExchangeBarWriter(
|
writer = BcolzExchangeBarWriter(
|
||||||
rootdir=self.root_dir,
|
rootdir=self.root_dir,
|
||||||
@@ -140,7 +140,7 @@ class TestBcolzWriter(object):
|
|||||||
|
|
||||||
dx = get_df_from_arrays(arrays, periods)
|
dx = get_df_from_arrays(arrays, periods)
|
||||||
|
|
||||||
assert_equals(df.equals(dx), True)
|
assert_equals(df.equals(df), True)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_bcolz_bitfinex_daily_write_read(self):
|
def test_bcolz_bitfinex_daily_write_read(self):
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ from base import BaseExchangeTestCase
|
|||||||
from catalyst.exchange.bitfinex.bitfinex import Bitfinex
|
from catalyst.exchange.bitfinex.bitfinex import Bitfinex
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth
|
from catalyst.exchange.exchange_utils import get_exchange_auth
|
||||||
from catalyst.finance.execution import (LimitOrder)
|
from catalyst.finance.execution import (LimitOrder)
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
|
|
||||||
log = Logger('test_bitfinex')
|
log = Logger('test_bitfinex')
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class TestBitfinex(BaseExchangeTestCase):
|
class TestBitfinex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -36,7 +34,7 @@ class TestBitfinex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_open_orders(self):
|
def test_open_orders(self):
|
||||||
log.info('retrieving open orders')
|
log.info('retrieving open orders')
|
||||||
# orders = self.exchange.get_open_orders()
|
orders = self.exchange.get_open_orders()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_order(self):
|
def test_get_order(self):
|
||||||
@@ -49,17 +47,18 @@ class TestBitfinex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_candles(self):
|
def test_get_candles(self):
|
||||||
log.info('retrieving candles')
|
log.info('retrieving candles')
|
||||||
# ohlcv_neo = self.exchange.get_candles(
|
ohlcv_neo = self.exchange.get_candles(
|
||||||
# freq='1T',
|
freq='1T',
|
||||||
# assets=self.exchange.get_asset('neo_btc'))
|
assets=self.exchange.get_asset('neo_btc')
|
||||||
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_tickers(self):
|
def test_tickers(self):
|
||||||
log.info('retrieving tickers')
|
log.info('retrieving tickers')
|
||||||
# tickers = self.exchange.tickers([
|
tickers = self.exchange.tickers([
|
||||||
# self.exchange.get_asset('eth_btc'),
|
self.exchange.get_asset('eth_btc'),
|
||||||
# self.exchange.get_asset('etc_btc')
|
self.exchange.get_asset('etc_btc')
|
||||||
# ])
|
])
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_account(self):
|
def test_get_account(self):
|
||||||
@@ -68,11 +67,11 @@ class TestBitfinex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_balances(self):
|
def test_get_balances(self):
|
||||||
log.info('testing exchange balances')
|
log.info('testing exchange balances')
|
||||||
# balances = self.exchange.get_balances()
|
balances = self.exchange.get_balances()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_orderbook(self):
|
def test_orderbook(self):
|
||||||
log.info('testing order book for bitfinex')
|
log.info('testing order book for bitfinex')
|
||||||
# asset = self.exchange.get_asset('eth_btc')
|
asset = self.exchange.get_asset('eth_btc')
|
||||||
# orderbook = self.exchange.get_orderbook(asset)
|
orderbook = self.exchange.get_orderbook(asset)
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
# import pandas as pd
|
import pandas as pd
|
||||||
from catalyst.exchange.bittrex.bittrex import Bittrex
|
from catalyst.exchange.bittrex.bittrex import Bittrex
|
||||||
from catalyst.finance.order import Order
|
from catalyst.finance.order import Order
|
||||||
from base import BaseExchangeTestCase
|
from base import BaseExchangeTestCase
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth
|
from catalyst.exchange.exchange_utils import get_exchange_auth
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
|
|
||||||
log = Logger('test_bittrex')
|
log = Logger('test_bittrex')
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class TestBittrex(BaseExchangeTestCase):
|
class TestBittrex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -35,8 +33,8 @@ class TestBittrex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_open_orders(self):
|
def test_open_orders(self):
|
||||||
log.info('retrieving open orders')
|
log.info('retrieving open orders')
|
||||||
# asset = self.exchange.get_asset('neo_btc')
|
asset = self.exchange.get_asset('neo_btc')
|
||||||
# orders = self.exchange.get_open_orders(asset)
|
orders = self.exchange.get_open_orders(asset)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_order(self):
|
def test_get_order(self):
|
||||||
@@ -53,21 +51,21 @@ class TestBittrex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_candles(self):
|
def test_get_candles(self):
|
||||||
log.info('retrieving candles')
|
log.info('retrieving candles')
|
||||||
# ohlcv_neo = self.exchange.get_candles(
|
ohlcv_neo = self.exchange.get_candles(
|
||||||
# freq='5T',
|
freq='5T',
|
||||||
# assets=self.exchange.get_asset('neo_btc'),
|
assets=self.exchange.get_asset('neo_btc'),
|
||||||
# bar_count=20,
|
bar_count=20,
|
||||||
# end_dt=pd.to_datetime('2017-10-20', utc=True)
|
end_dt=pd.to_datetime('2017-10-20', utc=True)
|
||||||
# )
|
)
|
||||||
# ohlcv_neo_ubq = self.exchange.get_candles(
|
ohlcv_neo_ubq = self.exchange.get_candles(
|
||||||
# freq='1D',
|
freq='1D',
|
||||||
# assets=[
|
assets=[
|
||||||
# self.exchange.get_asset('neo_btc'),
|
self.exchange.get_asset('neo_btc'),
|
||||||
# self.exchange.get_asset('ubq_btc')
|
self.exchange.get_asset('ubq_btc')
|
||||||
# ],
|
],
|
||||||
# bar_count=14,
|
bar_count=14,
|
||||||
# end_dt=pd.to_datetime('2017-10-20', utc=True)
|
end_dt=pd.to_datetime('2017-10-20', utc=True)
|
||||||
# )
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_tickers(self):
|
def test_tickers(self):
|
||||||
@@ -81,7 +79,7 @@ class TestBittrex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_balances(self):
|
def test_get_balances(self):
|
||||||
log.info('testing wallet balances')
|
log.info('testing wallet balances')
|
||||||
# balances = self.exchange.get_balances()
|
balances = self.exchange.get_balances()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_account(self):
|
def test_get_account(self):
|
||||||
@@ -90,6 +88,6 @@ class TestBittrex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_orderbook(self):
|
def test_orderbook(self):
|
||||||
log.info('testing order book for bittrex')
|
log.info('testing order book for bittrex')
|
||||||
# asset = self.exchange.get_asset('eth_btc')
|
asset = self.exchange.get_asset('eth_btc')
|
||||||
# orderbook = self.exchange.get_orderbook(asset)
|
orderbook = self.exchange.get_orderbook(asset)
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from catalyst import get_calendar
|
||||||
from catalyst.exchange.bundle_utils import get_bcolz_chunk, \
|
from catalyst.exchange.bundle_utils import get_bcolz_chunk, \
|
||||||
get_start_dt, get_df_from_arrays
|
get_start_dt, get_df_from_arrays
|
||||||
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
||||||
@@ -21,22 +22,22 @@ log = getLogger('test_exchange_bundle')
|
|||||||
|
|
||||||
class TestExchangeBundle:
|
class TestExchangeBundle:
|
||||||
def test_spot_value(self):
|
def test_spot_value(self):
|
||||||
# data_frequency = 'daily'
|
data_frequency = 'daily'
|
||||||
# exchange_name = 'poloniex'
|
exchange_name = 'poloniex'
|
||||||
|
|
||||||
# exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
# exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
# assets = [
|
assets = [
|
||||||
# exchange.get_asset('btc_usdt')
|
exchange.get_asset('btc_usdt')
|
||||||
# ]
|
]
|
||||||
# dt = pd.to_datetime('2017-10-14', utc=True)
|
dt = pd.to_datetime('2017-10-14', utc=True)
|
||||||
|
|
||||||
# values = exchange_bundle.get_spot_values(
|
values = exchange_bundle.get_spot_values(
|
||||||
# assets=assets,
|
assets=assets,
|
||||||
# field='close',
|
field='close',
|
||||||
# dt=dt,
|
dt=dt,
|
||||||
# data_frequency=data_frequency
|
data_frequency=data_frequency
|
||||||
# )
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_ingest_minute(self):
|
def test_ingest_minute(self):
|
||||||
@@ -214,7 +215,7 @@ class TestExchangeBundle:
|
|||||||
# encounter these problems as I have been focusing on minute data.
|
# encounter these problems as I have been focusing on minute data.
|
||||||
reader = exchange_bundle.get_reader(data_frequency)
|
reader = exchange_bundle.get_reader(data_frequency)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
# Since this pair was loaded last. It should be here in daily mode.
|
# Since this pair was loaded last. It should be there in daily mode.
|
||||||
arrays = reader.load_raw_arrays(
|
arrays = reader.load_raw_arrays(
|
||||||
sids=[asset.sid],
|
sids=[asset.sid],
|
||||||
fields=['close'],
|
fields=['close'],
|
||||||
@@ -251,6 +252,7 @@ class TestExchangeBundle:
|
|||||||
ensure_directory(path)
|
ensure_directory(path)
|
||||||
|
|
||||||
exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
calendar = get_calendar('OPEN')
|
||||||
|
|
||||||
# We are using a BcolzMinuteBarWriter even though the data is daily
|
# We are using a BcolzMinuteBarWriter even though the data is daily
|
||||||
# Each day has a maximum of one bar
|
# Each day has a maximum of one bar
|
||||||
@@ -302,25 +304,26 @@ class TestExchangeBundle:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def test_minute_bundle(self):
|
def test_minute_bundle(self):
|
||||||
# exchange_name = 'poloniex'
|
exchange_name = 'poloniex'
|
||||||
# data_frequency = 'minute'
|
data_frequency = 'minute'
|
||||||
|
|
||||||
# exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
# asset = exchange.get_asset('neos_btc')
|
asset = exchange.get_asset('neos_btc')
|
||||||
|
|
||||||
|
path = get_bcolz_chunk(
|
||||||
|
exchange_name=exchange_name,
|
||||||
|
symbol=asset.symbol,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
period='2017-5',
|
||||||
|
)
|
||||||
|
|
||||||
# path = get_bcolz_chunk(
|
|
||||||
# exchange_name=exchange_name,
|
|
||||||
# symbol=asset.symbol,
|
|
||||||
# data_frequency=data_frequency,
|
|
||||||
# period='2017-5',
|
|
||||||
# )
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_hash_symbol(self):
|
def test_hash_symbol(self):
|
||||||
# symbol = 'etc_btc'
|
symbol = 'etc_btc'
|
||||||
# sid = int(
|
sid = int(
|
||||||
# hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16
|
hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16
|
||||||
# ) % 10 ** 6
|
) % 10 ** 6
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_validate_data(self):
|
def test_validate_data(self):
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import pandas as pd
|
import os
|
||||||
from logbook import Logger
|
import tempfile
|
||||||
from base import BaseExchangeTestCase
|
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
||||||
from catalyst.finance.order import Order
|
from catalyst.finance.order import Order
|
||||||
|
from base import BaseExchangeTestCase
|
||||||
|
from logbook import Logger
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth
|
from catalyst.exchange.exchange_utils import get_exchange_auth
|
||||||
|
from catalyst.utils.paths import ensure_directory
|
||||||
|
|
||||||
log = Logger('test_ccxt')
|
log = Logger('test_ccxt')
|
||||||
|
|
||||||
@@ -35,9 +38,9 @@ class TestCCXT(BaseExchangeTestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def test_open_orders(self):
|
def test_open_orders(self):
|
||||||
# log.info('retrieving open orders')
|
log.info('retrieving open orders')
|
||||||
# asset = self.exchange.get_asset('neo_eth')
|
asset = self.exchange.get_asset('neo_eth')
|
||||||
# orders = self.exchange.get_open_orders(asset)
|
orders = self.exchange.get_open_orders(asset)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_order(self):
|
def test_get_order(self):
|
||||||
@@ -76,7 +79,7 @@ class TestCCXT(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_balances(self):
|
def test_get_balances(self):
|
||||||
log.info('testing wallet balances')
|
log.info('testing wallet balances')
|
||||||
# balances = self.exchange.get_balances()
|
balances = self.exchange.get_balances()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_account(self):
|
def test_get_account(self):
|
||||||
@@ -85,8 +88,8 @@ class TestCCXT(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_orderbook(self):
|
def test_orderbook(self):
|
||||||
log.info('testing order book for bittrex')
|
log.info('testing order book for bittrex')
|
||||||
# asset = self.exchange.get_asset('eth_btc')
|
asset = self.exchange.get_asset('eth_btc')
|
||||||
# orderbook = self.exchange.get_orderbook(asset, 'all', limit=10)
|
orderbook = self.exchange.get_orderbook(asset, 'all', limit=10)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_fees(self):
|
def test_get_fees(self):
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ from logbook import Logger
|
|||||||
|
|
||||||
from catalyst import get_calendar
|
from catalyst import get_calendar
|
||||||
from catalyst.exchange.asset_finder_exchange import AssetFinderExchange
|
from catalyst.exchange.asset_finder_exchange import AssetFinderExchange
|
||||||
from catalyst.exchange.exchange_data_portal import (
|
from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest, \
|
||||||
DataPortalExchangeBacktest,
|
|
||||||
DataPortalExchangeLive
|
DataPortalExchangeLive
|
||||||
)
|
|
||||||
from catalyst.exchange.exchange_utils import get_common_assets
|
from catalyst.exchange.exchange_utils import get_common_assets
|
||||||
from catalyst.exchange.factory import get_exchanges
|
from catalyst.exchange.factory import get_exchange, get_exchanges
|
||||||
from test_utils import rnd_history_date_days, rnd_bar_count
|
from test_utils import rnd_history_date_days, rnd_bar_count, output_df
|
||||||
|
|
||||||
log = Logger('test_bitfinex')
|
log = Logger('test_bitfinex')
|
||||||
|
|
||||||
@@ -37,31 +35,31 @@ class TestExchangeDataPortal:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_get_history_window_live(self):
|
def test_get_history_window_live(self):
|
||||||
# asset_finder = self.data_portal_live.asset_finder
|
asset_finder = self.data_portal_live.asset_finder
|
||||||
|
|
||||||
# assets = [
|
assets = [
|
||||||
# asset_finder.lookup_symbol('eth_btc', self.bitfinex),
|
asset_finder.lookup_symbol('eth_btc', self.bitfinex),
|
||||||
# asset_finder.lookup_symbol('eth_btc', self.bittrex)
|
asset_finder.lookup_symbol('eth_btc', self.bittrex)
|
||||||
# ]
|
]
|
||||||
# now = pd.Timestamp.utcnow()
|
now = pd.Timestamp.utcnow()
|
||||||
# data = self.data_portal_live.get_history_window(
|
data = self.data_portal_live.get_history_window(
|
||||||
# assets,
|
assets,
|
||||||
# now,
|
now,
|
||||||
# 10,
|
10,
|
||||||
# '1m',
|
'1m',
|
||||||
# 'price')
|
'price')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_spot_value_live(self):
|
def test_get_spot_value_live(self):
|
||||||
# asset_finder = self.data_portal_live.asset_finder
|
asset_finder = self.data_portal_live.asset_finder
|
||||||
|
|
||||||
# assets = [
|
assets = [
|
||||||
# asset_finder.lookup_symbol('eth_btc', self.bitfinex),
|
asset_finder.lookup_symbol('eth_btc', self.bitfinex),
|
||||||
# asset_finder.lookup_symbol('eth_btc', self.bittrex)
|
asset_finder.lookup_symbol('eth_btc', self.bittrex)
|
||||||
# ]
|
]
|
||||||
# now = pd.Timestamp.utcnow()
|
now = pd.Timestamp.utcnow()
|
||||||
# value = self.data_portal_live.get_spot_value(
|
value = self.data_portal_live.get_spot_value(
|
||||||
# assets, 'price', now, '1m')
|
assets, 'price', now, '1m')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_history_window_backtest(self):
|
def test_get_history_window_backtest(self):
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ from base import BaseExchangeTestCase
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth
|
from catalyst.exchange.exchange_utils import get_exchange_auth
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
from test_utils import output_df
|
from test_utils import output_df
|
||||||
|
|
||||||
log = Logger('test_poloniex')
|
log = Logger('test_poloniex')
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class TestPoloniex(BaseExchangeTestCase):
|
class TestPoloniex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -37,8 +34,8 @@ class TestPoloniex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_open_orders(self):
|
def test_open_orders(self):
|
||||||
log.info('retrieving open orders')
|
log.info('retrieving open orders')
|
||||||
# asset = self.exchange.get_asset('neos_btc')
|
asset = self.exchange.get_asset('neos_btc')
|
||||||
# orders = self.exchange.get_open_orders(asset)
|
orders = self.exchange.get_open_orders(asset)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_order(self):
|
def test_get_order(self):
|
||||||
@@ -82,7 +79,7 @@ class TestPoloniex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_get_balances(self):
|
def test_get_balances(self):
|
||||||
log.info('testing wallet balances')
|
log.info('testing wallet balances')
|
||||||
# balances = self.exchange.get_balances()
|
balances = self.exchange.get_balances()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_account(self):
|
def test_get_account(self):
|
||||||
@@ -91,6 +88,7 @@ class TestPoloniex(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_orderbook(self):
|
def test_orderbook(self):
|
||||||
log.info('testing order book for poloniex')
|
log.info('testing order book for poloniex')
|
||||||
# asset = self.exchange.get_asset('eth_btc')
|
asset = self.exchange.get_asset('eth_btc')
|
||||||
# orderbook = self.exchange.get_orderbook(asset)
|
|
||||||
|
orderbook = self.exchange.get_orderbook(asset)
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import os
|
import os
|
||||||
|
import tarfile
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import matplotlib
|
|
||||||
import matplotlib.pyplot as plt
|
from catalyst import get_calendar
|
||||||
from matplotlib.finance import candlestick2_ohlc
|
|
||||||
# from matplotlib.finance import volume_overlay
|
|
||||||
import matplotlib.ticker as ticker
|
|
||||||
|
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader
|
||||||
|
from catalyst.data.minute_bars import BcolzMinuteBarMetadata
|
||||||
from catalyst.exchange.bundle_utils import get_df_from_arrays, get_bcolz_chunk
|
from catalyst.exchange.bundle_utils import get_df_from_arrays, get_bcolz_chunk
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.finance import candlestick2_ohlc
|
||||||
|
from matplotlib.finance import volume_overlay
|
||||||
|
import matplotlib.ticker as ticker
|
||||||
|
|
||||||
from catalyst.exchange.factory import get_exchange
|
from catalyst.exchange.factory import get_exchange
|
||||||
|
|
||||||
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
|
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
|
||||||
@@ -46,7 +51,7 @@ class ValidateChunks(object):
|
|||||||
if data_frequency == 'daily':
|
if data_frequency == 'daily':
|
||||||
end = end - pd.Timedelta(hours=23, minutes=59)
|
end = end - pd.Timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
print(start, end, data_frequency)
|
print start, end, data_frequency
|
||||||
|
|
||||||
arrays = reader.load_raw_arrays(self.columns, start, end,
|
arrays = reader.load_raw_arrays(self.columns, start, end,
|
||||||
[asset.sid, ])
|
[asset.sid, ])
|
||||||
@@ -80,8 +85,8 @@ class ValidateChunks(object):
|
|||||||
matplotlib.transforms.Bbox([[0.125, 0.1], [0.9, 0.26]]))
|
matplotlib.transforms.Bbox([[0.125, 0.1], [0.9, 0.26]]))
|
||||||
|
|
||||||
# Plot the volume overlay
|
# Plot the volume overlay
|
||||||
# bc = volume_overlay(ax2, df['open'], df['close'], df['volume'],
|
bc = volume_overlay(ax2, df['open'], df['close'], df['volume'],
|
||||||
# colorup='g', alpha=0.5, width=1)
|
colorup='g', alpha=0.5, width=1)
|
||||||
|
|
||||||
ax.xaxis.set_major_locator(ticker.MaxNLocator(6))
|
ax.xaxis.set_major_locator(ticker.MaxNLocator(6))
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ def rnd_history_date_minutes(max_minutes=1440):
|
|||||||
|
|
||||||
|
|
||||||
def rnd_bar_count(max_bars=21):
|
def rnd_bar_count(max_bars=21):
|
||||||
# now = pd.Timestamp.utcnow()
|
now = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
return randint(0, max_bars)
|
return randint(0, max_bars)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user