mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
Compare commits
107
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
515c6e13f0 | ||
|
|
5abdc063eb | ||
|
|
360e1adc22 | ||
|
|
8c6ac53a05 | ||
|
|
b636edb32f | ||
|
|
d02c6d8ce9 | ||
|
|
88f6557aaf | ||
|
|
b476024612 | ||
|
|
a3808c31ef | ||
|
|
5d251f6f9a | ||
|
|
117332d0b4 | ||
|
|
2bbc0c00cc | ||
|
|
5e4ad9b338 | ||
|
|
a9a422c892 | ||
|
|
5b6bbacab0 | ||
|
|
35677c553c | ||
|
|
df357d2327 | ||
|
|
e6ff7ee4fc | ||
|
|
30eea4b8f7 | ||
|
|
7ad047a432 | ||
|
|
e291a260b2 | ||
|
|
9a9e66b43d | ||
|
|
1c3deb648a | ||
|
|
b39311de85 | ||
|
|
5417a0cdcf | ||
|
|
6a4ea43d27 | ||
|
|
7fc1ade46c | ||
|
|
635fc80ef2 | ||
|
|
c59d805717 | ||
|
|
3394614ecf | ||
|
|
06c8ab9c37 | ||
|
|
6a0d0a0422 | ||
|
|
9470771561 | ||
|
|
8132e1f5ea | ||
|
|
0b28bf0e96 | ||
|
|
13f023d364 | ||
|
|
eaefe4a908 | ||
|
|
f47b657c6f | ||
|
|
800a2efa50 | ||
|
|
7465e8e432 | ||
|
|
86ab3804e5 | ||
|
|
b749d47a61 | ||
|
|
032c7fd16b | ||
|
|
b9579ab4b4 | ||
|
|
c7632b57a6 | ||
|
|
da17e66961 | ||
|
|
5f8016c67e | ||
|
|
3d88d6a2c7 | ||
|
|
9c3a9e233b | ||
|
|
c43509c28e | ||
|
|
0e0bfc82b5 | ||
|
|
2f660db511 | ||
|
|
fdc5a30060 | ||
|
|
bb1d96ed5d | ||
|
|
59501905ab | ||
|
|
2b85732e36 | ||
|
|
284c749bb5 | ||
|
|
d7f5e73f84 | ||
|
|
cde69da173 | ||
|
|
bcc75f6b00 | ||
|
|
f179381b64 | ||
|
|
10ba53b897 | ||
|
|
1cfe3b1bb2 | ||
|
|
268ff9c826 | ||
|
|
7eb184d946 | ||
|
|
1cc34a1485 | ||
|
|
aa2f2f3627 | ||
|
|
7e373e2f9c | ||
|
|
942e6f263c | ||
|
|
2e6d7d28ba | ||
|
|
3a823ea457 | ||
|
|
4daba6cfb4 | ||
|
|
fa018e2e0c | ||
|
|
315d25f7c0 | ||
|
|
cc7ffada96 | ||
|
|
5394c1bc91 | ||
|
|
b230b73829 | ||
|
|
930a68ab4a | ||
|
|
4e833981e4 | ||
|
|
2ea402ff10 | ||
|
|
da6b024edc | ||
|
|
565e9a3cea | ||
|
|
3c10d19a7e | ||
|
|
cf96e047cd | ||
|
|
6f6a8e1272 | ||
|
|
c2a02e7074 | ||
|
|
7d2cf97fbf | ||
|
|
195469897c | ||
|
|
c7b422d465 | ||
|
|
2dbace37bb | ||
|
|
2e903fd42c | ||
|
|
47a104b29c | ||
|
|
d248581523 | ||
|
|
48f6300e08 | ||
|
|
f7a143cb78 | ||
|
|
2f7cd97852 | ||
|
|
73eca75ed9 | ||
|
|
2ade2989e8 | ||
|
|
b1d5acf2ad | ||
|
|
5d5ec6b9be | ||
|
|
1b84023c5d | ||
|
|
97f3329c1b | ||
|
|
bdeb344999 | ||
|
|
52e1de954f | ||
|
|
7b9eafef4e | ||
|
|
8b141a0c28 | ||
|
|
7f602d7fcc |
+3
-1
@@ -1 +1,3 @@
|
|||||||
All the documentation for `Catalyst <https://github.com/enigmampc/catalyst>`_ can be found in the `catalyst-docs wiki <https://github.com/enigmampc/catalyst-docs/wiki>`_.
|
All the documentation for `Catalyst <https://github.com/enigmampc/catalyst>`_
|
||||||
|
can be found in the
|
||||||
|
`documentation website <https://enigmampc.github.io/catalyst>`_.
|
||||||
+41
-3
@@ -9,7 +9,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.init_utils import get_exchange
|
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
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ except NameError:
|
|||||||
'--default-extension/--no-default-extension',
|
'--default-extension/--no-default-extension',
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=True,
|
default=True,
|
||||||
help="Don't load the default catalyst extension.py file in $ZIPLINE_HOME.",
|
help="Don't load the default catalyst extension.py file in $CATALYST_HOME.",
|
||||||
)
|
)
|
||||||
@click.version_option()
|
@click.version_option()
|
||||||
def main(extension, strict_extensions, default_extension):
|
def main(extension, strict_extensions, default_extension):
|
||||||
@@ -495,6 +495,10 @@ def ingest_exchange(exchange_name, data_frequency, start, end,
|
|||||||
"""
|
"""
|
||||||
Ingest data for the given exchange.
|
Ingest data for the given exchange.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if exchange_name is None:
|
||||||
|
ctx.fail("must specify an exchange name '-x'")
|
||||||
|
|
||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
|
||||||
@@ -509,6 +513,40 @@ def ingest_exchange(exchange_name, data_frequency, start, end,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@main.command(name='clean-exchange')
|
||||||
|
@click.option(
|
||||||
|
'-x',
|
||||||
|
'--exchange-name',
|
||||||
|
type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}),
|
||||||
|
help='The name of the exchange bundle to ingest (supported: bitfinex,'
|
||||||
|
' bittrex, poloniex).',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-f',
|
||||||
|
'--data-frequency',
|
||||||
|
type=click.Choice({'daily', 'minute'}),
|
||||||
|
default=None,
|
||||||
|
help='The bundle data frequency to remove. If not specified, it will '
|
||||||
|
'remove both daily and minute bundles.',
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def clean_exchange(ctx, exchange_name, data_frequency):
|
||||||
|
"""Clean up bundles from 'ingest-exchange'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if exchange_name is None:
|
||||||
|
ctx.fail("must specify an exchange name '-x'")
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
|
||||||
|
click.echo('Cleaning exchange bundle {}...'.format(exchange_name))
|
||||||
|
exchange_bundle.clean(
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
)
|
||||||
|
click.echo('Done')
|
||||||
|
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
'-b',
|
'-b',
|
||||||
@@ -598,7 +636,7 @@ def ingest(ctx, bundle, exchange_name, compile_locally, assets_version,
|
|||||||
' This may not be passed with -e / --before or -a / --after',
|
' This may not be passed with -e / --before or -a / --after',
|
||||||
)
|
)
|
||||||
def clean(bundle, before, after, keep_last):
|
def clean(bundle, before, after, keep_last):
|
||||||
"""Clean up data downloaded with the ingest command.
|
"""Clean up bundles from 'ingest'.
|
||||||
"""
|
"""
|
||||||
bundles_module.clean(
|
bundles_module.clean(
|
||||||
bundle,
|
bundle,
|
||||||
|
|||||||
@@ -138,8 +138,9 @@ from catalyst.gens.sim_engine import MinuteSimulationClock
|
|||||||
from catalyst.sources.benchmark_source import BenchmarkSource
|
from catalyst.sources.benchmark_source import BenchmarkSource
|
||||||
from catalyst.catalyst_warnings import ZiplineDeprecationWarning
|
from catalyst.catalyst_warnings import ZiplineDeprecationWarning
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
log = logbook.Logger("ZiplineLog")
|
log = logbook.Logger("CatalystLog", level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class TradingAlgorithm(object):
|
class TradingAlgorithm(object):
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
"""
|
"""
|
||||||
Cythonized Asset object.
|
Cythonized Asset object.
|
||||||
"""
|
"""
|
||||||
|
import hashlib
|
||||||
|
|
||||||
cimport cython
|
cimport cython
|
||||||
from cpython.number cimport PyNumber_Index
|
from cpython.number cimport PyNumber_Index
|
||||||
from cpython.object cimport (
|
from cpython.object cimport (
|
||||||
@@ -501,7 +503,11 @@ cdef class TradingPair(Asset):
|
|||||||
|
|
||||||
if sid == 0 or sid is None:
|
if sid == 0 or sid is None:
|
||||||
try:
|
try:
|
||||||
sid = abs(hash(symbol)) % (10 ** 4)
|
# sid = abs(hash(symbol)) % (10 ** 4)
|
||||||
|
# TODO: try to encode the symbol in the main scope
|
||||||
|
sid = int(
|
||||||
|
hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16
|
||||||
|
) % 10 ** 6
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise SidHashError(symbol=symbol)
|
raise SidHashError(symbol=symbol)
|
||||||
|
|
||||||
@@ -553,6 +559,20 @@ cdef class TradingPair(Asset):
|
|||||||
end_minute=self.end_minute
|
end_minute=self.end_minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_exchange_open(self, dt_minute):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt_minute: pd.Timestamp (UTC, tz-aware)
|
||||||
|
The minute to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
boolean: whether the asset's exchange is open at the given minute.
|
||||||
|
"""
|
||||||
|
#TODO: consider implementing to spot holds
|
||||||
|
return True
|
||||||
|
|
||||||
cpdef __reduce__(self):
|
cpdef __reduce__(self):
|
||||||
"""
|
"""
|
||||||
Function used by pickle to determine how to serialize/deserialize this
|
Function used by pickle to determine how to serialize/deserialize this
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ from catalyst.utils.numpy_utils import as_column
|
|||||||
from catalyst.utils.preprocess import preprocess
|
from catalyst.utils.preprocess import preprocess
|
||||||
from catalyst.utils.sqlite_utils import group_into_chunks, coerce_string_to_eng
|
from catalyst.utils.sqlite_utils import group_into_chunks, coerce_string_to_eng
|
||||||
|
|
||||||
log = Logger('assets.py')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('assets.py', level=LOG_LEVEL)
|
||||||
|
|
||||||
# A set of fields that need to be converted to strings before building an
|
# A set of fields that need to be converted to strings before building an
|
||||||
# Asset to avoid unicode fields
|
# Asset to avoid unicode fields
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logbook
|
||||||
|
|
||||||
|
LOG_LEVEL = logbook.INFO
|
||||||
@@ -212,16 +212,16 @@ class PoloniexCurator(object):
|
|||||||
def write_ohlcv_file(self, currencyPair):
|
def write_ohlcv_file(self, currencyPair):
|
||||||
csv_trades = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv'
|
csv_trades = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv'
|
||||||
csv_1min = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv'
|
csv_1min = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv'
|
||||||
if( os.path.isfile(csv_1min) ):
|
#if( os.path.isfile(csv_1min) ):
|
||||||
log.debug(currencyPair+': 1min data already present. Delete the file if you want to rebuild it.')
|
# log.debug(currencyPair+': 1min data already present. Delete the file if you want to rebuild it.')
|
||||||
else:
|
#else:
|
||||||
df = pd.read_csv(csv_trades, names=['tradeID','date','type','rate','amount','total','globalTradeID'],
|
df = pd.read_csv(csv_trades, names=['tradeID','date','type','rate','amount','total','globalTradeID'],
|
||||||
dtype = {'tradeID': int, 'date': str, 'type': str, 'rate': float, 'amount': float, 'total': float, 'globalTradeID': int } )
|
dtype = {'tradeID': int, 'date': str, 'type': str, 'rate': float, 'amount': float, 'total': float, 'globalTradeID': int } )
|
||||||
df.drop(['tradeID','type','amount','globalTradeID'], axis=1, inplace=True)
|
df.drop(['tradeID','type','amount','globalTradeID'], axis=1, inplace=True)
|
||||||
df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True)
|
df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True)
|
||||||
ohlcv = self.generate_ohlcv(df)
|
ohlcv = self.generate_ohlcv(df)
|
||||||
try:
|
try:
|
||||||
with open(csv_1min, 'ab') as csvfile:
|
with open(csv_1min, 'w') as csvfile:
|
||||||
csvwriter = csv.writer(csvfile)
|
csvwriter = csv.writer(csvfile)
|
||||||
for item in ohlcv.itertuples():
|
for item in ohlcv.itertuples():
|
||||||
if item.Index == 0:
|
if item.Index == 0:
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ cpdef _read_bcolz_data(ctable_t table,
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if column_name in ['open', 'high', 'low', 'close']:
|
if column_name in ['open', 'high', 'low', 'close', 'volume']:
|
||||||
where_nan = (outbuf == 0)
|
where_nan = (outbuf == 0)
|
||||||
outbuf_as_float = outbuf.astype(float64) * .000000001
|
outbuf_as_float = outbuf.astype(float64) * .000000001
|
||||||
outbuf_as_float[where_nan] = NAN
|
outbuf_as_float[where_nan] = NAN
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ from catalyst.utils.cli import (
|
|||||||
)
|
)
|
||||||
from catalyst.utils.memoize import lazyval
|
from catalyst.utils.memoize import lazyval
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
logbook.StderrHandler().push_application()
|
logbook.StderrHandler().push_application()
|
||||||
log = logbook.Logger(__name__)
|
log = logbook.Logger(__name__, level=LOG_LEVEL)
|
||||||
|
|
||||||
DEFAULT_RETRIES = 5
|
DEFAULT_RETRIES = 5
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ from catalyst.utils.cli import maybe_show_progress
|
|||||||
|
|
||||||
from . import core as bundles
|
from . import core as bundles
|
||||||
|
|
||||||
log = Logger(__name__)
|
from catalyst.constants import 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):
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ from catalyst.errors import (
|
|||||||
HistoryWindowStartsBeforeData,
|
HistoryWindowStartsBeforeData,
|
||||||
)
|
)
|
||||||
|
|
||||||
log = Logger('DataPortal')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('DataPortal', level=LOG_LEVEL)
|
||||||
|
|
||||||
BASE_FIELDS = frozenset([
|
BASE_FIELDS = frozenset([
|
||||||
"open",
|
"open",
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ from ..utils.paths import (
|
|||||||
data_root,
|
data_root,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logbook.Logger('Loader')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
logger = logbook.Logger('Loader', level=LOG_LEVEL)
|
||||||
|
|
||||||
# Mapping from index symbol to appropriate bond data
|
# Mapping from index symbol to appropriate bond data
|
||||||
INDEX_MAPPING = {
|
INDEX_MAPPING = {
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ from catalyst.utils.calendars import get_calendar
|
|||||||
from catalyst.utils.cli import maybe_show_progress
|
from catalyst.utils.cli import maybe_show_progress
|
||||||
from catalyst.utils.memoize import lazyval
|
from catalyst.utils.memoize import lazyval
|
||||||
|
|
||||||
logger = logbook.Logger('MinuteBars')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
logger = logbook.Logger('MinuteBars', level=LOG_LEVEL)
|
||||||
|
|
||||||
US_EQUITIES_MINUTES_PER_DAY = 390
|
US_EQUITIES_MINUTES_PER_DAY = 390
|
||||||
FUTURES_MINUTES_PER_DAY = 1440
|
FUTURES_MINUTES_PER_DAY = 1440
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ from catalyst.utils.cli import (
|
|||||||
from ._equities import _compute_row_slices, _read_bcolz_data
|
from ._equities import _compute_row_slices, _read_bcolz_data
|
||||||
from ._adjustments import load_adjustments_from_sqlite
|
from ._adjustments import load_adjustments_from_sqlite
|
||||||
|
|
||||||
logger = logbook.Logger('UsEquityPricing')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
logger = logbook.Logger('UsEquityPricing', level=LOG_LEVEL)
|
||||||
|
|
||||||
OHLC = frozenset(['open', 'high', 'low', 'close'])
|
OHLC = frozenset(['open', 'high', 'low', 'close'])
|
||||||
OHLCV = frozenset(['open', 'high', 'low', 'close', 'volume'])
|
OHLCV = frozenset(['open', 'high', 'low', 'close', 'volume'])
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from catalyst.api import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.ASSET_NAME = 'USDT_BTC'
|
context.ASSET_NAME = 'BTC_USDT'
|
||||||
context.TARGET_HODL_RATIO = 0.8
|
context.TARGET_HODL_RATIO = 0.8
|
||||||
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
|
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ def handle_data(context, data):
|
|||||||
context.is_buying = False
|
context.is_buying = False
|
||||||
|
|
||||||
# Retrieve current asset price from pricing data
|
# Retrieve current asset price from pricing data
|
||||||
price = data[context.asset].price
|
price = data.current(context.asset, 'price')
|
||||||
|
|
||||||
# Check if still buying and could (approximately) afford another purchase
|
# Check if still buying and could (approximately) afford another purchase
|
||||||
if context.is_buying and cash > price:
|
if context.is_buying and cash > price:
|
||||||
@@ -70,7 +70,7 @@ def handle_data(context, data):
|
|||||||
|
|
||||||
record(
|
record(
|
||||||
price=price,
|
price=price,
|
||||||
volume=data[context.asset].volume,
|
volume=data.current(context.asset, 'volume'),
|
||||||
cash=cash,
|
cash=cash,
|
||||||
starting_cash=context.portfolio.starting_cash,
|
starting_cash=context.portfolio.starting_cash,
|
||||||
leverage=context.account.leverage,
|
leverage=context.account.leverage,
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
|
'''
|
||||||
|
This is a very simple example referenced in the beginner's tutorial:
|
||||||
|
https://enigmampc.github.io/catalyst/beginner-tutorial.html
|
||||||
|
|
||||||
|
Run this example, by executing the following from your terminal:
|
||||||
|
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
||||||
|
|
||||||
|
If you want to run this code using another exchange, make sure that
|
||||||
|
the asset is available on that exchange. For example, if you were to run
|
||||||
|
it for exchange Poloniex, you would need to edit the following line:
|
||||||
|
|
||||||
|
context.asset = symbol('btc_usdt') # note 'usdt' instead of 'usd'
|
||||||
|
|
||||||
|
and specify exchange poloniex as follows:
|
||||||
|
|
||||||
|
catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
||||||
|
|
||||||
|
To see which assets are available on each exchange, visit:
|
||||||
|
https://www.enigma.co/catalyst/status
|
||||||
|
'''
|
||||||
|
|
||||||
from catalyst.api import order, record, symbol
|
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(asset, 1)
|
order(context.asset, 1)
|
||||||
record(btc = data.current(context.asset, 'price'))
|
record(btc = data.current(context.asset, 'price'))
|
||||||
@@ -27,7 +27,7 @@ log = Logger(algo_namespace)
|
|||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
log.info('initializing algo')
|
log.info('initializing algo')
|
||||||
context.ASSET_NAME = 'XRP_USD'
|
context.ASSET_NAME = 'XRP_USDT'
|
||||||
context.asset = symbol(context.ASSET_NAME)
|
context.asset = symbol(context.ASSET_NAME)
|
||||||
|
|
||||||
context.TARGET_POSITIONS = 5000
|
context.TARGET_POSITIONS = 5000
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
import talib
|
|
||||||
from logbook import Logger
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from catalyst.api import (
|
|
||||||
order,
|
|
||||||
order_target_percent,
|
|
||||||
symbol,
|
|
||||||
record,
|
|
||||||
get_open_orders,
|
|
||||||
)
|
|
||||||
from catalyst.exchange.stats_utils import get_pretty_stats
|
|
||||||
from catalyst.utils.run_algo import run_algorithm
|
|
||||||
|
|
||||||
algo_namespace = 'buy_low_sell_high_neo'
|
|
||||||
log = Logger(algo_namespace)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
log.info('initializing algo')
|
|
||||||
context.asset = symbol('neo_btc', 'bitfinex')
|
|
||||||
|
|
||||||
context.TARGET_POSITIONS = 50000
|
|
||||||
context.PROFIT_TARGET = 0.1
|
|
||||||
context.SLIPPAGE_ALLOWED = 0.02
|
|
||||||
|
|
||||||
context.retry_check_open_orders = 10
|
|
||||||
context.retry_update_portfolio = 10
|
|
||||||
context.retry_order = 5
|
|
||||||
|
|
||||||
context.errors = []
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_data(context, data):
|
|
||||||
price = data.current(context.asset, 'close')
|
|
||||||
log.info('got price {price}'.format(price=price))
|
|
||||||
|
|
||||||
if price is None:
|
|
||||||
log.warn('no pricing data')
|
|
||||||
return
|
|
||||||
|
|
||||||
prices = data.history(
|
|
||||||
context.asset,
|
|
||||||
fields='price',
|
|
||||||
bar_count=1,
|
|
||||||
frequency='1m'
|
|
||||||
)
|
|
||||||
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
|
||||||
log.info('got rsi: {}'.format(rsi))
|
|
||||||
|
|
||||||
# Buying more when RSI is low, this should lower our cost basis
|
|
||||||
if rsi <= 30:
|
|
||||||
buy_increment = 1
|
|
||||||
elif rsi <= 40:
|
|
||||||
buy_increment = 0.5
|
|
||||||
elif rsi <= 70:
|
|
||||||
buy_increment = 0.1
|
|
||||||
else:
|
|
||||||
buy_increment = None
|
|
||||||
|
|
||||||
cash = context.portfolio.cash
|
|
||||||
log.info('base currency available: {cash}'.format(cash=cash))
|
|
||||||
|
|
||||||
record(price=price)
|
|
||||||
|
|
||||||
orders = get_open_orders(context.asset)
|
|
||||||
if len(orders) > 0:
|
|
||||||
log.info('skipping bar until all open orders execute')
|
|
||||||
return
|
|
||||||
|
|
||||||
is_buy = False
|
|
||||||
cost_basis = None
|
|
||||||
if context.asset in context.portfolio.positions:
|
|
||||||
position = context.portfolio.positions[context.asset]
|
|
||||||
|
|
||||||
cost_basis = position.cost_basis
|
|
||||||
log.info(
|
|
||||||
'found {amount} positions with cost basis {cost_basis}'.format(
|
|
||||||
amount=position.amount,
|
|
||||||
cost_basis=cost_basis
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if position.amount >= context.TARGET_POSITIONS:
|
|
||||||
log.info('reached positions target: {}'.format(position.amount))
|
|
||||||
return
|
|
||||||
|
|
||||||
if price < cost_basis:
|
|
||||||
is_buy = True
|
|
||||||
elif position.amount > 0 and \
|
|
||||||
price > cost_basis * (1 + context.PROFIT_TARGET):
|
|
||||||
profit = (price * position.amount) - (cost_basis * position.amount)
|
|
||||||
|
|
||||||
log.info('closing position, taking profit: {}'.format(profit))
|
|
||||||
order_target_percent(
|
|
||||||
asset=context.asset,
|
|
||||||
target=0,
|
|
||||||
limit_price=price * (1 - context.SLIPPAGE_ALLOWED),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
log.info('no buy or sell opportunity found')
|
|
||||||
else:
|
|
||||||
is_buy = True
|
|
||||||
|
|
||||||
if is_buy:
|
|
||||||
if buy_increment is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if price * buy_increment > cash:
|
|
||||||
log.info('not enough base currency to consider buying')
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info(
|
|
||||||
'buying position cheaper than cost basis {} < {}'.format(
|
|
||||||
price,
|
|
||||||
cost_basis
|
|
||||||
)
|
|
||||||
)
|
|
||||||
limit_price = price * (1 + context.SLIPPAGE_ALLOWED)
|
|
||||||
order(
|
|
||||||
asset=context.asset,
|
|
||||||
amount=buy_increment,
|
|
||||||
limit_price=limit_price
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
log.info('handling bar {}'.format(data.current_dt))
|
|
||||||
# try:
|
|
||||||
_handle_data(context, data)
|
|
||||||
# except Exception as e:
|
|
||||||
# log.warn('aborting the bar on error {}'.format(e))
|
|
||||||
# context.errors.append(e)
|
|
||||||
|
|
||||||
log.info('completed bar {}, total execution errors {}'.format(
|
|
||||||
data.current_dt,
|
|
||||||
len(context.errors)
|
|
||||||
))
|
|
||||||
|
|
||||||
if len(context.errors) > 0:
|
|
||||||
log.info('the errors:\n{}'.format(context.errors))
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context, stats):
|
|
||||||
log.info('the daily stats:\n{}'.format(get_pretty_stats(stats)))
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# run_algorithm(
|
|
||||||
# initialize=initialize,
|
|
||||||
# handle_data=handle_data,
|
|
||||||
# analyze=analyze,
|
|
||||||
# exchange_name='bitfinex',
|
|
||||||
# live=True,
|
|
||||||
# algo_namespace=algo_namespace,
|
|
||||||
# base_currency='btc',
|
|
||||||
# live_graph=False
|
|
||||||
# )
|
|
||||||
|
|
||||||
# Backtest
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=250,
|
|
||||||
data_frequency='minute',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace=algo_namespace,
|
|
||||||
base_currency='btc'
|
|
||||||
)
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import pandas as pd
|
|
||||||
import talib
|
import talib
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import symbol
|
from catalyst.api import symbol
|
||||||
@@ -7,7 +7,7 @@ from catalyst.api import symbol
|
|||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
print('initializing')
|
print('initializing')
|
||||||
context.asset = symbol('xrp_btc')
|
context.asset = symbol('eth_btc')
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
@@ -16,36 +16,37 @@ def handle_data(context, data):
|
|||||||
price = data.current(context.asset, 'close')
|
price = data.current(context.asset, 'close')
|
||||||
print('got price {price}'.format(price=price))
|
print('got price {price}'.format(price=price))
|
||||||
|
|
||||||
|
try:
|
||||||
prices = data.history(
|
prices = data.history(
|
||||||
context.asset,
|
context.asset,
|
||||||
fields='price',
|
fields='price',
|
||||||
bar_count=15,
|
bar_count=16,
|
||||||
frequency='1d'
|
frequency='5T'
|
||||||
)
|
)
|
||||||
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))
|
||||||
pass
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
# run_algorithm(
|
|
||||||
# capital_base=250,
|
|
||||||
# start=pd.to_datetime('2015-08-01', utc=True),
|
|
||||||
# end=pd.to_datetime('2017-9-30', utc=True),
|
|
||||||
# data_frequency='daily',
|
|
||||||
# initialize=initialize,
|
|
||||||
# handle_data=handle_data,
|
|
||||||
# analyze=None,
|
|
||||||
# exchange_name='poloniex',
|
|
||||||
# algo_namespace='simple_loop',
|
|
||||||
# base_currency='eth'
|
|
||||||
# )
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
|
capital_base=250,
|
||||||
|
start=pd.to_datetime('2016-6-1', utc=True),
|
||||||
|
end=pd.to_datetime('2016-12-31', utc=True),
|
||||||
|
data_frequency='daily',
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=None,
|
analyze=None,
|
||||||
exchange_name='bitfinex',
|
exchange_name='bitfinex',
|
||||||
live=True,
|
|
||||||
algo_namespace='simple_loop',
|
algo_namespace='simple_loop',
|
||||||
base_currency='eth',
|
base_currency='btc'
|
||||||
live_graph=False
|
|
||||||
)
|
)
|
||||||
|
# run_algorithm(
|
||||||
|
# initialize=initialize,
|
||||||
|
# handle_data=handle_data,
|
||||||
|
# analyze=None,
|
||||||
|
# exchange_name='poloniex',
|
||||||
|
# live=True,
|
||||||
|
# algo_namespace='simple_loop',
|
||||||
|
# base_currency='eth',
|
||||||
|
# live_graph=False
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('AssetFinderExchange')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('AssetFinderExchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class AssetFinderExchange(object):
|
class AssetFinderExchange(object):
|
||||||
@@ -41,9 +43,9 @@ class AssetFinderExchange(object):
|
|||||||
"""
|
"""
|
||||||
for sid in sids:
|
for sid in sids:
|
||||||
if sid in self._asset_cache:
|
if sid in self._asset_cache:
|
||||||
log.info('got asset from cache: {}'.format(sid))
|
log.debug('got asset from cache: {}'.format(sid))
|
||||||
else:
|
else:
|
||||||
log.info('fetching asset: {}'.format(sid))
|
log.debug('fetching asset: {}'.format(sid))
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
def lookup_symbol(self, symbol, exchange, as_of_date=None, fuzzy=False):
|
def lookup_symbol(self, symbol, exchange, as_of_date=None, fuzzy=False):
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import datetime
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -22,10 +22,10 @@ from catalyst.exchange.exchange_errors import (
|
|||||||
InvalidOrderStyle, OrderCancelError)
|
InvalidOrderStyle, OrderCancelError)
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
||||||
ExchangeStopLimitOrder, ExchangeStopOrder
|
ExchangeStopLimitOrder, ExchangeStopOrder
|
||||||
|
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
||||||
|
download_exchange_symbols, get_symbols_string
|
||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
from catalyst.protocol import Account
|
from catalyst.protocol import Account
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
|
||||||
download_exchange_symbols
|
|
||||||
|
|
||||||
# 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
|
||||||
@@ -33,7 +33,9 @@ requests.adapters.DEFAULT_RETRIES = 20
|
|||||||
|
|
||||||
BITFINEX_URL = 'https://api.bitfinex.com'
|
BITFINEX_URL = 'https://api.bitfinex.com'
|
||||||
|
|
||||||
log = Logger('Bitfinex')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('Bitfinex', level=LOG_LEVEL)
|
||||||
warning_logger = Logger('AlgoWarning')
|
warning_logger = Logger('AlgoWarning')
|
||||||
|
|
||||||
|
|
||||||
@@ -238,7 +240,7 @@ class Bitfinex(Exchange):
|
|||||||
# TODO: fetch account data and keep in cache
|
# TODO: fetch account data and keep in cache
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
def get_candles(self, freq, assets, bar_count=None,
|
||||||
start_dt=None, end_dt=None):
|
start_dt=None, end_dt=None):
|
||||||
"""
|
"""
|
||||||
Retrieve OHLVC candles from Bitfinex
|
Retrieve OHLVC candles from Bitfinex
|
||||||
@@ -253,33 +255,40 @@ class Bitfinex(Exchange):
|
|||||||
'1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D',
|
'1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D',
|
||||||
'1M'
|
'1M'
|
||||||
"""
|
"""
|
||||||
|
log.debug(
|
||||||
|
'retrieving {bars} {freq} candles on {exchange} from '
|
||||||
|
'{end_dt} for markets {symbols}, '.format(
|
||||||
|
bars=bar_count,
|
||||||
|
freq=freq,
|
||||||
|
exchange=self.name,
|
||||||
|
end_dt=end_dt,
|
||||||
|
symbols=get_symbols_string(assets)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
freq_match = re.match(r'([0-9].*)(m|h|d)', data_frequency, re.M | re.I)
|
allowed_frequencies = ['1T', '5T', '15T', '30T', '60T', '180T',
|
||||||
|
'360T', '720T', '1D', '7D', '14D', '30D']
|
||||||
|
if freq not in allowed_frequencies:
|
||||||
|
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||||
|
|
||||||
|
freq_match = re.match(r'([0-9].*)(T|H|D)', freq, re.M | re.I)
|
||||||
if freq_match:
|
if freq_match:
|
||||||
number = int(freq_match.group(1))
|
number = int(freq_match.group(1))
|
||||||
unit = freq_match.group(2)
|
unit = freq_match.group(2)
|
||||||
|
|
||||||
if unit == 'd':
|
if unit == 'T':
|
||||||
converted_unit = 'D'
|
if number in [60, 180, 360, 720]:
|
||||||
|
number = number / 60
|
||||||
|
converted_unit = 'h'
|
||||||
|
else:
|
||||||
|
converted_unit = 'm'
|
||||||
else:
|
else:
|
||||||
converted_unit = unit
|
converted_unit = unit
|
||||||
|
|
||||||
frequency = '{}{}'.format(number, converted_unit)
|
frequency = '{}{}'.format(number, converted_unit)
|
||||||
allowed_frequencies = ['1m', '5m', '15m', '30m', '1h', '3h', '6h',
|
|
||||||
'12h', '1D', '7D', '14D', '1M']
|
|
||||||
|
|
||||||
if frequency not in allowed_frequencies:
|
|
||||||
raise InvalidHistoryFrequencyError(
|
|
||||||
frequency=data_frequency
|
|
||||||
)
|
|
||||||
elif data_frequency == 'minute':
|
|
||||||
frequency = '1m'
|
|
||||||
elif data_frequency == 'daily':
|
|
||||||
frequency = '1D'
|
|
||||||
else:
|
else:
|
||||||
raise InvalidHistoryFrequencyError(
|
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||||
frequency=data_frequency
|
|
||||||
)
|
|
||||||
|
|
||||||
# Making sure that assets are iterable
|
# Making sure that assets are iterable
|
||||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
import pandas as pd
|
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 six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.bittrex.bittrex_api import Bittrex_api
|
from catalyst.exchange.bittrex.bittrex_api import Bittrex_api
|
||||||
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 InvalidHistoryFrequencyError, \
|
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
||||||
ExchangeRequestError, InvalidOrderStyle, OrderNotFound, OrderCancelError, \
|
ExchangeRequestError, InvalidOrderStyle, OrderNotFound, OrderCancelError, \
|
||||||
CreateOrderError
|
CreateOrderError
|
||||||
|
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
||||||
|
download_exchange_symbols, get_symbols_string
|
||||||
from catalyst.finance.execution import LimitOrder, StopLimitOrder
|
from catalyst.finance.execution import LimitOrder, StopLimitOrder
|
||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
|
||||||
download_exchange_symbols
|
|
||||||
|
|
||||||
log = Logger('Bittrex')
|
# TODO: consider using this: https://github.com/mondeja/bittrex_v2
|
||||||
|
|
||||||
|
log = Logger('Bittrex', level=LOG_LEVEL)
|
||||||
|
|
||||||
URL2 = 'https://bittrex.com/Api/v2.0'
|
URL2 = 'https://bittrex.com/Api/v2.0'
|
||||||
|
|
||||||
|
|
||||||
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.encode('UTF-8'))
|
self.api = Bittrex_api(key=key, secret=secret)
|
||||||
self.name = 'bittrex'
|
self.name = 'bittrex'
|
||||||
self.color = 'blue'
|
self.color = 'blue'
|
||||||
self.base_currency = base_currency
|
self.base_currency = base_currency
|
||||||
@@ -64,10 +68,10 @@ class Bittrex(Exchange):
|
|||||||
return exchange_symbol.lower()
|
return exchange_symbol.lower()
|
||||||
|
|
||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
|
balances = self.api.getbalances()
|
||||||
try:
|
try:
|
||||||
log.debug('retrieving wallet balances')
|
log.debug('retrieving wallet balances')
|
||||||
self.ask_request()
|
self.ask_request()
|
||||||
balances = self.api.getbalances()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
@@ -206,44 +210,59 @@ class Bittrex(Exchange):
|
|||||||
error=status['message']
|
error=status['message']
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
def get_candles(self, freq, assets, bar_count=None,
|
||||||
start_date=None):
|
start_dt=None, end_dt=None):
|
||||||
"""
|
"""
|
||||||
Supported Intervals
|
Supported Intervals
|
||||||
-------------------
|
-------------------
|
||||||
day, oneMin, fiveMin, thirtyMin, hour
|
day, oneMin, fiveMin, thirtyMin, hour
|
||||||
|
|
||||||
:param data_frequency:
|
:param freq:
|
||||||
:param assets:
|
:param assets:
|
||||||
:param bar_count:
|
:param bar_count:
|
||||||
|
:param start_dt
|
||||||
|
:param end_dt
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
log.info('retrieving candles')
|
|
||||||
|
|
||||||
if data_frequency == 'minute' or data_frequency == '1m':
|
# TODO: this has no effect at the moment
|
||||||
|
if end_dt is None:
|
||||||
|
end_dt = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
'retrieving {bars} {freq} candles on {exchange} from '
|
||||||
|
'{end_dt} for markets {symbols}, '.format(
|
||||||
|
bars=bar_count,
|
||||||
|
freq=freq,
|
||||||
|
exchange=self.name,
|
||||||
|
end_dt=end_dt,
|
||||||
|
symbols=get_symbols_string(assets)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if freq == '1T':
|
||||||
frequency = 'oneMin'
|
frequency = 'oneMin'
|
||||||
elif data_frequency == '5m':
|
elif freq == '5T':
|
||||||
frequency = 'fiveMin'
|
frequency = 'fiveMin'
|
||||||
elif data_frequency == '30m':
|
elif freq == '30T':
|
||||||
frequency = 'thirtyMin'
|
frequency = 'thirtyMin'
|
||||||
elif data_frequency == '1h':
|
elif freq == '60T':
|
||||||
frequency = 'hour'
|
frequency = 'hour'
|
||||||
elif data_frequency == 'daily' or data_frequency == '1D':
|
elif freq == '1D':
|
||||||
frequency = 'day'
|
frequency = 'day'
|
||||||
else:
|
else:
|
||||||
raise InvalidHistoryFrequencyError(
|
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||||
frequency=data_frequency
|
|
||||||
)
|
|
||||||
|
|
||||||
# Making sure that assets are iterable
|
# Making sure that assets are iterable
|
||||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||||
ohlc_map = dict()
|
|
||||||
for asset in asset_list:
|
for asset in asset_list:
|
||||||
|
end = int(time.mktime(end_dt.timetuple()))
|
||||||
url = '{url}/pub/market/GetTicks?marketName={symbol}' \
|
url = '{url}/pub/market/GetTicks?marketName={symbol}' \
|
||||||
'&tickInterval={frequency}&_=1499127220008'.format(
|
'&tickInterval={frequency}&_={end}'.format(
|
||||||
url=URL2,
|
url=URL2,
|
||||||
symbol=self.get_symbol(asset),
|
symbol=self.get_symbol(asset),
|
||||||
frequency=frequency
|
frequency=frequency,
|
||||||
|
end=end
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -271,9 +290,11 @@ class Bittrex(Exchange):
|
|||||||
return ohlc
|
return ohlc
|
||||||
|
|
||||||
ordered_candles = list(reversed(candles))
|
ordered_candles = list(reversed(candles))
|
||||||
|
ohlc_map = dict()
|
||||||
if bar_count is None:
|
if bar_count is None:
|
||||||
ohlc_map[asset] = ohlc_from_candle(ordered_candles[0])
|
ohlc_map[asset] = ohlc_from_candle(ordered_candles[0])
|
||||||
else:
|
else:
|
||||||
|
# TODO: optimize
|
||||||
ohlc_bars = []
|
ohlc_bars = []
|
||||||
for candle in ordered_candles[:bar_count]:
|
for candle in ordered_candles[:bar_count]:
|
||||||
ohlc = ohlc_from_candle(candle)
|
ohlc = ohlc_from_candle(candle)
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import time
|
|||||||
import hmac
|
import hmac
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from six.moves import urllib
|
|
||||||
|
|
||||||
# Workaround for backwards compatibility
|
# Workaround for backwards compatibility
|
||||||
# https://stackoverflow.com/questions/3745771/urllib-request-in-python-2-7
|
# https://stackoverflow.com/questions/3745771/urllib-request-in-python-2-7
|
||||||
|
from six.moves import urllib
|
||||||
urlopen = urllib.request.urlopen
|
urlopen = urllib.request.urlopen
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,10 @@ class Bittrex_api(object):
|
|||||||
if method not in self.public:
|
if method not in self.public:
|
||||||
url += '&apikey=' + self.key
|
url += '&apikey=' + self.key
|
||||||
url += '&nonce=' + str(int(time.time()))
|
url += '&nonce=' + str(int(time.time()))
|
||||||
signature = hmac.new(self.secret, url, hashlib.sha512).hexdigest()
|
|
||||||
|
signature = hmac.new(self.secret.encode('utf-8'),
|
||||||
|
url.encode('utf-8'),
|
||||||
|
hashlib.sha512).hexdigest()
|
||||||
headers = {'apisign': signature}
|
headers = {'apisign': signature}
|
||||||
else:
|
else:
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|||||||
+179
-117
@@ -7,22 +7,42 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from catalyst.data.bundles import from_bundle_ingest_dirname
|
|
||||||
from catalyst.data.bundles.core import download_without_progress
|
from catalyst.data.bundles.core import download_without_progress
|
||||||
from catalyst.exchange.exchange_errors import NoDataAvailableOnExchange
|
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_bundles_folder
|
from catalyst.exchange.exchange_utils import get_exchange_bundles_folder
|
||||||
from catalyst.utils.deprecate import deprecated
|
|
||||||
from catalyst.utils.paths import data_path
|
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
|
|
||||||
def get_date_from_ms(ms):
|
def get_date_from_ms(ms):
|
||||||
|
"""
|
||||||
|
The date from the number of miliseconds from the epoch.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ms: int
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
datetime
|
||||||
|
|
||||||
|
"""
|
||||||
return datetime.fromtimestamp(ms / 1000.0)
|
return datetime.fromtimestamp(ms / 1000.0)
|
||||||
|
|
||||||
|
|
||||||
def get_seconds_from_date(date):
|
def get_seconds_from_date(date):
|
||||||
|
"""
|
||||||
|
The number of seconds from the epoch.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
date: datetime
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
|
||||||
|
"""
|
||||||
epoch = datetime.utcfromtimestamp(0)
|
epoch = datetime.utcfromtimestamp(0)
|
||||||
epoch = epoch.replace(tzinfo=pytz.UTC)
|
epoch = epoch.replace(tzinfo=pytz.UTC)
|
||||||
|
|
||||||
@@ -33,16 +53,19 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
|
|||||||
"""
|
"""
|
||||||
Download and extract a bcolz bundle.
|
Download and extract a bcolz bundle.
|
||||||
|
|
||||||
:param exchange_name:
|
Parameters
|
||||||
:param symbol:
|
----------
|
||||||
:param data_frequency:
|
exchange_name: str
|
||||||
:param period:
|
symbol: str
|
||||||
:return:
|
data_frequency: str
|
||||||
|
period: str
|
||||||
|
|
||||||
Note:
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
Filename: bitfinex-daily-neo_eth-2017-10.tar.gz
|
Filename: bitfinex-daily-neo_eth-2017-10.tar.gz
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
root = get_exchange_bundles_folder(exchange_name)
|
root = get_exchange_bundles_folder(exchange_name)
|
||||||
name = '{exchange}-{frequency}-{symbol}-{period}'.format(
|
name = '{exchange}-{frequency}-{symbol}-{period}'.format(
|
||||||
exchange=exchange_name,
|
exchange=exchange_name,
|
||||||
@@ -67,32 +90,80 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
|
|||||||
|
|
||||||
|
|
||||||
def get_delta(periods, data_frequency):
|
def get_delta(periods, data_frequency):
|
||||||
|
"""
|
||||||
|
Get a time delta based on the specified data frequency.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
periods: int
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
timedelta
|
||||||
|
|
||||||
|
"""
|
||||||
return timedelta(minutes=periods) \
|
return timedelta(minutes=periods) \
|
||||||
if data_frequency == 'minute' else timedelta(days=periods)
|
if data_frequency == 'minute' else timedelta(days=periods)
|
||||||
|
|
||||||
|
|
||||||
def get_periods_range(start_dt, end_dt, data_frequency):
|
def get_periods_range(start_dt, end_dt, freq):
|
||||||
freq = 'T' if data_frequency == 'minute' else 'D'
|
"""
|
||||||
|
Get a date range for the specified parameters.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
freq: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DateTimeIndex
|
||||||
|
|
||||||
|
"""
|
||||||
|
if freq == 'minute':
|
||||||
|
freq = 'T'
|
||||||
|
|
||||||
|
elif freq == 'daily':
|
||||||
|
freq = 'D'
|
||||||
|
|
||||||
return pd.date_range(start_dt, end_dt, freq=freq)
|
return pd.date_range(start_dt, end_dt, freq=freq)
|
||||||
|
|
||||||
|
|
||||||
def get_periods(start_dt, end_dt, data_frequency):
|
def get_periods(start_dt, end_dt, freq):
|
||||||
delta = end_dt - start_dt
|
"""
|
||||||
|
The number of periods in the specified range.
|
||||||
|
|
||||||
if data_frequency == 'minute':
|
Parameters
|
||||||
delta_periods = delta.total_seconds() / 60
|
----------
|
||||||
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
freq: str
|
||||||
|
|
||||||
elif data_frequency == 'daily':
|
Returns
|
||||||
delta_periods = delta.total_seconds() / 60 / 60 / 24
|
-------
|
||||||
|
int
|
||||||
|
|
||||||
else:
|
"""
|
||||||
raise ValueError('frequency not supported')
|
return len(get_periods_range(start_dt, end_dt, freq))
|
||||||
|
|
||||||
return int(delta_periods)
|
|
||||||
|
|
||||||
|
|
||||||
def get_start_dt(end_dt, bar_count, data_frequency):
|
def get_start_dt(end_dt, bar_count, data_frequency):
|
||||||
|
"""
|
||||||
|
The start date based on specified end date and data frequency.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
end_dt: datetime
|
||||||
|
bar_count: int
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
datetime
|
||||||
|
|
||||||
|
"""
|
||||||
periods = bar_count
|
periods = bar_count
|
||||||
if periods > 1:
|
if periods > 1:
|
||||||
delta = get_delta(periods, data_frequency)
|
delta = get_delta(periods, data_frequency)
|
||||||
@@ -103,77 +174,102 @@ def get_start_dt(end_dt, bar_count, data_frequency):
|
|||||||
return start_dt
|
return start_dt
|
||||||
|
|
||||||
|
|
||||||
def get_adj_dates(start, end, assets, data_frequency):
|
def get_period_label(dt, data_frequency):
|
||||||
"""
|
"""
|
||||||
Contains a date range to the trading availability of the specified pairs.
|
The period label for the specified date and frequency.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt: datetime
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
:param start:
|
|
||||||
:param end:
|
|
||||||
:param assets:
|
|
||||||
:param data_frequency:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
earliest_trade = None
|
return '{}-{:02d}'.format(dt.year, dt.month) if data_frequency == 'minute' \
|
||||||
last_entry = None
|
else '{}'.format(dt.year)
|
||||||
for asset in assets:
|
|
||||||
if earliest_trade is None or earliest_trade > asset.start_date:
|
|
||||||
earliest_trade = asset.start_date
|
|
||||||
|
|
||||||
end_asset = asset.end_minute if data_frequency == 'minute' else \
|
|
||||||
asset.end_daily
|
|
||||||
if end_asset is not None and \
|
|
||||||
(last_entry is None or end_asset > last_entry):
|
|
||||||
last_entry = end_asset
|
|
||||||
|
|
||||||
if start is None or earliest_trade > start:
|
|
||||||
start = earliest_trade
|
|
||||||
|
|
||||||
if end is None or (last_entry is not None and end > last_entry):
|
|
||||||
end = last_entry
|
|
||||||
|
|
||||||
if end is None or start >= end:
|
|
||||||
raise NoDataAvailableOnExchange(
|
|
||||||
exchange=asset.exchange.title(),
|
|
||||||
symbol=[asset.symbol.encode('utf-8')],
|
|
||||||
data_frequency=data_frequency,
|
|
||||||
)
|
|
||||||
|
|
||||||
return start, end
|
|
||||||
|
|
||||||
|
|
||||||
def get_month_start_end(dt):
|
def get_month_start_end(dt, first_day=None, last_day=None):
|
||||||
"""
|
"""
|
||||||
Returns the first and last day of the month for the specified date.
|
The first and last day of the month for the specified date.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt: datetime
|
||||||
|
first_day: datetime
|
||||||
|
last_day: datetime
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
datetime, datetime
|
||||||
|
|
||||||
:param dt:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
month_range = calendar.monthrange(dt.year, dt.month)
|
month_range = calendar.monthrange(dt.year, dt.month)
|
||||||
|
|
||||||
|
if first_day:
|
||||||
|
month_start = first_day
|
||||||
|
else:
|
||||||
month_start = pd.to_datetime(datetime(
|
month_start = pd.to_datetime(datetime(
|
||||||
dt.year, dt.month, 1, 0, 0, 0, 0
|
dt.year, dt.month, 1, 0, 0, 0, 0
|
||||||
), utc=True)
|
), utc=True)
|
||||||
|
|
||||||
|
if last_day:
|
||||||
|
month_end = last_day
|
||||||
|
else:
|
||||||
month_end = pd.to_datetime(datetime(
|
month_end = pd.to_datetime(datetime(
|
||||||
dt.year, dt.month, month_range[1], 23, 59, 0, 0
|
dt.year, dt.month, month_range[1], 23, 59, 0, 0
|
||||||
), utc=True)
|
), utc=True)
|
||||||
|
|
||||||
|
if month_end > pd.Timestamp.utcnow():
|
||||||
|
month_end = pd.Timestamp.utcnow().floor('1D')
|
||||||
|
|
||||||
return month_start, month_end
|
return month_start, month_end
|
||||||
|
|
||||||
|
|
||||||
def get_year_start_end(dt):
|
def get_year_start_end(dt, first_day=None, last_day=None):
|
||||||
"""
|
"""
|
||||||
Returns the first and last day of the year for the specified date.
|
The first and last day of the year for the specified date.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
dt: datetime
|
||||||
|
first_day: datetime
|
||||||
|
last_day: datetime
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
datetime, datetime
|
||||||
|
|
||||||
:param dt:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
year_start = pd.to_datetime(date(dt.year, 1, 1), utc=True)
|
year_start = first_day if first_day \
|
||||||
year_end = pd.to_datetime(date(dt.year, 12, 31), utc=True)
|
else pd.to_datetime(date(dt.year, 1, 1), utc=True)
|
||||||
|
year_end = last_day if last_day \
|
||||||
|
else pd.to_datetime(date(dt.year, 12, 31), utc=True)
|
||||||
|
|
||||||
|
if year_end > pd.Timestamp.utcnow():
|
||||||
|
year_end = pd.Timestamp.utcnow().floor('1D')
|
||||||
|
|
||||||
return year_start, year_end
|
return year_start, year_end
|
||||||
|
|
||||||
|
|
||||||
def get_df_from_arrays(arrays, periods):
|
def get_df_from_arrays(arrays, periods):
|
||||||
|
"""
|
||||||
|
A DataFrame from the specified OHCLV arrays.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
arrays: Object
|
||||||
|
periods: DateTimeIndex
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
|
||||||
|
"""
|
||||||
ohlcv = dict()
|
ohlcv = dict()
|
||||||
for index, field in enumerate(
|
for index, field in enumerate(
|
||||||
['open', 'high', 'low', 'close', 'volume']):
|
['open', 'high', 'low', 'close', 'volume']):
|
||||||
@@ -191,64 +287,30 @@ def range_in_bundle(asset, start_dt, end_dt, reader):
|
|||||||
Evaluate whether price data of an asset is included has been ingested in
|
Evaluate whether price data of an asset is included has been ingested in
|
||||||
the exchange bundle for the given date range.
|
the exchange bundle for the given date range.
|
||||||
|
|
||||||
:param asset:
|
Parameters
|
||||||
:param start_dt:
|
----------
|
||||||
:param end_dt:
|
asset: TradingPair
|
||||||
:param reader:
|
start_dt: datetime
|
||||||
:return:
|
end_dt: datetime
|
||||||
|
reader: BcolzBarMinuteReader
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
has_data = True
|
has_data = True
|
||||||
if has_data and reader is not None:
|
dates = [start_dt, end_dt]
|
||||||
|
|
||||||
|
while dates and has_data:
|
||||||
try:
|
try:
|
||||||
start_close = \
|
dt = dates.pop(0)
|
||||||
reader.get_value(asset.sid, start_dt, 'close')
|
close = reader.get_value(asset.sid, dt, 'close')
|
||||||
|
|
||||||
if np.isnan(start_close):
|
if np.isnan(close):
|
||||||
has_data = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
end_close = reader.get_value(asset.sid, end_dt, 'close')
|
|
||||||
|
|
||||||
if np.isnan(end_close):
|
|
||||||
has_data = False
|
has_data = False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
has_data = False
|
has_data = False
|
||||||
|
|
||||||
else:
|
|
||||||
has_data = False
|
|
||||||
|
|
||||||
return has_data
|
return has_data
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
|
||||||
def find_most_recent_time(bundle_name):
|
|
||||||
"""
|
|
||||||
Find most recent "time folder" for a given bundle.
|
|
||||||
|
|
||||||
:param bundle_name:
|
|
||||||
The name of the targeted bundle.
|
|
||||||
|
|
||||||
:return folder:
|
|
||||||
The name of the time folder.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
bundle_folders = os.listdir(
|
|
||||||
data_path([bundle_name]),
|
|
||||||
)
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
most_recent_bundle = dict()
|
|
||||||
for folder in bundle_folders:
|
|
||||||
date = from_bundle_ingest_dirname(folder)
|
|
||||||
if not most_recent_bundle or date > \
|
|
||||||
most_recent_bundle[most_recent_bundle.keys()[0]]:
|
|
||||||
most_recent_bundle = dict()
|
|
||||||
most_recent_bundle[folder] = date
|
|
||||||
|
|
||||||
if most_recent_bundle:
|
|
||||||
return most_recent_bundle.keys()[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|||||||
+264
-109
@@ -1,5 +1,4 @@
|
|||||||
import abc
|
import abc
|
||||||
import re
|
|
||||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@@ -9,22 +8,25 @@ 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.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, \
|
||||||
get_delta, get_periods, get_adj_dates
|
get_delta, get_periods, get_periods_range
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||||
InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||||
InvalidHistoryFrequencyError, MismatchingFrequencyError, \
|
PricingDataNotLoadedError, \
|
||||||
BundleNotFoundError, NoDataAvailableOnExchange, PricingDataNotLoadedError
|
NoDataAvailableOnExchange
|
||||||
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
||||||
ExchangeLimitOrder, ExchangeStopOrder
|
ExchangeLimitOrder, ExchangeStopOrder
|
||||||
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
|
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
|
||||||
from catalyst.finance.order import ORDER_STATUS
|
from catalyst.finance.order import ORDER_STATUS
|
||||||
from catalyst.finance.transaction import Transaction
|
from catalyst.finance.transaction import Transaction
|
||||||
|
from catalyst.utils.deprecate import deprecated
|
||||||
|
|
||||||
log = Logger('Exchange')
|
log = Logger('Exchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class Exchange:
|
class Exchange:
|
||||||
@@ -50,9 +52,11 @@ class Exchange:
|
|||||||
@property
|
@property
|
||||||
def portfolio(self):
|
def portfolio(self):
|
||||||
"""
|
"""
|
||||||
Return the Portfolio
|
The exchange portfolio
|
||||||
|
|
||||||
:return:
|
Returns
|
||||||
|
-------
|
||||||
|
ExchangePortfolio
|
||||||
"""
|
"""
|
||||||
if self._portfolio is None:
|
if self._portfolio is None:
|
||||||
self._portfolio = ExchangePortfolio(
|
self._portfolio = ExchangePortfolio(
|
||||||
@@ -70,6 +74,22 @@ class Exchange:
|
|||||||
def time_skew(self):
|
def time_skew(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_open(self, dt):
|
||||||
|
"""
|
||||||
|
Is the exchange open
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt: Timestamp
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: implement for each exchange.
|
||||||
|
return True
|
||||||
|
|
||||||
def ask_request(self):
|
def ask_request(self):
|
||||||
"""
|
"""
|
||||||
Asks permission to issue a request to the exchange.
|
Asks permission to issue a request to the exchange.
|
||||||
@@ -78,7 +98,9 @@ class Exchange:
|
|||||||
The application will pause if the maximum requests per minute
|
The application will pause if the maximum requests per minute
|
||||||
permitted by the exchange is exceeded.
|
permitted by the exchange is exceeded.
|
||||||
|
|
||||||
:return boolean:
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
now = pd.Timestamp.utcnow()
|
now = pd.Timestamp.utcnow()
|
||||||
@@ -87,7 +109,7 @@ class Exchange:
|
|||||||
self.request_cpt[now] = 0
|
self.request_cpt[now] = 0
|
||||||
return True
|
return True
|
||||||
|
|
||||||
cpt_date = self.request_cpt.keys()[0]
|
cpt_date = list(self.request_cpt.keys())[0]
|
||||||
cpt = self.request_cpt[cpt_date]
|
cpt = self.request_cpt[cpt_date]
|
||||||
|
|
||||||
if now > cpt_date + timedelta(minutes=1):
|
if now > cpt_date + timedelta(minutes=1):
|
||||||
@@ -110,10 +132,16 @@ class Exchange:
|
|||||||
|
|
||||||
def get_symbol(self, asset):
|
def get_symbol(self, asset):
|
||||||
"""
|
"""
|
||||||
Get the exchange specific symbol of the given asset.
|
The the exchange specific symbol of the specified market.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
asset: TradingPair
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
:param asset: Asset
|
|
||||||
:return: symbol: str
|
|
||||||
"""
|
"""
|
||||||
symbol = None
|
symbol = None
|
||||||
|
|
||||||
@@ -131,17 +159,34 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Get a list of symbols corresponding to each given asset.
|
Get a list of symbols corresponding to each given asset.
|
||||||
|
|
||||||
:param assets: Asset[]
|
Parameters
|
||||||
:return:
|
----------
|
||||||
|
assets: list[TradingPair]
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[str]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
symbols = []
|
symbols = []
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
symbols.append(self.get_symbol(asset))
|
symbols.append(self.get_symbol(asset))
|
||||||
|
|
||||||
return symbols
|
return symbols
|
||||||
|
|
||||||
def get_assets(self, symbols=None):
|
def get_assets(self, symbols=None):
|
||||||
|
"""
|
||||||
|
The list of markets for the specified symbols.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
symbols: list[str]
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[TradingPair]
|
||||||
|
|
||||||
|
"""
|
||||||
assets = []
|
assets = []
|
||||||
|
|
||||||
if symbols is not None:
|
if symbols is not None:
|
||||||
@@ -156,9 +201,16 @@ class Exchange:
|
|||||||
|
|
||||||
def get_asset(self, symbol):
|
def get_asset(self, symbol):
|
||||||
"""
|
"""
|
||||||
Find an Asset on the current exchange based on its Catalyst symbol
|
The market for the specified symbol.
|
||||||
:param symbol: the [target]_[base] currency pair symbol
|
|
||||||
:return: Asset
|
Parameters
|
||||||
|
----------
|
||||||
|
symbol: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
TradingPair
|
||||||
|
|
||||||
"""
|
"""
|
||||||
asset = None
|
asset = None
|
||||||
|
|
||||||
@@ -167,8 +219,10 @@ class Exchange:
|
|||||||
asset = self.assets[key]
|
asset = self.assets[key]
|
||||||
|
|
||||||
if not asset:
|
if not asset:
|
||||||
supported_symbols = [pair.symbol.encode('utf-8') for pair in
|
supported_symbols = [
|
||||||
self.assets.values()]
|
pair.symbol for pair in list(self.assets.values())
|
||||||
|
]
|
||||||
|
|
||||||
raise SymbolNotFoundOnExchange(
|
raise SymbolNotFoundOnExchange(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
exchange=self.name.title(),
|
exchange=self.name.title(),
|
||||||
@@ -187,7 +241,6 @@ class Exchange:
|
|||||||
currency pair symbol. The universal symbol is contained in the
|
currency pair symbol. The universal symbol is contained in the
|
||||||
'symbol' attribute of each asset.
|
'symbol' attribute of each asset.
|
||||||
|
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
The sid of each asset is calculated based on a numeric hash of the
|
The sid of each asset is calculated based on a numeric hash of the
|
||||||
@@ -196,8 +249,8 @@ class Exchange:
|
|||||||
|
|
||||||
This method can be overridden if an exchange offers equivalent data
|
This method can be overridden if an exchange offers equivalent data
|
||||||
via its api.
|
via its api.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
symbol_map = self.fetch_symbol_map()
|
symbol_map = self.fetch_symbol_map()
|
||||||
for exchange_symbol in symbol_map:
|
for exchange_symbol in symbol_map:
|
||||||
asset = symbol_map[exchange_symbol]
|
asset = symbol_map[exchange_symbol]
|
||||||
@@ -258,8 +311,10 @@ class Exchange:
|
|||||||
For each executed order found, create a transaction and apply to the
|
For each executed order found, create a transaction and apply to the
|
||||||
Portfolio.
|
Portfolio.
|
||||||
|
|
||||||
:return:
|
Returns
|
||||||
transactions: Transaction[]
|
-------
|
||||||
|
list[Transaction]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
transactions = list()
|
transactions = list()
|
||||||
if self.portfolio.open_orders:
|
if self.portfolio.open_orders:
|
||||||
@@ -342,17 +397,24 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Similar to 'get_spot_value' but for a single asset
|
Similar to 'get_spot_value' but for a single asset
|
||||||
|
|
||||||
Note
|
Notes
|
||||||
----
|
-----
|
||||||
We're writing each minute bar to disk using zipline's machinery.
|
We're writing each minute bar to disk using zipline's machinery.
|
||||||
This is especially useful when running multiple algorithms
|
This is especially useful when running multiple algorithms
|
||||||
concurrently. By using local data when possible, we try to reaching
|
concurrently. By using local data when possible, we try to reaching
|
||||||
request limits on exchanges.
|
request limits on exchanges.
|
||||||
|
|
||||||
:param asset:
|
Parameters
|
||||||
:param field:
|
----------
|
||||||
:param data_frequency:
|
asset: TradingPair
|
||||||
:return value: The spot value of the given asset / field
|
field: str
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The spot value of the given asset / field
|
||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug(
|
log.debug(
|
||||||
'fetching spot value {field} for symbol {symbol}'.format(
|
'fetching spot value {field} for symbol {symbol}'.format(
|
||||||
@@ -361,7 +423,8 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ohlc = self.get_candles(data_frequency, asset)
|
freq = '1T' if data_frequency == 'minute' else '1D'
|
||||||
|
ohlc = self.get_candles(freq, asset)
|
||||||
if field not in ohlc:
|
if field not in ohlc:
|
||||||
raise KeyError('Invalid column: %s' % field)
|
raise KeyError('Invalid column: %s' % field)
|
||||||
|
|
||||||
@@ -371,29 +434,42 @@ class Exchange:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def get_series_from_candles(self, candles, start_dt, end_dt,
|
def get_series_from_candles(self, candles, start_dt, end_dt,
|
||||||
field, previous_value=None):
|
data_frequency, field, previous_value=None):
|
||||||
"""
|
"""
|
||||||
Get a series of field data for the specified candles.
|
Get a series of field data for the specified candles.
|
||||||
|
|
||||||
:param candles:
|
Parameters
|
||||||
:param start_dt:
|
----------
|
||||||
:param end_dt:
|
candles: list[dict[str, float]]
|
||||||
:param field:
|
start_dt: datetime
|
||||||
:param previous_value:
|
end_dt: datetime
|
||||||
:return:
|
data_frequency: str
|
||||||
"""
|
field: str
|
||||||
|
previous_value: float
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Series
|
||||||
|
|
||||||
|
"""
|
||||||
dates = [candle['last_traded'] for candle in candles]
|
dates = [candle['last_traded'] for candle in candles]
|
||||||
values = [candle[field] for candle in candles]
|
values = [candle[field] for candle in candles]
|
||||||
|
|
||||||
periods = pd.date_range(start_dt, end_dt)
|
|
||||||
series = pd.Series(values, index=dates)
|
series = pd.Series(values, index=dates)
|
||||||
|
|
||||||
series.reindex(periods, method='ffill', fill_value=previous_value)
|
periods = get_periods_range(
|
||||||
|
start_dt, end_dt, data_frequency
|
||||||
|
)
|
||||||
|
# TODO: ensure that this working as expected, if not use fillna
|
||||||
|
series = series.reindex(
|
||||||
|
periods,
|
||||||
|
method='ffill',
|
||||||
|
fill_value=previous_value,
|
||||||
|
)
|
||||||
|
|
||||||
return series
|
return series
|
||||||
|
|
||||||
def get_history_window(self,
|
@deprecated
|
||||||
|
def get_history_window_direct(self,
|
||||||
assets,
|
assets,
|
||||||
end_dt,
|
end_dt,
|
||||||
bar_count,
|
bar_count,
|
||||||
@@ -408,10 +484,11 @@ class Exchange:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
assets : list of catalyst.data.Asset objects
|
assets : list[TradingPair]
|
||||||
The assets whose data is desired.
|
The assets whose data is desired.
|
||||||
|
|
||||||
end_dt: not applicable to cryptocurrencies
|
end_dt: datetime
|
||||||
|
The date of the last bar
|
||||||
|
|
||||||
bar_count: int
|
bar_count: int
|
||||||
The number of bars desired.
|
The number of bars desired.
|
||||||
@@ -433,28 +510,79 @@ class Exchange:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
DataFrame
|
||||||
A dataframe containing the requested data.
|
A dataframe containing the requested data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||||
|
|
||||||
freq_match = re.match(r'([0-9].*)(m|M|d|D)', frequency, re.M | re.I)
|
# The get_history method supports multiple asset
|
||||||
if freq_match:
|
candles = self.get_candles(
|
||||||
candle_size = int(freq_match.group(1))
|
data_frequency=frequency,
|
||||||
unit = freq_match.group(2)
|
assets=assets,
|
||||||
|
bar_count=bar_count,
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt
|
||||||
|
)
|
||||||
|
candle_series = self.get_series_from_candles(
|
||||||
|
candles=candles,
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt,
|
||||||
|
data_frequency=frequency,
|
||||||
|
field=field,
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
df = pd.DataFrame(candle_series)
|
||||||
raise InvalidHistoryFrequencyError(frequency)
|
return df
|
||||||
|
|
||||||
if unit.lower() == 'd':
|
def get_history_window(self,
|
||||||
if data_frequency == 'minute':
|
assets,
|
||||||
data_frequency = 'daily'
|
end_dt,
|
||||||
|
bar_count,
|
||||||
|
frequency,
|
||||||
|
field,
|
||||||
|
data_frequency=None,
|
||||||
|
ffill=True):
|
||||||
|
|
||||||
elif unit.lower() == 'm':
|
"""
|
||||||
if data_frequency == 'daily':
|
Public API method that returns a dataframe containing the requested
|
||||||
data_frequency = 'minute'
|
history window. Data is fully adjusted.
|
||||||
|
|
||||||
else:
|
Parameters
|
||||||
raise InvalidHistoryFrequencyError(frequency)
|
----------
|
||||||
|
assets : list[TradingPair]
|
||||||
|
The assets whose data is desired.
|
||||||
|
|
||||||
|
end_dt: datetime
|
||||||
|
The date of the last bar.
|
||||||
|
|
||||||
|
bar_count: int
|
||||||
|
The number of bars desired.
|
||||||
|
|
||||||
|
frequency: string
|
||||||
|
"1d" or "1m"
|
||||||
|
|
||||||
|
field: string
|
||||||
|
The desired field of the asset.
|
||||||
|
|
||||||
|
data_frequency: string
|
||||||
|
The frequency of the data to query; i.e. whether the data is
|
||||||
|
'daily' or 'minute' bars.
|
||||||
|
|
||||||
|
# TODO: fill how?
|
||||||
|
ffill: boolean
|
||||||
|
Forward-fill missing values. Only has effect if field
|
||||||
|
is 'price'.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
A dataframe containing the requested data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
freq, candle_size, unit, data_frequency = get_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(
|
||||||
@@ -464,7 +592,7 @@ class Exchange:
|
|||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency
|
data_frequency=data_frequency
|
||||||
)
|
)
|
||||||
except PricingDataNotLoadedError:
|
except (PricingDataNotLoadedError, NoDataAvailableOnExchange):
|
||||||
series = dict()
|
series = dict()
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
@@ -477,24 +605,30 @@ class Exchange:
|
|||||||
series[asset].index[-1] + get_delta(1, data_frequency) \
|
series[asset].index[-1] + get_delta(1, data_frequency) \
|
||||||
if asset in series else start_dt
|
if asset in series else start_dt
|
||||||
|
|
||||||
trailing_bar_count = \
|
|
||||||
get_periods(trailing_dt, end_dt, data_frequency)
|
|
||||||
|
|
||||||
# The get_history method supports multiple asset
|
# The get_history method supports multiple asset
|
||||||
|
# Use the original frequency to let each api optimize
|
||||||
|
# the size of result sets
|
||||||
|
trailing_bar_count = get_periods(
|
||||||
|
trailing_dt, end_dt, freq
|
||||||
|
)
|
||||||
candles = self.get_candles(
|
candles = self.get_candles(
|
||||||
data_frequency=data_frequency,
|
freq=freq,
|
||||||
assets=asset,
|
assets=asset,
|
||||||
bar_count=trailing_bar_count,
|
bar_count=trailing_bar_count,
|
||||||
|
start_dt=start_dt,
|
||||||
end_dt=end_dt
|
end_dt=end_dt
|
||||||
)
|
)
|
||||||
|
|
||||||
last_value = series[asset].iloc(0) if asset in series \
|
last_value = series[asset].iloc(0) if asset in series \
|
||||||
else np.nan
|
else np.nan
|
||||||
|
|
||||||
|
# Create a series with the common data_frequency, ffill
|
||||||
|
# missing values
|
||||||
candle_series = self.get_series_from_candles(
|
candle_series = self.get_series_from_candles(
|
||||||
candles=candles,
|
candles=candles,
|
||||||
start_dt=trailing_dt,
|
start_dt=trailing_dt,
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
|
data_frequency=data_frequency,
|
||||||
field=field,
|
field=field,
|
||||||
previous_value=last_value
|
previous_value=last_value
|
||||||
)
|
)
|
||||||
@@ -505,23 +639,9 @@ class Exchange:
|
|||||||
else:
|
else:
|
||||||
series[asset] = candle_series
|
series[asset] = candle_series
|
||||||
|
|
||||||
df = pd.DataFrame(series)
|
df = resample_history_df(pd.DataFrame(series), freq, field)
|
||||||
|
# TODO: consider this more carefully
|
||||||
if candle_size > 1:
|
df.dropna(inplace=True)
|
||||||
if field == 'open':
|
|
||||||
agg = 'first'
|
|
||||||
elif field == 'high':
|
|
||||||
agg = 'max'
|
|
||||||
elif field == 'low':
|
|
||||||
agg = 'min'
|
|
||||||
elif field == 'close':
|
|
||||||
agg = 'last'
|
|
||||||
elif field == 'volume':
|
|
||||||
agg = 'sum'
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid field.')
|
|
||||||
|
|
||||||
df = df.resample('{}T'.format(candle_size)).agg(agg)
|
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@@ -530,7 +650,6 @@ class Exchange:
|
|||||||
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.
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
||||||
balances = self.get_balances()
|
balances = self.get_balances()
|
||||||
@@ -552,7 +671,7 @@ class Exchange:
|
|||||||
portfolio.starting_cash = portfolio.cash
|
portfolio.starting_cash = portfolio.cash
|
||||||
|
|
||||||
if portfolio.positions:
|
if portfolio.positions:
|
||||||
assets = portfolio.positions.keys()
|
assets = list(portfolio.positions.keys())
|
||||||
tickers = self.tickers(assets)
|
tickers = self.tickers(assets)
|
||||||
|
|
||||||
portfolio.positions_value = 0.0
|
portfolio.positions_value = 0.0
|
||||||
@@ -574,16 +693,20 @@ class Exchange:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
asset : Asset
|
asset : TradingPair
|
||||||
The asset that this order is for.
|
The asset that this order is for.
|
||||||
|
|
||||||
amount : int
|
amount : int
|
||||||
The amount of shares to order. If ``amount`` is positive, this is
|
The amount of shares to order. If ``amount`` is positive, this is
|
||||||
the number of shares to buy or cover. If ``amount`` is negative,
|
the number of shares to buy or cover. If ``amount`` is negative,
|
||||||
this is the number of shares to sell or short.
|
this is the number of shares to sell or short.
|
||||||
|
|
||||||
limit_price : float, optional
|
limit_price : float, optional
|
||||||
The limit price for the order.
|
The limit price for the order.
|
||||||
|
|
||||||
stop_price : float, optional
|
stop_price : float, optional
|
||||||
The stop price for the order.
|
The stop price for the order.
|
||||||
|
|
||||||
style : ExecutionStyle, optional
|
style : ExecutionStyle, optional
|
||||||
The execution style for the order.
|
The execution style for the order.
|
||||||
|
|
||||||
@@ -608,6 +731,7 @@ class Exchange:
|
|||||||
:class:`catalyst.finance.execution.ExecutionStyle`
|
:class:`catalyst.finance.execution.ExecutionStyle`
|
||||||
:func:`catalyst.api.order_value`
|
:func:`catalyst.api.order_value`
|
||||||
:func:`catalyst.api.order_percent`
|
:func:`catalyst.api.order_percent`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if amount == 0:
|
if amount == 0:
|
||||||
log.warn('skipping order amount of 0')
|
log.warn('skipping order amount of 0')
|
||||||
@@ -657,8 +781,12 @@ class Exchange:
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
"""
|
"""
|
||||||
Retrieve wallet balances for the exchange
|
Retrieve wallet balances for the exchange.
|
||||||
:return balances: A dict of currency => available balance
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[TradingPair, float]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -667,17 +795,25 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Place an order on the exchange.
|
Place an order on the exchange.
|
||||||
|
|
||||||
:param asset : Asset
|
Parameters
|
||||||
The asset that this order is for.
|
----------
|
||||||
:param amount : int
|
asset: TradingPair
|
||||||
|
The target market.
|
||||||
|
|
||||||
|
amount: float
|
||||||
The amount of shares to order. If ``amount`` is positive, this is
|
The amount of shares to order. If ``amount`` is positive, this is
|
||||||
the number of shares to buy or cover. If ``amount`` is negative,
|
the number of shares to buy or cover. If ``amount`` is negative,
|
||||||
this is the number of shares to sell or short.
|
this is the number of shares to sell or short.
|
||||||
:param style : ExecutionStyle
|
|
||||||
The execution style for the order.
|
is_buy: bool
|
||||||
:param is_buy: boolean
|
|
||||||
Is it a buy order?
|
Is it a buy order?
|
||||||
:return:
|
|
||||||
|
style: ExecutionStyle
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Order
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -732,23 +868,32 @@ class Exchange:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
def get_candles(self, freq, assets, bar_count=None,
|
||||||
start_dt=None, end_dt=None):
|
start_dt=None, end_dt=None):
|
||||||
"""
|
"""
|
||||||
Retrieve OHLCV candles for the given assets
|
Retrieve OHLCV candles for the given assets
|
||||||
|
|
||||||
:param data_frequency:
|
Parameters
|
||||||
The candle frequency: minute or daily
|
----------
|
||||||
:param assets: list[TradingPair]
|
freq: str
|
||||||
|
The frequency alias per convention:
|
||||||
|
http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
|
||||||
|
|
||||||
|
assets: list[TradingPair]
|
||||||
The targeted assets.
|
The targeted assets.
|
||||||
:param bar_count:
|
|
||||||
|
bar_count: int
|
||||||
The number of bar desired. (default 1)
|
The number of bar desired. (default 1)
|
||||||
:param end_dt: datetime, optional
|
|
||||||
|
end_dt: datetime, optional
|
||||||
The last bar date.
|
The last bar date.
|
||||||
:param start_dt: datetime, optional
|
|
||||||
|
start_dt: datetime, optional
|
||||||
The first bar date.
|
The first bar date.
|
||||||
|
|
||||||
:return dict[TradingPair, dict[str, Object]]: OHLCV data
|
Returns
|
||||||
|
-------
|
||||||
|
dict[TradingPair, dict[str, Object]]
|
||||||
A dictionary of OHLCV candles. Each TradingPair instance is
|
A dictionary of OHLCV candles. Each TradingPair instance is
|
||||||
mapped to a list of dictionaries with this structure:
|
mapped to a list of dictionaries with this structure:
|
||||||
open: float
|
open: float
|
||||||
@@ -768,8 +913,14 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Retrieve current tick data for the given assets
|
Retrieve current tick data for the given assets
|
||||||
|
|
||||||
:param assets:
|
Parameters
|
||||||
:return:
|
----------
|
||||||
|
assets: list[TradingPair]
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[dict[str, float]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -777,19 +928,23 @@ class Exchange:
|
|||||||
def get_account(self):
|
def get_account(self):
|
||||||
"""
|
"""
|
||||||
Retrieve the account parameters.
|
Retrieve the account parameters.
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_orderbook(self, asset, order_type):
|
def get_orderbook(self, asset, order_type, limit):
|
||||||
"""
|
"""
|
||||||
Retrieve the the orderbook for the given trading pair.
|
Retrieve the the orderbook for the given trading pair.
|
||||||
|
|
||||||
:param asset: TradingPair
|
Parameters
|
||||||
:param order_type: str
|
----------
|
||||||
|
asset: TradingPair
|
||||||
|
order_type: str
|
||||||
The type of orders: bid, ask or all
|
The type of orders: bid, ask or all
|
||||||
|
limit: int
|
||||||
|
|
||||||
:return:
|
Returns
|
||||||
|
-------
|
||||||
|
list[dict[str, float]
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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.data.minute_bars import BcolzMinuteBarWriter, \
|
from catalyst.data.minute_bars import BcolzMinuteBarWriter, \
|
||||||
BcolzMinuteBarReader
|
BcolzMinuteBarReader
|
||||||
from catalyst.errors import OrderInBeforeTradingStart
|
from catalyst.errors import OrderInBeforeTradingStart
|
||||||
@@ -51,10 +52,10 @@ from catalyst.utils.api_support import (
|
|||||||
disallowed_in_before_trading_start)
|
disallowed_in_before_trading_start)
|
||||||
from catalyst.utils.input_validation import error_keywords, ensure_upper_case, \
|
from catalyst.utils.input_validation import error_keywords, ensure_upper_case, \
|
||||||
expect_types
|
expect_types
|
||||||
from catalyst.utils.preprocess import preprocess
|
|
||||||
from catalyst.utils.math_utils import round_nearest
|
from catalyst.utils.math_utils import round_nearest
|
||||||
|
from catalyst.utils.preprocess import preprocess
|
||||||
|
|
||||||
log = logbook.Logger('exchange_algorithm')
|
log = logbook.Logger('exchange_algorithm', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class ExchangeAlgorithmExecutor(AlgorithmSimulator):
|
class ExchangeAlgorithmExecutor(AlgorithmSimulator):
|
||||||
@@ -112,7 +113,7 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
|||||||
else self.sim_params.end_session
|
else self.sim_params.end_session
|
||||||
|
|
||||||
if exchange_name is None:
|
if exchange_name is None:
|
||||||
exchange = self.exchanges.values()[0]
|
exchange = list(self.exchanges.values())[0]
|
||||||
else:
|
else:
|
||||||
exchange = self.exchanges[exchange_name]
|
exchange = self.exchanges[exchange_name]
|
||||||
|
|
||||||
@@ -126,7 +127,13 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm):
|
|||||||
"""
|
"""
|
||||||
Creates a dictionary representing the state of the tracker.
|
Creates a dictionary representing the state of the tracker.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
I rewrote this in an attempt to better control the stats.
|
I rewrote this in an attempt to better control the stats.
|
||||||
I don't want things to happen magically through complex logic
|
I don't want things to happen magically through complex logic
|
||||||
pertaining to backtesting.
|
pertaining to backtesting.
|
||||||
@@ -295,6 +302,18 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.exchange.minute_reader = BcolzMinuteBarReader(root)
|
self.exchange.minute_reader = BcolzMinuteBarReader(root)
|
||||||
|
|
||||||
def signal_handler(self, signal, frame):
|
def signal_handler(self, signal, frame):
|
||||||
|
"""
|
||||||
|
Handles the keyboard interruption signal.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
signal
|
||||||
|
frame
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
if self._analyze is None:
|
if self._analyze is None:
|
||||||
@@ -383,7 +402,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
"""
|
"""
|
||||||
We skip the entire performance tracker business and update the
|
We skip the entire performance tracker business and update the
|
||||||
portfolio directly.
|
portfolio directly.
|
||||||
:return:
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ExchangePortfolio
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: build cumulative portfolio
|
# TODO: build cumulative portfolio
|
||||||
return self.perf_tracker.get_portfolio(False)
|
return self.perf_tracker.get_portfolio(False)
|
||||||
@@ -449,6 +472,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def add_pnl_stats(self, period_stats):
|
def add_pnl_stats(self, period_stats):
|
||||||
|
"""
|
||||||
|
Save p&l stats.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
period_stats
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
starting = period_stats['starting_cash']
|
starting = period_stats['starting_cash']
|
||||||
current = period_stats['portfolio_value']
|
current = period_stats['portfolio_value']
|
||||||
appreciation = (current / starting) - 1
|
appreciation = (current / starting) - 1
|
||||||
@@ -465,6 +499,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats)
|
save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats)
|
||||||
|
|
||||||
def add_custom_signals_stats(self, period_stats):
|
def add_custom_signals_stats(self, period_stats):
|
||||||
|
"""
|
||||||
|
Save custom signals stats.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
period_stats
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
log.debug('adding custom signals stats: {}'.format(self.recorded_vars))
|
log.debug('adding custom signals stats: {}'.format(self.recorded_vars))
|
||||||
df = pd.DataFrame(
|
df = pd.DataFrame(
|
||||||
data=[self.recorded_vars],
|
data=[self.recorded_vars],
|
||||||
@@ -476,6 +521,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.custom_signals_stats)
|
self.custom_signals_stats)
|
||||||
|
|
||||||
def add_exposure_stats(self, period_stats):
|
def add_exposure_stats(self, period_stats):
|
||||||
|
"""
|
||||||
|
Save exposure stats.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
period_stats
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
data = dict(
|
data = dict(
|
||||||
long_exposure=period_stats['long_exposure'],
|
long_exposure=period_stats['long_exposure'],
|
||||||
base_currency=period_stats['ending_cash']
|
base_currency=period_stats['ending_cash']
|
||||||
@@ -492,6 +548,14 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.exposure_stats)
|
self.exposure_stats)
|
||||||
|
|
||||||
def handle_data(self, data):
|
def handle_data(self, data):
|
||||||
|
"""
|
||||||
|
Wrapper around the handle_data method of each algo.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data
|
||||||
|
|
||||||
|
"""
|
||||||
if not self.is_running:
|
if not self.is_running:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -523,7 +587,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.add_pnl_stats(minute_stats)
|
self.add_pnl_stats(minute_stats)
|
||||||
if self.recorded_vars:
|
if self.recorded_vars:
|
||||||
self.add_custom_signals_stats(minute_stats)
|
self.add_custom_signals_stats(minute_stats)
|
||||||
recorded_cols = self.recorded_vars.keys()
|
recorded_cols = list(self.recorded_vars.keys())
|
||||||
else:
|
else:
|
||||||
recorded_cols = None
|
recorded_cols = None
|
||||||
|
|
||||||
@@ -555,6 +619,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn('unable to calculate performance: {}'.format(e))
|
log.warn('unable to calculate performance: {}'.format(e))
|
||||||
|
|
||||||
|
# TODO: pickle does not seem to work in python 3
|
||||||
try:
|
try:
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
@@ -617,15 +682,16 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
The cumulative portfolio does not contain open orders but exchange
|
The cumulative portfolio does not contain open orders but exchange
|
||||||
portfolios do.
|
portfolios do.
|
||||||
|
|
||||||
:param asset: TradingPair
|
Parameters
|
||||||
:param amount: float
|
----------
|
||||||
:param limit_price: float
|
asset: TradingPair
|
||||||
:param stop_price: float
|
amount: float
|
||||||
:param style: Style
|
limit_price: float
|
||||||
:return order: Order
|
stop_price: float
|
||||||
|
style: Style
|
||||||
|
order: Order
|
||||||
The catalyst order object or None
|
The catalyst order object or None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
amount, style = self._calculate_order(asset, amount,
|
amount, style = self._calculate_order(asset, amount,
|
||||||
limit_price, stop_price,
|
limit_price, stop_price,
|
||||||
style)
|
style)
|
||||||
@@ -687,15 +753,53 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
'get_open_orders. Use `asset` instead.')
|
'get_open_orders. Use `asset` instead.')
|
||||||
@api_method
|
@api_method
|
||||||
def get_open_orders(self, asset=None):
|
def get_open_orders(self, asset=None):
|
||||||
|
"""Retrieve all of the current open orders.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
asset : Asset
|
||||||
|
If passed and not None, return only the open orders for the given
|
||||||
|
asset instead of all open orders.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
open_orders : dict[list[Order]] or list[Order]
|
||||||
|
If no asset is passed this will return a dict mapping Assets
|
||||||
|
to a list containing all the open orders for the asset.
|
||||||
|
If an asset is passed then this will return a list of the open
|
||||||
|
orders for this asset.
|
||||||
|
"""
|
||||||
return self._get_open_orders(asset)
|
return self._get_open_orders(asset)
|
||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
def get_order(self, order_id, exchange_name):
|
def get_order(self, order_id, exchange_name):
|
||||||
|
"""Lookup an order based on the order id returned from one of the
|
||||||
|
order functions.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order_id : str
|
||||||
|
The unique identifier for the order.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
order : Order
|
||||||
|
The order object.
|
||||||
|
execution_price: float
|
||||||
|
The execution price per share of the order
|
||||||
|
"""
|
||||||
exchange = self.exchanges[exchange_name]
|
exchange = self.exchanges[exchange_name]
|
||||||
return exchange.get_order(order_id)
|
return exchange.get_order(order_id)
|
||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
def cancel_order(self, order_param, exchange_name):
|
def cancel_order(self, order_param, exchange_name):
|
||||||
|
"""Cancel an open order.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order_param : str or Order
|
||||||
|
The order_id or order object to cancel.
|
||||||
|
"""
|
||||||
exchange = self.exchanges[exchange_name]
|
exchange = self.exchanges[exchange_name]
|
||||||
|
|
||||||
order_id = order_param
|
order_id = order_param
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import numpy as np
|
|||||||
from catalyst import get_calendar
|
from catalyst import get_calendar
|
||||||
from catalyst.data.minute_bars import BcolzMinuteBarReader, \
|
from catalyst.data.minute_bars import BcolzMinuteBarReader, \
|
||||||
BcolzMinuteBarWriter
|
BcolzMinuteBarWriter
|
||||||
from catalyst.exchange.bundle_utils import get_periods, get_periods_range
|
|
||||||
|
|
||||||
|
|
||||||
class BcolzExchangeBarWriter(BcolzMinuteBarWriter):
|
class BcolzExchangeBarWriter(BcolzMinuteBarWriter):
|
||||||
@@ -17,7 +16,7 @@ class BcolzExchangeBarWriter(BcolzMinuteBarWriter):
|
|||||||
end_session = end_session.floor('1d')
|
end_session = end_session.floor('1d')
|
||||||
|
|
||||||
minutes_per_day = 1440 if self._data_frequency == 'minute' else 1
|
minutes_per_day = 1440 if self._data_frequency == 'minute' else 1
|
||||||
default_ohlc_ratio = kwargs.pop('default_ohlc_ratio', 1000000)
|
default_ohlc_ratio = kwargs.pop('default_ohlc_ratio', 100000000)
|
||||||
calendar = get_calendar('OPEN')
|
calendar = get_calendar('OPEN')
|
||||||
|
|
||||||
super(BcolzExchangeBarWriter, self) \
|
super(BcolzExchangeBarWriter, self) \
|
||||||
@@ -40,17 +39,25 @@ class BcolzExchangeBarReader(BcolzMinuteBarReader):
|
|||||||
return self._data_frequency
|
return self._data_frequency
|
||||||
|
|
||||||
def load_raw_arrays(self, fields, start_dt, end_dt, sids):
|
def load_raw_arrays(self, fields, start_dt, end_dt, sids):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fields : list of str
|
||||||
|
'open', 'high', 'low', 'close', or 'volume'
|
||||||
|
start_dt: Timestamp
|
||||||
|
Beginning of the window range.
|
||||||
|
end_dt: Timestamp
|
||||||
|
End of the window range.
|
||||||
|
sids : list of int
|
||||||
|
The asset identifiers in the window.
|
||||||
|
|
||||||
# if self._data_frequency == 'minute':
|
Returns
|
||||||
# return super(BcolzExchangeBarReader, self) \
|
-------
|
||||||
# .load_raw_arrays(fields, start_dt, end_dt, sids)
|
list of np.ndarray
|
||||||
#
|
A list with an entry per field of ndarrays with shape
|
||||||
# else:
|
(minutes in range, sids) with a dtype of float64, containing the
|
||||||
# return self._load_daily_raw_arrays(fields, start_dt, end_dt, sids)
|
values for the respective field over start and end dt range.
|
||||||
|
"""
|
||||||
return self._load_raw_arrays(fields, start_dt, end_dt, sids)
|
|
||||||
|
|
||||||
def _load_raw_arrays(self, fields, start_dt, end_dt, sids):
|
|
||||||
start_idx = self._find_position_of_minute(start_dt)
|
start_idx = self._find_position_of_minute(start_dt)
|
||||||
end_idx = self._find_position_of_minute(end_dt)
|
end_idx = self._find_position_of_minute(end_dt)
|
||||||
|
|
||||||
@@ -80,8 +87,9 @@ class BcolzExchangeBarReader(BcolzMinuteBarReader):
|
|||||||
if mask is None:
|
if mask is None:
|
||||||
mask = a != 0
|
mask = a != 0
|
||||||
|
|
||||||
|
inverse_ratio = self._ohlc_ratio_inverse_for_sid(sid)
|
||||||
out[:len(mask), i][mask] = (
|
out[:len(mask), i][mask] = (
|
||||||
a[mask] * self._ohlc_ratio_inverse_for_sid(sid)
|
a[mask] * inverse_ratio
|
||||||
)
|
)
|
||||||
|
|
||||||
if field in fields:
|
if field in fields:
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
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.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.slippage import SlippageModel
|
from catalyst.finance.slippage import SlippageModel
|
||||||
from catalyst.finance.transaction import Transaction
|
from catalyst.finance.transaction import create_transaction
|
||||||
|
|
||||||
log = Logger('exchange_blotter')
|
log = Logger('exchange_blotter', level=LOG_LEVEL)
|
||||||
|
|
||||||
# It seems like we need to accept greater slippage risk in cryptos
|
# It seems like we need to accept greater slippage risk in cryptos
|
||||||
# Orders won't often close at Equity levels.
|
# Orders won't often close at Equity levels.
|
||||||
# TODO: consider adjusting dynamically based on trading pair
|
# TODO: should work with set_commission and set_slippage
|
||||||
DEFAULT_SLIPPAGE_SPREAD = 0.02
|
DEFAULT_SLIPPAGE_SPREAD = 0.0001
|
||||||
DEFAULT_MAKER_FEE = 0.001
|
DEFAULT_MAKER_FEE = 0.0015
|
||||||
DEFAULT_TAKER_FEE = 0.002
|
DEFAULT_TAKER_FEE = 0.0025
|
||||||
|
|
||||||
|
|
||||||
class TradingPairFeeSchedule(CommissionModel):
|
class TradingPairFeeSchedule(CommissionModel):
|
||||||
@@ -96,12 +97,8 @@ class TradingPairFixedSlippage(SlippageModel):
|
|||||||
|
|
||||||
execution_price, execution_volume = self.process_order(data, order)
|
execution_price, execution_volume = self.process_order(data, order)
|
||||||
|
|
||||||
transaction = Transaction(
|
transaction = create_transaction(
|
||||||
asset=order.asset,
|
order, dt, execution_price, execution_volume
|
||||||
amount=abs(execution_volume),
|
|
||||||
dt=dt,
|
|
||||||
price=execution_price,
|
|
||||||
order_id=order.id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._volume_for_bar += abs(transaction.amount)
|
self._volume_for_bar += abs(transaction.amount)
|
||||||
|
|||||||
@@ -1,36 +1,67 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta
|
from itertools import chain
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from logbook import Logger, INFO
|
from catalyst.assets._assets import TradingPair
|
||||||
|
from logbook import Logger
|
||||||
|
from pandas.tslib import Timestamp
|
||||||
|
from pytz import UTC
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
from catalyst import get_calendar
|
from catalyst import get_calendar
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
||||||
BcolzMinuteBarMetadata
|
BcolzMinuteBarMetadata
|
||||||
from catalyst.exchange.bundle_utils import range_in_bundle, \
|
from catalyst.exchange.bundle_utils import range_in_bundle, \
|
||||||
get_bcolz_chunk, get_delta, get_adj_dates, get_month_start_end, \
|
get_bcolz_chunk, get_delta, get_month_start_end, \
|
||||||
get_year_start_end, get_periods_range, get_df_from_arrays, get_start_dt
|
get_year_start_end, get_df_from_arrays, get_start_dt, get_period_label
|
||||||
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
||||||
BcolzExchangeBarWriter
|
BcolzExchangeBarWriter
|
||||||
from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
||||||
InvalidHistoryFrequencyError, PricingDataBeforeTradingError, \
|
TempBundleNotFoundError, \
|
||||||
TempBundleNotFoundError, NoDataAvailableOnExchange, \
|
NoDataAvailableOnExchange, \
|
||||||
|
PricingDataNotLoadedError
|
||||||
|
from catalyst.exchange.exchange_utils import get_exchange_folder
|
||||||
|
from catalyst.utils.cli import maybe_show_progress
|
||||||
|
from catalyst.utils.paths import ensure_directory
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
|
from logbook import Logger
|
||||||
|
from pandas.tslib import Timestamp
|
||||||
|
from pytz import UTC
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
|
from catalyst import get_calendar
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
||||||
|
BcolzMinuteBarMetadata
|
||||||
|
from catalyst.exchange.bundle_utils import range_in_bundle, \
|
||||||
|
get_bcolz_chunk, get_delta, get_month_start_end, \
|
||||||
|
get_year_start_end, get_df_from_arrays, get_start_dt, get_period_label
|
||||||
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
||||||
|
BcolzExchangeBarWriter
|
||||||
|
from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
||||||
|
TempBundleNotFoundError, \
|
||||||
|
NoDataAvailableOnExchange, \
|
||||||
PricingDataNotLoadedError
|
PricingDataNotLoadedError
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_folder
|
from catalyst.exchange.exchange_utils import get_exchange_folder
|
||||||
from catalyst.utils.cli import maybe_show_progress
|
from catalyst.utils.cli import maybe_show_progress
|
||||||
from catalyst.utils.paths import ensure_directory
|
from catalyst.utils.paths import ensure_directory
|
||||||
|
|
||||||
|
log = Logger('exchange_bundle', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
BUNDLE_NAME_TEMPLATE = os.path.join('{root}', '{frequency}_bundle')
|
||||||
|
|
||||||
|
|
||||||
def _cachpath(symbol, type_):
|
def _cachpath(symbol, type_):
|
||||||
return '-'.join([symbol, type_])
|
return '-'.join([symbol, type_])
|
||||||
|
|
||||||
|
|
||||||
BUNDLE_NAME_TEMPLATE = '{root}/{frequency}_bundle'
|
|
||||||
log = Logger('exchange_bundle')
|
|
||||||
log.level = INFO
|
|
||||||
|
|
||||||
|
|
||||||
class ExchangeBundle:
|
class ExchangeBundle:
|
||||||
def __init__(self, exchange):
|
def __init__(self, exchange):
|
||||||
self.exchange = exchange
|
self.exchange = exchange
|
||||||
@@ -54,7 +85,10 @@ class ExchangeBundle:
|
|||||||
"""
|
"""
|
||||||
Get a data writer object, either a new object or from cache
|
Get a data writer object, either a new object or from cache
|
||||||
|
|
||||||
:return: BcolzMinuteBarReader or BcolzDailyBarReader
|
Returns
|
||||||
|
-------
|
||||||
|
BcolzMinuteBarReader | BcolzDailyBarReader
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
root = get_exchange_folder(self.exchange.name)
|
root = get_exchange_folder(self.exchange.name)
|
||||||
@@ -83,7 +117,10 @@ class ExchangeBundle:
|
|||||||
"""
|
"""
|
||||||
Get a data writer object, either a new object or from cache
|
Get a data writer object, either a new object or from cache
|
||||||
|
|
||||||
:return: BcolzMinuteBarWriter or BcolzDailyBarWriter
|
Returns
|
||||||
|
-------
|
||||||
|
BcolzMinuteBarWriter | BcolzDailyBarWriter
|
||||||
|
|
||||||
"""
|
"""
|
||||||
root = get_exchange_folder(self.exchange.name)
|
root = get_exchange_folder(self.exchange.name)
|
||||||
path = BUNDLE_NAME_TEMPLATE.format(
|
path = BUNDLE_NAME_TEMPLATE.format(
|
||||||
@@ -139,13 +176,19 @@ class ExchangeBundle:
|
|||||||
If the data exists, the chunk ingestion is complete.
|
If the data exists, the chunk ingestion is complete.
|
||||||
If any data is missing we ingest the data.
|
If any data is missing we ingest the data.
|
||||||
|
|
||||||
:param assets: list[TradingPair]
|
Parameters
|
||||||
|
----------
|
||||||
|
assets: list[TradingPair]
|
||||||
The assets is scope.
|
The assets is scope.
|
||||||
:param start_dt:
|
start_dt: datetime
|
||||||
The chunk start date.
|
The chunk start date.
|
||||||
:param end_dt:
|
end_dt: datetime
|
||||||
The chunk end date.
|
The chunk end date.
|
||||||
:return: list[TradingPair]
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[TradingPair]
|
||||||
The assets missing from the bundle
|
The assets missing from the bundle
|
||||||
"""
|
"""
|
||||||
reader = self.get_reader(data_frequency)
|
reader = self.get_reader(data_frequency)
|
||||||
@@ -159,13 +202,6 @@ class ExchangeBundle:
|
|||||||
return missing_assets
|
return missing_assets
|
||||||
|
|
||||||
def _write(self, data, writer, data_frequency):
|
def _write(self, data, writer, data_frequency):
|
||||||
"""
|
|
||||||
Write data to the writer
|
|
||||||
|
|
||||||
:param df:
|
|
||||||
:param writer:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
writer.write(
|
writer.write(
|
||||||
data=data,
|
data=data,
|
||||||
@@ -173,16 +209,13 @@ class ExchangeBundle:
|
|||||||
invalid_data_behavior='raise'
|
invalid_data_behavior='raise'
|
||||||
)
|
)
|
||||||
except BcolzMinuteOverlappingData as e:
|
except BcolzMinuteOverlappingData as e:
|
||||||
log.warn('chunk already exists: {}'.format(e))
|
log.debug('chunk already exists: {}'.format(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn('error when writing data: {}, trying again'.format(e))
|
log.warn('error when writing data: {}, trying again'.format(e))
|
||||||
|
|
||||||
# This is workaround, there is an issue with empty
|
# This is workaround, there is an issue with empty
|
||||||
# session_label when using a newly created writer
|
# session_label when using a newly created writer
|
||||||
key = writer._rootdir if data_frequency == 'minute' \
|
del self._writers[writer._rootdir]
|
||||||
else writer._filename
|
|
||||||
|
|
||||||
del self._writers[key]
|
|
||||||
|
|
||||||
writer = self.get_writer(writer._start_session,
|
writer = self.get_writer(writer._start_session,
|
||||||
writer._end_session, data_frequency)
|
writer._end_session, data_frequency)
|
||||||
@@ -193,57 +226,40 @@ class ExchangeBundle:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_calendar_periods_range(self, start_dt, end_dt, data_frequency):
|
def get_calendar_periods_range(self, start_dt, end_dt, data_frequency):
|
||||||
|
"""
|
||||||
|
Get a list of dates for the specified range.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[datetime]
|
||||||
|
|
||||||
|
"""
|
||||||
return self.calendar.minutes_in_range(start_dt, end_dt) \
|
return self.calendar.minutes_in_range(start_dt, end_dt) \
|
||||||
if data_frequency == 'minute' \
|
if data_frequency == 'minute' \
|
||||||
else self.calendar.sessions_in_range(start_dt, end_dt)
|
else self.calendar.sessions_in_range(start_dt, end_dt)
|
||||||
|
|
||||||
def ingest_ctable(self, asset, data_frequency, period, start_dt, end_dt,
|
def ingest_df(self, ohlcv_df, data_frequency, asset, writer,
|
||||||
writer, empty_rows_behavior='strip', cleanup=False):
|
empty_rows_behavior='strip'):
|
||||||
"""
|
"""
|
||||||
Merge a ctable bundle chunk into the main bundle for the exchange.
|
Ingest a DataFrame of OHLCV data for a given market.
|
||||||
|
|
||||||
:param asset: TradingPair
|
Parameters
|
||||||
:param data_frequency: str
|
----------
|
||||||
:param period: str
|
ohlcv_df: DataFrame
|
||||||
:param writer:
|
data_frequency: str
|
||||||
:param empty_rows_behavior: str
|
asset: TradingPair
|
||||||
Ensure that the bundle does not have any missing data.
|
writer:
|
||||||
|
empty_rows_behavior: str
|
||||||
|
|
||||||
:param cleanup: bool
|
|
||||||
Remove the temp bundle directory after ingestion.
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = get_bcolz_chunk(
|
|
||||||
exchange_name=self.exchange.name,
|
|
||||||
symbol=asset.symbol,
|
|
||||||
data_frequency=data_frequency,
|
|
||||||
period=period
|
|
||||||
)
|
|
||||||
|
|
||||||
reader = self.get_reader(data_frequency, path=path)
|
|
||||||
if reader is None:
|
|
||||||
raise TempBundleNotFoundError(path=path)
|
|
||||||
|
|
||||||
arrays = reader.load_raw_arrays(
|
|
||||||
sids=[asset.sid],
|
|
||||||
fields=['open', 'high', 'low', 'close', 'volume'],
|
|
||||||
start_dt=start_dt,
|
|
||||||
end_dt=end_dt
|
|
||||||
)
|
|
||||||
|
|
||||||
if not arrays:
|
|
||||||
return path
|
|
||||||
|
|
||||||
periods = self.get_calendar_periods_range(
|
|
||||||
start_dt, end_dt, data_frequency
|
|
||||||
)
|
|
||||||
|
|
||||||
df = get_df_from_arrays(arrays, periods)
|
|
||||||
|
|
||||||
if empty_rows_behavior is not 'ignore':
|
if empty_rows_behavior is not 'ignore':
|
||||||
nan_rows = df[df.isnull().T.any().T].index
|
nan_rows = ohlcv_df[ohlcv_df.isnull().T.any().T].index
|
||||||
|
|
||||||
if len(nan_rows) > 0:
|
if len(nan_rows) > 0:
|
||||||
dates = []
|
dates = []
|
||||||
@@ -265,7 +281,9 @@ class ExchangeBundle:
|
|||||||
|
|
||||||
dates.append(pd.to_datetime(nan_rows.values[-1]))
|
dates.append(pd.to_datetime(nan_rows.values[-1]))
|
||||||
|
|
||||||
name = path.split('/')[-1]
|
name = '{} from {} to {}'.format(
|
||||||
|
asset.symbol, ohlcv_df.index[0], ohlcv_df.index[-1]
|
||||||
|
)
|
||||||
if empty_rows_behavior == 'warn':
|
if empty_rows_behavior == 'warn':
|
||||||
log.warn(
|
log.warn(
|
||||||
'\n{name} with end minute {end_minute} has empty rows '
|
'\n{name} with end minute {end_minute} has empty rows '
|
||||||
@@ -283,182 +301,363 @@ class ExchangeBundle:
|
|||||||
dates=dates
|
dates=dates
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
df.dropna(inplace=True)
|
ohlcv_df.dropna(inplace=True)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
if not df.empty:
|
if not ohlcv_df.empty:
|
||||||
df.sort_index(inplace=True)
|
ohlcv_df.sort_index(inplace=True)
|
||||||
data.append((asset.sid, df))
|
data.append((asset.sid, ohlcv_df))
|
||||||
|
|
||||||
self._write(data, writer, data_frequency)
|
self._write(data, writer, data_frequency)
|
||||||
|
|
||||||
if cleanup:
|
def ingest_ctable(self, asset, data_frequency, period,
|
||||||
log.debug('removing bundle folder following '
|
writer, empty_rows_behavior='strip', cleanup=False):
|
||||||
'ingestion: {}'.format(path))
|
"""
|
||||||
|
Merge a ctable bundle chunk into the main bundle for the exchange.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
asset: TradingPair
|
||||||
|
data_frequency: str
|
||||||
|
period: str
|
||||||
|
writer:
|
||||||
|
empty_rows_behavior: str
|
||||||
|
Ensure that the bundle does not have any missing data.
|
||||||
|
|
||||||
|
cleanup: bool
|
||||||
|
Remove the temp bundle directory after ingestion.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# Download and extract the bundle
|
||||||
|
path = get_bcolz_chunk(
|
||||||
|
exchange_name=self.exchange.name,
|
||||||
|
symbol=asset.symbol,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
period=period
|
||||||
|
)
|
||||||
|
|
||||||
|
reader = self.get_reader(data_frequency, path=path)
|
||||||
|
if reader is None:
|
||||||
|
try:
|
||||||
|
log.warn('the reader is unable to use bundle: {}, '
|
||||||
|
'deleting it.'.format(path))
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
|
|
||||||
return path
|
except Exception as e:
|
||||||
|
log.warn('unable to remove temp bundle: {}'.format(e))
|
||||||
|
|
||||||
|
raise TempBundleNotFoundError(path=path)
|
||||||
|
|
||||||
|
start_dt = reader.first_trading_day
|
||||||
|
end_dt = reader.last_available_dt
|
||||||
|
|
||||||
|
if data_frequency == 'daily':
|
||||||
|
end_dt = end_dt - pd.Timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
|
arrays = None
|
||||||
|
try:
|
||||||
|
arrays = reader.load_raw_arrays(
|
||||||
|
sids=[asset.sid],
|
||||||
|
fields=['open', 'high', 'low', 'close', 'volume'],
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.warn('skipping ctable for {} from {} to {}: {}'.format(
|
||||||
|
asset.symbol, start_dt, end_dt, e
|
||||||
|
))
|
||||||
|
|
||||||
|
if not arrays:
|
||||||
|
return reader._rootdir
|
||||||
|
|
||||||
|
periods = self.get_calendar_periods_range(
|
||||||
|
start_dt, end_dt, data_frequency
|
||||||
|
)
|
||||||
|
df = get_df_from_arrays(arrays, periods)
|
||||||
|
self.ingest_df(
|
||||||
|
ohlcv_df=df,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
asset=asset,
|
||||||
|
writer=writer,
|
||||||
|
empty_rows_behavior=empty_rows_behavior
|
||||||
|
)
|
||||||
|
|
||||||
|
if cleanup:
|
||||||
|
log.debug(
|
||||||
|
'removing bundle folder following ingestion: {}'.format(
|
||||||
|
reader._rootdir)
|
||||||
|
)
|
||||||
|
shutil.rmtree(reader._rootdir)
|
||||||
|
|
||||||
|
return reader._rootdir
|
||||||
|
|
||||||
|
def get_adj_dates(self, start, end, assets, data_frequency):
|
||||||
|
"""
|
||||||
|
Contains a date range to the trading availability of the specified
|
||||||
|
markets.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start: datetime
|
||||||
|
end: datetime
|
||||||
|
assets: list[TradingPair]
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
datetime, datetime
|
||||||
|
"""
|
||||||
|
earliest_trade = None
|
||||||
|
last_entry = None
|
||||||
|
for asset in assets:
|
||||||
|
if earliest_trade is None or earliest_trade > asset.start_date:
|
||||||
|
if asset.start_date >= self.calendar.first_session:
|
||||||
|
earliest_trade = asset.start_date
|
||||||
|
|
||||||
|
else:
|
||||||
|
earliest_trade = self.calendar.first_session
|
||||||
|
|
||||||
|
end_asset = asset.end_minute if data_frequency == 'minute' else \
|
||||||
|
asset.end_daily
|
||||||
|
if end_asset is not None:
|
||||||
|
if last_entry is None or end_asset > last_entry:
|
||||||
|
last_entry = end_asset
|
||||||
|
|
||||||
|
else:
|
||||||
|
end = None
|
||||||
|
last_entry = None
|
||||||
|
|
||||||
|
if start is None or \
|
||||||
|
(earliest_trade is not None and earliest_trade > start):
|
||||||
|
start = earliest_trade
|
||||||
|
|
||||||
|
if end is None or (last_entry is not None and end > last_entry):
|
||||||
|
end = last_entry
|
||||||
|
|
||||||
|
if end is None or start is None or start >= end:
|
||||||
|
raise NoDataAvailableOnExchange(
|
||||||
|
exchange=[asset.exchange for asset in assets],
|
||||||
|
symbol=[asset.symbol for asset in assets],
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
)
|
||||||
|
|
||||||
|
return start, end
|
||||||
|
|
||||||
def prepare_chunks(self, assets, data_frequency, start_dt, end_dt):
|
def prepare_chunks(self, assets, data_frequency, start_dt, end_dt):
|
||||||
"""
|
"""
|
||||||
Split a price data request into chunks corresponding to individual
|
Split a price data request into chunks corresponding to individual
|
||||||
bundles.
|
bundles.
|
||||||
|
|
||||||
:param assets:
|
Parameters
|
||||||
:param data_frequency:
|
----------
|
||||||
:param start_dt:
|
assets: list[TradingPair]
|
||||||
:param end_dt:
|
data_frequency: str
|
||||||
:return:
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[TradingPair, list[dict(str, Object]]]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
get_start_end = get_month_start_end \
|
||||||
|
if data_frequency == 'minute' else get_year_start_end
|
||||||
|
|
||||||
|
# Get a reader for the main bundle to verify if data exists
|
||||||
reader = self.get_reader(data_frequency)
|
reader = self.get_reader(data_frequency)
|
||||||
|
|
||||||
chunks = []
|
chunks = dict()
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
try:
|
try:
|
||||||
asset_start, asset_end = \
|
# Checking if the the asset has price data in the specified
|
||||||
get_adj_dates(start_dt, end_dt, [asset], data_frequency)
|
# date range
|
||||||
|
adj_start, adj_end = self.get_adj_dates(
|
||||||
|
start_dt, end_dt, [asset], data_frequency
|
||||||
|
)
|
||||||
|
|
||||||
except NoDataAvailableOnExchange:
|
except NoDataAvailableOnExchange as e:
|
||||||
|
# If not, we continue to the next asset
|
||||||
|
log.debug('skipping {}: {}'.format(asset.symbol, e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Aligning start / end dates with the daily calendar
|
dates = pd.date_range(
|
||||||
sessions = get_periods_range(start_dt, end_dt, data_frequency) \
|
start=get_period_label(adj_start, data_frequency),
|
||||||
if data_frequency == 'minute' \
|
end=get_period_label(adj_end, data_frequency),
|
||||||
else self.calendar.sessions_in_range(start_dt, end_dt)
|
freq='MS' if data_frequency == 'minute' else 'AS',
|
||||||
|
tz=UTC
|
||||||
|
)
|
||||||
|
|
||||||
if asset_start < sessions[0]:
|
# Adjusting the last date of the range to avoid
|
||||||
asset_start = sessions[0]
|
# going over the asset's trading bounds
|
||||||
|
dates.values[0] = adj_start
|
||||||
|
dates.values[-1] = adj_end
|
||||||
|
|
||||||
if asset_end > sessions[-1]:
|
chunks[asset] = []
|
||||||
asset_end = sessions[-1]
|
for index, dt in enumerate(dates):
|
||||||
|
period_start, period_end = get_start_end(
|
||||||
chunk_labels = []
|
dt=dt,
|
||||||
dt = sessions[0]
|
first_day=dt if index == 0 else None,
|
||||||
while dt <= sessions[-1]:
|
last_day=dt if index == len(dates) - 1 else None
|
||||||
label = '{}-{:02d}'.format(dt.year, dt.month) \
|
|
||||||
if data_frequency == 'minute' else '{}'.format(dt.year)
|
|
||||||
|
|
||||||
if label not in chunk_labels:
|
|
||||||
chunk_labels.append(label)
|
|
||||||
|
|
||||||
# Adjusting the period dates to match the availability
|
|
||||||
# of the trading pair
|
|
||||||
if data_frequency == 'minute':
|
|
||||||
period_start, period_end = get_month_start_end(dt)
|
|
||||||
asset_start_month, _ = get_month_start_end(asset_start)
|
|
||||||
|
|
||||||
if asset_start_month == period_start \
|
|
||||||
and period_start < asset_start:
|
|
||||||
period_start = asset_start
|
|
||||||
|
|
||||||
_, asset_end_month = get_month_start_end(asset_end)
|
|
||||||
if asset_end_month == period_end \
|
|
||||||
and period_end > asset_end:
|
|
||||||
period_end = asset_end
|
|
||||||
|
|
||||||
elif data_frequency == 'daily':
|
|
||||||
period_start, period_end = get_year_start_end(dt)
|
|
||||||
asset_start_year, _ = get_year_start_end(asset_start)
|
|
||||||
|
|
||||||
if asset_start_year == period_start \
|
|
||||||
and period_start < asset_start:
|
|
||||||
period_start = asset_start
|
|
||||||
|
|
||||||
_, asset_end_year = get_year_start_end(asset_end)
|
|
||||||
if asset_end_year == period_end \
|
|
||||||
and period_end > asset_end:
|
|
||||||
period_end = asset_end
|
|
||||||
else:
|
|
||||||
raise InvalidHistoryFrequencyError(
|
|
||||||
frequency=data_frequency
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Currencies don't always start trading at midnight.
|
# Currencies don't always start trading at midnight.
|
||||||
# Checking the last minute of the day instead.
|
# Checking the last minute of the day instead.
|
||||||
range_start = period_start.replace(hour=23, minute=59) \
|
range_start = period_start.replace(hour=23, minute=59) \
|
||||||
if data_frequency == 'minute' else period_start
|
if data_frequency == 'minute' else period_start
|
||||||
|
|
||||||
|
# Checking if the data already exists in the bundle
|
||||||
|
# for the date range of the chunk. If not, we create
|
||||||
|
# a chunk for ingestion.
|
||||||
has_data = range_in_bundle(
|
has_data = range_in_bundle(
|
||||||
asset, range_start, period_end, reader
|
asset, range_start, period_end, reader
|
||||||
)
|
)
|
||||||
|
|
||||||
if not has_data:
|
if not has_data:
|
||||||
log.debug('adding period: {}'.format(label))
|
period = get_period_label(dt, data_frequency)
|
||||||
chunks.append(
|
chunk = dict(
|
||||||
dict(
|
|
||||||
asset=asset,
|
asset=asset,
|
||||||
period_start=period_start,
|
period=period,
|
||||||
period_end=period_end,
|
|
||||||
period=label
|
|
||||||
)
|
)
|
||||||
|
chunks[asset].append(chunk)
|
||||||
|
|
||||||
|
# We sort the chunks by end date to ingest most recent data first
|
||||||
|
chunks[asset].sort(
|
||||||
|
key=lambda chunk: pd.to_datetime(chunk['period'])
|
||||||
)
|
)
|
||||||
|
|
||||||
dt += timedelta(days=1)
|
|
||||||
|
|
||||||
chunks.sort(key=lambda chunk: chunk['period_end'])
|
|
||||||
|
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
def ingest_assets(self, assets, start_dt, end_dt, data_frequency,
|
def ingest_assets(self, assets, data_frequency, start_dt=None, end_dt=None,
|
||||||
show_progress=False):
|
show_progress=False, asset_chunks=False):
|
||||||
"""
|
"""
|
||||||
Determine if data is missing from the bundle and attempt to ingest it.
|
Determine if data is missing from the bundle and attempt to ingest it.
|
||||||
|
|
||||||
:param assets:
|
Parameters
|
||||||
:param start_dt:
|
----------
|
||||||
:param end_dt:
|
assets: list[TradingPair]
|
||||||
:return:
|
data_frequency: str
|
||||||
|
start_dt: datetime
|
||||||
|
end_dt: datetime
|
||||||
|
show_progress: bool
|
||||||
|
asset_chunks: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
writer = self.get_writer(start_dt, end_dt, data_frequency)
|
if start_dt is None:
|
||||||
|
start_dt = self.calendar.first_session
|
||||||
|
|
||||||
|
if end_dt is None:
|
||||||
|
end_dt = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
|
get_start_end = get_month_start_end \
|
||||||
|
if data_frequency == 'minute' else get_year_start_end
|
||||||
|
|
||||||
|
# Assign the first and last day of the period
|
||||||
|
start_dt, _ = get_start_end(start_dt)
|
||||||
|
_, end_dt = get_start_end(end_dt)
|
||||||
|
|
||||||
chunks = self.prepare_chunks(
|
chunks = self.prepare_chunks(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
start_dt=start_dt,
|
start_dt=start_dt,
|
||||||
end_dt=end_dt
|
end_dt=end_dt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# This is the common writer for the entire exchange bundle
|
||||||
|
# we want to give an end_date far in time
|
||||||
|
writer = self.get_writer(start_dt, end_dt, data_frequency)
|
||||||
|
if asset_chunks:
|
||||||
|
for asset in chunks:
|
||||||
with maybe_show_progress(
|
with maybe_show_progress(
|
||||||
chunks,
|
chunks[asset],
|
||||||
show_progress,
|
show_progress,
|
||||||
label='Fetching {exchange} {frequency} candles: '.format(
|
label='Ingesting {frequency} price data for '
|
||||||
|
'{symbol} on {exchange}'.format(
|
||||||
exchange=self.exchange.name,
|
exchange=self.exchange.name,
|
||||||
frequency=data_frequency
|
frequency=data_frequency,
|
||||||
|
symbol=asset.symbol
|
||||||
)) as it:
|
)) as it:
|
||||||
for chunk in it:
|
for chunk in it:
|
||||||
self.ingest_ctable(
|
self.ingest_ctable(
|
||||||
asset=chunk['asset'],
|
asset=chunk['asset'],
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
period=chunk['period'],
|
period=chunk['period'],
|
||||||
start_dt=chunk['period_start'],
|
|
||||||
end_dt=chunk['period_end'],
|
|
||||||
writer=writer,
|
writer=writer,
|
||||||
empty_rows_behavior='strip'
|
empty_rows_behavior='strip',
|
||||||
|
cleanup=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
all_chunks = list(chain.from_iterable(itervalues(chunks)))
|
||||||
|
|
||||||
|
# We sort the chunks by end date to ingest most recent data first
|
||||||
|
all_chunks.sort(
|
||||||
|
key=lambda chunk: pd.to_datetime(chunk['period'])
|
||||||
|
)
|
||||||
|
with maybe_show_progress(
|
||||||
|
all_chunks,
|
||||||
|
show_progress,
|
||||||
|
label='Ingesting {frequency} price data on '
|
||||||
|
'{exchange}'.format(
|
||||||
|
exchange=self.exchange.name,
|
||||||
|
frequency=data_frequency,
|
||||||
|
)) as it:
|
||||||
|
for chunk in it:
|
||||||
|
self.ingest_ctable(
|
||||||
|
asset=chunk['asset'],
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
period=chunk['period'],
|
||||||
|
writer=writer,
|
||||||
|
empty_rows_behavior='strip',
|
||||||
|
cleanup=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def ingest(self, data_frequency, include_symbols=None,
|
def ingest(self, data_frequency, include_symbols=None,
|
||||||
exclude_symbols=None, start=None, end=None,
|
exclude_symbols=None, start=None, end=None,
|
||||||
show_progress=True, environ=os.environ):
|
show_progress=True, environ=os.environ):
|
||||||
"""
|
"""
|
||||||
|
Inject data based on specified parameters.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data_frequency: str
|
||||||
|
include_symbols: str
|
||||||
|
exclude_symbols: str
|
||||||
|
start: datetime
|
||||||
|
end: datetime
|
||||||
|
show_progress: bool
|
||||||
|
environ:
|
||||||
|
|
||||||
:param data_frequency:
|
|
||||||
:param include_symbols:
|
|
||||||
:param exclude_symbols:
|
|
||||||
:param start:
|
|
||||||
:param end:
|
|
||||||
:param show_progress:
|
|
||||||
:param environ:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
assets = self.get_assets(include_symbols, exclude_symbols)
|
assets = self.get_assets(include_symbols, exclude_symbols)
|
||||||
start_dt, end_dt = get_adj_dates(start, end, assets, data_frequency)
|
|
||||||
|
|
||||||
for frequency in data_frequency.split(','):
|
for frequency in data_frequency.split(','):
|
||||||
self.ingest_assets(assets, start_dt, end_dt, frequency,
|
self.ingest_assets(assets, frequency, start, end,
|
||||||
show_progress)
|
show_progress, True)
|
||||||
|
|
||||||
def get_history_window_series_and_load(self,
|
def get_history_window_series_and_load(self,
|
||||||
assets,
|
assets,
|
||||||
end_dt,
|
end_dt,
|
||||||
bar_count,
|
bar_count,
|
||||||
field,
|
field,
|
||||||
data_frequency):
|
data_frequency,
|
||||||
|
algo_end_dt=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Retrieve price data history, ingest missing data.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
assets: list[TradingPair]
|
||||||
|
end_dt: datetime
|
||||||
|
bar_count: int
|
||||||
|
field: str
|
||||||
|
data_frequency: str
|
||||||
|
algo_end_dt: datetime
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Series
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
series = self.get_history_window_series(
|
series = self.get_history_window_series(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
@@ -482,9 +681,10 @@ class ExchangeBundle:
|
|||||||
self.ingest_assets(
|
self.ingest_assets(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
start_dt=start_dt,
|
start_dt=start_dt,
|
||||||
end_dt=end_dt,
|
end_dt=algo_end_dt,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
show_progress=True
|
show_progress=True,
|
||||||
|
asset_chunks=True
|
||||||
)
|
)
|
||||||
series = self.get_history_window_series(
|
series = self.get_history_window_series(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
@@ -492,12 +692,28 @@ class ExchangeBundle:
|
|||||||
bar_count=bar_count,
|
bar_count=bar_count,
|
||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency,
|
data_frequency=data_frequency,
|
||||||
reset_reader=True
|
reset_reader=False
|
||||||
)
|
)
|
||||||
return series
|
return series
|
||||||
|
|
||||||
def get_spot_values(self, assets, field, dt, data_frequency,
|
def get_spot_values(self,
|
||||||
reset_reader=False):
|
assets,
|
||||||
|
field,
|
||||||
|
dt,
|
||||||
|
data_frequency,
|
||||||
|
reset_reader=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
The spot values for the gives assets, field and date. Reads from
|
||||||
|
the exchange data bundle.
|
||||||
|
|
||||||
|
:param assets:
|
||||||
|
:param field:
|
||||||
|
:param dt:
|
||||||
|
:param data_frequency:
|
||||||
|
:param reset_reader:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
values = []
|
values = []
|
||||||
try:
|
try:
|
||||||
reader = self.get_reader(data_frequency)
|
reader = self.get_reader(data_frequency)
|
||||||
@@ -516,7 +732,7 @@ class ExchangeBundle:
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
symbols = [asset.symbol.encode('utf-8') for asset in assets]
|
symbols = [asset.symbol for asset in assets]
|
||||||
raise PricingDataNotLoadedError(
|
raise PricingDataNotLoadedError(
|
||||||
field=field,
|
field=field,
|
||||||
first_trading_day=min([asset.start_date for asset in assets]),
|
first_trading_day=min([asset.start_date for asset in assets]),
|
||||||
@@ -534,8 +750,9 @@ class ExchangeBundle:
|
|||||||
data_frequency,
|
data_frequency,
|
||||||
reset_reader=False):
|
reset_reader=False):
|
||||||
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||||
start_dt, end_dt = \
|
start_dt, end_dt = self.get_adj_dates(
|
||||||
get_adj_dates(start_dt, end_dt, assets, data_frequency)
|
start_dt, end_dt, assets, data_frequency
|
||||||
|
)
|
||||||
|
|
||||||
reader = self.get_reader(data_frequency)
|
reader = self.get_reader(data_frequency)
|
||||||
if reset_reader:
|
if reset_reader:
|
||||||
@@ -543,7 +760,7 @@ class ExchangeBundle:
|
|||||||
reader = self.get_reader(data_frequency)
|
reader = self.get_reader(data_frequency)
|
||||||
|
|
||||||
if reader is None:
|
if reader is None:
|
||||||
symbols = [asset.symbol.encode('utf-8') for asset in assets]
|
symbols = [asset.symbol for asset in assets]
|
||||||
raise PricingDataNotLoadedError(
|
raise PricingDataNotLoadedError(
|
||||||
field=field,
|
field=field,
|
||||||
first_trading_day=min([asset.start_date for asset in assets]),
|
first_trading_day=min([asset.start_date for asset in assets]),
|
||||||
@@ -554,8 +771,9 @@ class ExchangeBundle:
|
|||||||
)
|
)
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
asset_start_dt, asset_end_dt = \
|
asset_start_dt, asset_end_dt = self.get_adj_dates(
|
||||||
get_adj_dates(start_dt, end_dt, assets, data_frequency)
|
start_dt, end_dt, assets, data_frequency
|
||||||
|
)
|
||||||
|
|
||||||
in_bundle = range_in_bundle(
|
in_bundle = range_in_bundle(
|
||||||
asset, asset_start_dt, asset_end_dt, reader
|
asset, asset_start_dt, asset_end_dt, reader
|
||||||
@@ -601,3 +819,42 @@ class ExchangeBundle:
|
|||||||
series[asset] = value_series
|
series[asset] = value_series
|
||||||
|
|
||||||
return series
|
return series
|
||||||
|
|
||||||
|
def clean(self, data_frequency):
|
||||||
|
"""
|
||||||
|
Removing the bundle data from the catalyst folder.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
log.debug('cleaning exchange {}, frequency {}'.format(
|
||||||
|
self.exchange.name, data_frequency
|
||||||
|
))
|
||||||
|
root = get_exchange_folder(self.exchange.name)
|
||||||
|
|
||||||
|
symbols = os.path.join(root, 'symbols.json')
|
||||||
|
if os.path.isfile(symbols):
|
||||||
|
os.remove(symbols)
|
||||||
|
|
||||||
|
temp_bundles = os.path.join(root, 'temp_bundles')
|
||||||
|
|
||||||
|
if os.path.isdir(temp_bundles):
|
||||||
|
log.debug('removing folder and content: {}'.format(temp_bundles))
|
||||||
|
shutil.rmtree(temp_bundles)
|
||||||
|
log.debug('{} removed'.format(temp_bundles))
|
||||||
|
|
||||||
|
frequencies = ['daily', 'minute'] if data_frequency is None \
|
||||||
|
else [data_frequency]
|
||||||
|
|
||||||
|
for frequency in frequencies:
|
||||||
|
label = '{}_bundle'.format(frequency)
|
||||||
|
frequency_bundle = os.path.join(root, label)
|
||||||
|
|
||||||
|
if os.path.isdir(frequency_bundle):
|
||||||
|
log.debug(
|
||||||
|
'removing folder and content: {}'.format(frequency_bundle)
|
||||||
|
)
|
||||||
|
shutil.rmtree(frequency_bundle)
|
||||||
|
log.debug('{} removed'.format(frequency_bundle))
|
||||||
|
|||||||
+95
-39
@@ -1,16 +1,3 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
@@ -19,17 +6,16 @@ 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.data.data_portal import DataPortal
|
from catalyst.data.data_portal import DataPortal
|
||||||
from catalyst.exchange.bundle_utils import get_start_dt
|
|
||||||
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 (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
ExchangeBarDataError,
|
ExchangeBarDataError,
|
||||||
PricingDataBeforeTradingError,
|
PricingDataNotLoadedError)
|
||||||
PricingDataNotLoadedError, InvalidHistoryFrequencyError,
|
from catalyst.exchange.exchange_utils import get_frequency, resample_history_df
|
||||||
BundleNotFoundError)
|
|
||||||
|
|
||||||
log = Logger('DataPortalExchange')
|
log = Logger('DataPortalExchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class DataPortalExchangeBase(DataPortal):
|
class DataPortalExchangeBase(DataPortal):
|
||||||
@@ -82,7 +68,7 @@ class DataPortalExchangeBase(DataPortal):
|
|||||||
return pd.concat(df_list)
|
return pd.concat(df_list)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
exchange = self.exchanges[exchange_assets.keys()[0]]
|
exchange = self.exchanges[list(exchange_assets.keys())[0]]
|
||||||
return self.get_exchange_history_window(
|
return self.get_exchange_history_window(
|
||||||
exchange,
|
exchange,
|
||||||
assets,
|
assets,
|
||||||
@@ -167,8 +153,8 @@ class DataPortalExchangeBase(DataPortal):
|
|||||||
|
|
||||||
exchange_assets[asset.exchange].append(asset)
|
exchange_assets[asset.exchange].append(asset)
|
||||||
|
|
||||||
if len(exchange_assets.keys()) == 1:
|
if len(list(exchange_assets.keys())) == 1:
|
||||||
exchange = self.exchanges[exchange_assets.keys()[0]]
|
exchange = self.exchanges[list(exchange_assets.keys())[0]]
|
||||||
return self.get_exchange_spot_value(
|
return self.get_exchange_spot_value(
|
||||||
exchange, assets, field, dt, data_frequency)
|
exchange, assets, field, dt, data_frequency)
|
||||||
|
|
||||||
@@ -239,6 +225,25 @@ class DataPortalExchangeLive(DataPortalExchangeBase):
|
|||||||
field,
|
field,
|
||||||
data_frequency,
|
data_frequency,
|
||||||
ffill=True):
|
ffill=True):
|
||||||
|
"""
|
||||||
|
Fetching price history window from the exchange.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange: Exchange
|
||||||
|
assets: list[TradingPair]
|
||||||
|
end_dt: datetime
|
||||||
|
bar_count: int
|
||||||
|
frequency: str
|
||||||
|
field: str
|
||||||
|
data_frequency: str
|
||||||
|
ffill: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
|
||||||
|
"""
|
||||||
df = exchange.get_history_window(
|
df = exchange.get_history_window(
|
||||||
assets,
|
assets,
|
||||||
end_dt,
|
end_dt,
|
||||||
@@ -251,6 +256,22 @@ class DataPortalExchangeLive(DataPortalExchangeBase):
|
|||||||
|
|
||||||
def get_exchange_spot_value(self, exchange, assets, field, dt,
|
def get_exchange_spot_value(self, exchange, assets, field, dt,
|
||||||
data_frequency):
|
data_frequency):
|
||||||
|
"""
|
||||||
|
A spot value for the exchange.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange: Exchange
|
||||||
|
assets: list[TradingPair]
|
||||||
|
field: str
|
||||||
|
dt: datetime
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
|
"""
|
||||||
exchange_spot_values = exchange.get_spot_value(
|
exchange_spot_values = exchange.get_spot_value(
|
||||||
assets, field, dt, data_frequency)
|
assets, field, dt, data_frequency)
|
||||||
|
|
||||||
@@ -289,34 +310,69 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
|
|||||||
"""
|
"""
|
||||||
Fetching price history window from the exchange bundle.
|
Fetching price history window from the exchange bundle.
|
||||||
|
|
||||||
Using a try... except approach to minimize reads most of the time,
|
Parameters
|
||||||
when the data exists.
|
----------
|
||||||
|
exchange: Exchange
|
||||||
|
assets: list[TradingPair]
|
||||||
|
end_dt: datetime
|
||||||
|
bar_count: int
|
||||||
|
frequency: str
|
||||||
|
field: str
|
||||||
|
data_frequency: str
|
||||||
|
ffill: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
|
||||||
:param exchange:
|
|
||||||
:param assets:
|
|
||||||
:param end_dt:
|
|
||||||
:param bar_count:
|
|
||||||
:param frequency:
|
|
||||||
:param field:
|
|
||||||
:param data_frequency:
|
|
||||||
:param ffill:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle
|
||||||
|
|
||||||
|
freq, candle_size, unit, adj_data_frequency = get_frequency(
|
||||||
|
frequency, data_frequency
|
||||||
|
)
|
||||||
|
adj_bar_count = candle_size * bar_count
|
||||||
|
|
||||||
|
if data_frequency == 'minute' and adj_data_frequency == 'daily':
|
||||||
|
end_dt = end_dt.floor('1D')
|
||||||
|
|
||||||
bundle = self.exchange_bundles[exchange.name]
|
|
||||||
series = bundle.get_history_window_series_and_load(
|
series = bundle.get_history_window_series_and_load(
|
||||||
assets=assets,
|
assets=assets,
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
bar_count=bar_count,
|
bar_count=adj_bar_count,
|
||||||
field=field,
|
field=field,
|
||||||
data_frequency=data_frequency
|
data_frequency=adj_data_frequency,
|
||||||
|
algo_end_dt=self._last_available_session,
|
||||||
)
|
)
|
||||||
return pd.DataFrame(series)
|
|
||||||
|
|
||||||
def get_exchange_spot_value(self, exchange, assets, field, dt,
|
df = resample_history_df(pd.DataFrame(series), freq, field)
|
||||||
data_frequency):
|
return df
|
||||||
|
|
||||||
|
def get_exchange_spot_value(self,
|
||||||
|
exchange,
|
||||||
|
assets,
|
||||||
|
field,
|
||||||
|
dt,
|
||||||
|
data_frequency
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
A spot value for the exchange bundle. Try to ingest data if not in
|
||||||
|
the bundle.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange: Exchange
|
||||||
|
assets: list[TradingPair]
|
||||||
|
field: str
|
||||||
|
dt: datetime
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
|
"""
|
||||||
bundle = self.exchange_bundles[exchange.name]
|
bundle = self.exchange_bundles[exchange.name]
|
||||||
|
|
||||||
if data_frequency == 'daily':
|
if data_frequency == 'daily':
|
||||||
dt = dt.floor('1D')
|
dt = dt.floor('1D')
|
||||||
else:
|
else:
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import sys, traceback
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
from catalyst.errors import ZiplineError
|
from catalyst.errors import ZiplineError
|
||||||
|
|
||||||
|
|
||||||
def silent_except_hook(exctype, excvalue, exctraceback):
|
def silent_except_hook(exctype, excvalue, exctraceback):
|
||||||
if exctype in [PricingDataBeforeTradingError, PricingDataNotLoadedError,
|
if exctype in [PricingDataBeforeTradingError, PricingDataNotLoadedError,
|
||||||
SymbolNotFoundOnExchange, NoDataAvailableOnExchange, ]:
|
SymbolNotFoundOnExchange, NoDataAvailableOnExchange,
|
||||||
|
ExchangeAuthEmpty]:
|
||||||
fn = traceback.extract_tb(exctraceback)[-1][0]
|
fn = traceback.extract_tb(exctraceback)[-1][0]
|
||||||
ln = traceback.extract_tb(exctraceback)[-1][1]
|
ln = traceback.extract_tb(exctraceback)[-1][1]
|
||||||
print "Error traceback: {1} (line {2})\n" \
|
print("Error traceback: {1} (line {2})\n"
|
||||||
"{0.__name__}: {3}".format(exctype, fn, ln, excvalue)
|
"{0.__name__}: {3}".format(exctype, fn, ln, excvalue))
|
||||||
else:
|
else:
|
||||||
sys.__excepthook__(exctype, excvalue, exctraceback)
|
sys.__excepthook__(exctype, excvalue, exctraceback)
|
||||||
|
|
||||||
@@ -63,6 +66,13 @@ class ExchangeAuthNotFound(ZiplineError):
|
|||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
class ExchangeAuthEmpty(ZiplineError):
|
||||||
|
msg = (
|
||||||
|
'Please enter your API token key and secret for exchange {exchange} '
|
||||||
|
'in the following file: {filename}'
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
class ExchangeSymbolsNotFound(ZiplineError):
|
class ExchangeSymbolsNotFound(ZiplineError):
|
||||||
msg = (
|
msg = (
|
||||||
'Unable to download or find a local copy of symbols.json for exchange '
|
'Unable to download or find a local copy of symbols.json for exchange '
|
||||||
@@ -76,6 +86,14 @@ class AlgoPickleNotFound(ZiplineError):
|
|||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidHistoryFrequencyAlias(ZiplineError):
|
||||||
|
msg = (
|
||||||
|
'Invalid frequency alias {freq}. Valid suffixes are M (minute) '
|
||||||
|
'and D (day). For example, these aliases would be valid '
|
||||||
|
'1M, 5M, 1D.'
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
class InvalidHistoryFrequencyError(ZiplineError):
|
class InvalidHistoryFrequencyError(ZiplineError):
|
||||||
msg = (
|
msg = (
|
||||||
'Frequency {frequency} not supported by the exchange.'
|
'Frequency {frequency} not supported by the exchange.'
|
||||||
@@ -204,7 +222,9 @@ class PricingDataNotLoadedError(ZiplineError):
|
|||||||
class ApiCandlesError(ZiplineError):
|
class ApiCandlesError(ZiplineError):
|
||||||
msg = ('Unable to fetch candles from the remote API: {error}.').strip()
|
msg = ('Unable to fetch candles from the remote API: {error}.').strip()
|
||||||
|
|
||||||
|
|
||||||
class NoDataAvailableOnExchange(ZiplineError):
|
class NoDataAvailableOnExchange(ZiplineError):
|
||||||
msg = ('Requested data for trading pair {symbol} is not available on exchange {exchange} '
|
msg = (
|
||||||
|
'Requested data for trading pair {symbol} is not available on 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.').strip()
|
'Check `http://enigma.co/catalyst/status` for market coverage.').strip()
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder
|
|||||||
class ExchangeLimitOrder(LimitOrder):
|
class ExchangeLimitOrder(LimitOrder):
|
||||||
def get_limit_price(self, is_buy):
|
def get_limit_price(self, is_buy):
|
||||||
"""
|
"""
|
||||||
We may be trading Satoshis with 8 decimals, we cannot round numbers
|
We may be trading Satoshis with 8 decimals, we cannot round numbers.
|
||||||
:param is_buy:
|
|
||||||
:return:
|
Parameters
|
||||||
|
----------
|
||||||
|
is_buy: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.limit_price
|
return self.limit_price
|
||||||
|
|
||||||
@@ -14,9 +21,16 @@ class ExchangeLimitOrder(LimitOrder):
|
|||||||
class ExchangeStopOrder(StopOrder):
|
class ExchangeStopOrder(StopOrder):
|
||||||
def get_stop_price(self, is_buy):
|
def get_stop_price(self, is_buy):
|
||||||
"""
|
"""
|
||||||
We may be trading Satoshis with 8 decimals, we cannot round numbers
|
We may be trading Satoshis with 8 decimals, we cannot round numbers.
|
||||||
:param is_buy:
|
|
||||||
:return:
|
Parameters
|
||||||
|
----------
|
||||||
|
is_buy: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.stop_price
|
return self.stop_price
|
||||||
|
|
||||||
@@ -24,16 +38,30 @@ class ExchangeStopOrder(StopOrder):
|
|||||||
class ExchangeStopLimitOrder(StopLimitOrder):
|
class ExchangeStopLimitOrder(StopLimitOrder):
|
||||||
def get_limit_price(self, is_buy):
|
def get_limit_price(self, is_buy):
|
||||||
"""
|
"""
|
||||||
We may be trading Satoshis with 8 decimals, we cannot round numbers
|
We may be trading Satoshis with 8 decimals, we cannot round numbers.
|
||||||
:param is_buy:
|
|
||||||
:return:
|
Parameters
|
||||||
|
----------
|
||||||
|
is_buy: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.limit_price
|
return self.limit_price
|
||||||
|
|
||||||
def get_stop_price(self, is_buy):
|
def get_stop_price(self, is_buy):
|
||||||
"""
|
"""
|
||||||
We may be trading Satoshis with 8 decimals, we cannot round numbers
|
We may be trading Satoshis with 8 decimals, we cannot round numbers.
|
||||||
:param is_buy:
|
|
||||||
:return:
|
Parameters
|
||||||
|
----------
|
||||||
|
is_buy: bool
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.stop_price
|
return self.stop_price
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.protocol import Portfolio, Positions, Position
|
from catalyst.protocol import Portfolio, Positions, Position
|
||||||
|
from catalyst.utils.deprecate import deprecated
|
||||||
|
|
||||||
log = Logger('ExchangePortfolio')
|
log = Logger('ExchangePortfolio', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class ExchangePortfolio(Portfolio):
|
class ExchangePortfolio(Portfolio):
|
||||||
@@ -28,10 +30,15 @@ class ExchangePortfolio(Portfolio):
|
|||||||
self.positions_value = 0.0
|
self.positions_value = 0.0
|
||||||
self.open_orders = dict()
|
self.open_orders = dict()
|
||||||
|
|
||||||
def calculate_pnl(self):
|
|
||||||
log.debug('calculating pnl')
|
|
||||||
|
|
||||||
def create_order(self, order):
|
def create_order(self, order):
|
||||||
|
"""
|
||||||
|
Create an open order and store in memory.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order: Order
|
||||||
|
|
||||||
|
"""
|
||||||
log.debug('creating order {}'.format(order.id))
|
log.debug('creating order {}'.format(order.id))
|
||||||
self.open_orders[order.id] = order
|
self.open_orders[order.id] = order
|
||||||
|
|
||||||
@@ -46,6 +53,18 @@ class ExchangePortfolio(Portfolio):
|
|||||||
log.debug('open order added to portfolio')
|
log.debug('open order added to portfolio')
|
||||||
|
|
||||||
def execute_order(self, order, transaction):
|
def execute_order(self, order, transaction):
|
||||||
|
"""
|
||||||
|
Update the open orders and positions to apply an executed order.
|
||||||
|
|
||||||
|
Unlike with backtesting, we do not need to add slippage and fees.
|
||||||
|
The executed price includes transaction fees.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order: Order
|
||||||
|
transaction: Transaction
|
||||||
|
|
||||||
|
"""
|
||||||
log.debug('executing order {}'.format(order.id))
|
log.debug('executing order {}'.format(order.id))
|
||||||
del self.open_orders[order.id]
|
del self.open_orders[order.id]
|
||||||
|
|
||||||
@@ -70,7 +89,9 @@ class ExchangePortfolio(Portfolio):
|
|||||||
|
|
||||||
log.debug('updated portfolio with executed order')
|
log.debug('updated portfolio with executed order')
|
||||||
|
|
||||||
|
@deprecated
|
||||||
def execute_transaction(self, transaction):
|
def execute_transaction(self, transaction):
|
||||||
|
# TODO: almost duplicate of execute_order. Not sure why Poloniex needs this.
|
||||||
log.debug('executing transaction {}'.format(transaction.order_id))
|
log.debug('executing transaction {}'.format(transaction.order_id))
|
||||||
|
|
||||||
order_position = self.positions[transaction.asset] \
|
order_position = self.positions[transaction.asset] \
|
||||||
@@ -95,6 +116,14 @@ class ExchangePortfolio(Portfolio):
|
|||||||
log.debug('updated portfolio with executed order')
|
log.debug('updated portfolio with executed order')
|
||||||
|
|
||||||
def remove_order(self, order):
|
def remove_order(self, order):
|
||||||
|
"""
|
||||||
|
Removing an open order.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order: Order
|
||||||
|
|
||||||
|
"""
|
||||||
log.info('removing cancelled order {}'.format(order.id))
|
log.info('removing cancelled order {}'.format(order.id))
|
||||||
del self.open_orders[order.id]
|
del self.open_orders[order.id]
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import urllib
|
import re
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
|
from six.moves.urllib import request
|
||||||
|
|
||||||
from catalyst.exchange.exchange_errors import ExchangeAuthNotFound, \
|
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \
|
||||||
ExchangeSymbolsNotFound
|
InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias
|
||||||
from catalyst.utils.paths import data_root, ensure_directory, last_modified_time
|
from catalyst.utils.paths import data_root, ensure_directory, \
|
||||||
|
last_modified_time
|
||||||
|
|
||||||
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
||||||
'{exchange}/symbols.json'
|
'{exchange}/symbols.json'
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_folder(exchange_name, environ=None):
|
def get_exchange_folder(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The root path of an exchange folder.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
if not environ:
|
if not environ:
|
||||||
environ = os.environ
|
environ = os.environ
|
||||||
|
|
||||||
@@ -26,22 +42,63 @@ def get_exchange_folder(exchange_name, environ=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_exchange_symbols_filename(exchange_name, environ=None):
|
def get_exchange_symbols_filename(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The absolute path of the exchange's symbol.json file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name:
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||||
return os.path.join(exchange_folder, 'symbols.json')
|
return os.path.join(exchange_folder, 'symbols.json')
|
||||||
|
|
||||||
|
|
||||||
def download_exchange_symbols(exchange_name, environ=None):
|
def download_exchange_symbols(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
Downloads the exchange's symbols.json from the repository.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
filename = get_exchange_symbols_filename(exchange_name)
|
filename = get_exchange_symbols_filename(exchange_name)
|
||||||
url = SYMBOLS_URL.format(exchange=exchange_name)
|
url = SYMBOLS_URL.format(exchange=exchange_name)
|
||||||
response = urllib.urlretrieve(url=url, filename=filename)
|
response = request.urlretrieve(url=url, filename=filename)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_symbols(exchange_name, environ=None):
|
def get_exchange_symbols(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The de-serialized content of the exchange's symbols.json.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Object
|
||||||
|
|
||||||
|
"""
|
||||||
filename = get_exchange_symbols_filename(exchange_name)
|
filename = get_exchange_symbols_filename(exchange_name)
|
||||||
|
|
||||||
if not os.path.isfile(filename) or \
|
if not os.path.isfile(filename) or \
|
||||||
pd.Timedelta(pd.Timestamp('now', tz='UTC') - last_modified_time(filename)).days > 1:
|
pd.Timedelta(pd.Timestamp('now',
|
||||||
|
tz='UTC') - last_modified_time(
|
||||||
|
filename)).days > 1:
|
||||||
download_exchange_symbols(exchange_name, environ)
|
download_exchange_symbols(exchange_name, environ)
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
@@ -55,7 +112,37 @@ def get_exchange_symbols(exchange_name, environ=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_symbols_string(assets):
|
||||||
|
"""
|
||||||
|
A concatenated string of symbols from a list of assets.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
assets: list[TradingPair]
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
|
array = [assets] if isinstance(assets, TradingPair) else assets
|
||||||
|
return ', '.join([asset.symbol for asset in array])
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_auth(exchange_name, environ=None):
|
def get_exchange_auth(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The de-serialized contend of the exchange's auth.json file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Object
|
||||||
|
|
||||||
|
"""
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||||
filename = os.path.join(exchange_folder, 'auth.json')
|
filename = os.path.join(exchange_folder, 'auth.json')
|
||||||
|
|
||||||
@@ -64,13 +151,27 @@ def get_exchange_auth(exchange_name, environ=None):
|
|||||||
data = json.load(data_file)
|
data = json.load(data_file)
|
||||||
return data
|
return data
|
||||||
else:
|
else:
|
||||||
raise ExchangeAuthNotFound(
|
data = dict(name=exchange_name, key='', secret='')
|
||||||
exchange=exchange_name,
|
with open(filename, 'w') as f:
|
||||||
filename=filename
|
json.dump(data, f, sort_keys=False, indent=2,
|
||||||
)
|
separators=(',', ':'))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_algo_folder(algo_name, environ=None):
|
def get_algo_folder(algo_name, environ=None):
|
||||||
|
"""
|
||||||
|
The algorithm root folder of the algorithm.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
algo_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
if not environ:
|
if not environ:
|
||||||
environ = os.environ
|
environ = os.environ
|
||||||
|
|
||||||
@@ -82,6 +183,21 @@ def get_algo_folder(algo_name, environ=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_algo_object(algo_name, key, environ=None, rel_path=None):
|
def get_algo_object(algo_name, key, environ=None, rel_path=None):
|
||||||
|
"""
|
||||||
|
The de-serialized object of the algo name and key.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
algo_name: str
|
||||||
|
key: str
|
||||||
|
environ:
|
||||||
|
rel_path: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Object
|
||||||
|
|
||||||
|
"""
|
||||||
if algo_name is None:
|
if algo_name is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -103,6 +219,18 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None):
|
|||||||
|
|
||||||
|
|
||||||
def save_algo_object(algo_name, key, obj, environ=None, rel_path=None):
|
def save_algo_object(algo_name, key, obj, environ=None, rel_path=None):
|
||||||
|
"""
|
||||||
|
Serialize and save an object by algo name and key.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
algo_name: str
|
||||||
|
key: str
|
||||||
|
obj: Object
|
||||||
|
environ:
|
||||||
|
rel_path: str
|
||||||
|
|
||||||
|
"""
|
||||||
folder = get_algo_folder(algo_name, environ)
|
folder = get_algo_folder(algo_name, environ)
|
||||||
|
|
||||||
if rel_path is not None:
|
if rel_path is not None:
|
||||||
@@ -115,16 +243,22 @@ def save_algo_object(algo_name, key, obj, environ=None, rel_path=None):
|
|||||||
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
|
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
|
|
||||||
|
|
||||||
def append_algo_object(algo_name, key, obj, environ=None):
|
|
||||||
algo_folder = get_algo_folder(algo_name, environ)
|
|
||||||
filename = os.path.join(algo_folder, key + '.p')
|
|
||||||
|
|
||||||
mode = 'a+b' if os.path.isfile(filename) else 'wb'
|
|
||||||
with open(filename, mode) as handle:
|
|
||||||
pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL)
|
|
||||||
|
|
||||||
|
|
||||||
def get_algo_df(algo_name, key, environ=None, rel_path=None):
|
def get_algo_df(algo_name, key, environ=None, rel_path=None):
|
||||||
|
"""
|
||||||
|
The de-serialized DataFrame of an algo name and key.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
algo_name: str
|
||||||
|
key: str
|
||||||
|
environ:
|
||||||
|
rel_path: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
|
||||||
|
"""
|
||||||
folder = get_algo_folder(algo_name, environ)
|
folder = get_algo_folder(algo_name, environ)
|
||||||
|
|
||||||
if rel_path is not None:
|
if rel_path is not None:
|
||||||
@@ -143,19 +277,43 @@ def get_algo_df(algo_name, key, environ=None, rel_path=None):
|
|||||||
|
|
||||||
|
|
||||||
def save_algo_df(algo_name, key, df, environ=None, rel_path=None):
|
def save_algo_df(algo_name, key, df, environ=None, rel_path=None):
|
||||||
folder = get_algo_folder(algo_name, environ)
|
"""
|
||||||
|
Serialize to csv and save a DataFrame by algo name and key.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
algo_name: str
|
||||||
|
key: str
|
||||||
|
df: pd.DataFrame
|
||||||
|
environ:
|
||||||
|
rel_path: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
folder = get_algo_folder(algo_name, environ)
|
||||||
if rel_path is not None:
|
if rel_path is not None:
|
||||||
folder = os.path.join(folder, rel_path)
|
folder = os.path.join(folder, rel_path)
|
||||||
ensure_directory(folder)
|
ensure_directory(folder)
|
||||||
|
|
||||||
filename = os.path.join(folder, key + '.csv')
|
filename = os.path.join(folder, key + '.csv')
|
||||||
|
|
||||||
with open(filename, 'wb') as handle:
|
with open(filename, 'wt') as handle:
|
||||||
df.to_csv(handle)
|
df.to_csv(handle, encoding='UTF_8')
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_minute_writer_root(exchange_name, environ=None):
|
def get_exchange_minute_writer_root(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The minute writer folder for the exchange.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
BcolzExchangeBarWriter
|
||||||
|
|
||||||
|
"""
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||||
|
|
||||||
minute_data_folder = os.path.join(exchange_folder, 'minute_data')
|
minute_data_folder = os.path.join(exchange_folder, 'minute_data')
|
||||||
@@ -163,7 +321,21 @@ def get_exchange_minute_writer_root(exchange_name, environ=None):
|
|||||||
|
|
||||||
return minute_data_folder
|
return minute_data_folder
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_bundles_folder(exchange_name, environ=None):
|
def get_exchange_bundles_folder(exchange_name, environ=None):
|
||||||
|
"""
|
||||||
|
The temp folder for bundle downloads by algo name.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchange_name: str
|
||||||
|
environ:
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||||
|
|
||||||
temp_bundles = os.path.join(exchange_folder, 'temp_bundles')
|
temp_bundles = os.path.join(exchange_folder, 'temp_bundles')
|
||||||
@@ -173,8 +345,140 @@ def get_exchange_bundles_folder(exchange_name, environ=None):
|
|||||||
|
|
||||||
|
|
||||||
def perf_serial(obj):
|
def perf_serial(obj):
|
||||||
"""JSON serializer for objects not serializable by default json code"""
|
"""
|
||||||
|
JSON serializer for objects not serializable by default json code
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj: Object
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(obj, (datetime, date)):
|
if isinstance(obj, (datetime, date)):
|
||||||
return obj.isoformat()
|
return obj.isoformat()
|
||||||
|
|
||||||
raise TypeError("Type %s not serializable" % type(obj))
|
raise TypeError("Type %s not serializable" % type(obj))
|
||||||
|
|
||||||
|
|
||||||
|
def get_common_assets(exchanges):
|
||||||
|
"""
|
||||||
|
The assets available in all specified exchanges.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
exchanges: list[Exchange]
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[TradingPair]
|
||||||
|
|
||||||
|
"""
|
||||||
|
symbols = []
|
||||||
|
for exchange_name in exchanges:
|
||||||
|
s = [asset.symbol for asset in exchanges[exchange_name].get_assets()]
|
||||||
|
symbols.append(s)
|
||||||
|
|
||||||
|
inter_symbols = set.intersection(*map(set, symbols))
|
||||||
|
|
||||||
|
assets = []
|
||||||
|
for symbol in inter_symbols:
|
||||||
|
for exchange_name in exchanges:
|
||||||
|
asset = exchanges[exchange_name].get_asset(symbol)
|
||||||
|
assets.append(asset)
|
||||||
|
|
||||||
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
def get_frequency(freq, data_frequency):
|
||||||
|
"""
|
||||||
|
Get the frequency parameters.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
We're trying to use Pandas convention for frequency aliases.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
freq: str
|
||||||
|
data_frequency: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str, int, str, str
|
||||||
|
|
||||||
|
"""
|
||||||
|
if freq == 'minute':
|
||||||
|
unit = 'T'
|
||||||
|
candle_size = 1
|
||||||
|
|
||||||
|
elif freq == 'daily':
|
||||||
|
unit = 'D'
|
||||||
|
candle_size = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I)
|
||||||
|
if freq_match:
|
||||||
|
candle_size = int(freq_match.group(1)) if freq_match.group(1) \
|
||||||
|
else 1
|
||||||
|
unit = freq_match.group(2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||||
|
|
||||||
|
if unit.lower() == 'd':
|
||||||
|
alias = '{}D'.format(candle_size)
|
||||||
|
|
||||||
|
if data_frequency == 'minute':
|
||||||
|
data_frequency = 'daily'
|
||||||
|
|
||||||
|
elif unit.lower() == 'm' or unit == 'T':
|
||||||
|
alias = '{}T'.format(candle_size)
|
||||||
|
|
||||||
|
if data_frequency == 'daily':
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
|
# elif unit.lower() == 'h':
|
||||||
|
# candle_size = candle_size * 60
|
||||||
|
#
|
||||||
|
# alias = '{}T'.format(candle_size)
|
||||||
|
# if data_frequency == 'daily':
|
||||||
|
# data_frequency = 'minute'
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise InvalidHistoryFrequencyAlias(freq=freq)
|
||||||
|
|
||||||
|
return alias, candle_size, unit, data_frequency
|
||||||
|
|
||||||
|
|
||||||
|
def resample_history_df(df, freq, field):
|
||||||
|
"""
|
||||||
|
Resample the OHCLV DataFrame using the specified frequency.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
df: DataFrame
|
||||||
|
freq: str
|
||||||
|
field: str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DataFrame
|
||||||
|
|
||||||
|
"""
|
||||||
|
if field == 'open':
|
||||||
|
agg = 'first'
|
||||||
|
elif field == 'high':
|
||||||
|
agg = 'max'
|
||||||
|
elif field == 'low':
|
||||||
|
agg = 'min'
|
||||||
|
elif field == 'close':
|
||||||
|
agg = 'last'
|
||||||
|
elif field == 'volume':
|
||||||
|
agg = 'sum'
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid field.')
|
||||||
|
|
||||||
|
return df.resample(freq).agg(agg)
|
||||||
|
|||||||
@@ -5,28 +5,39 @@ from catalyst.exchange.exchange_utils import get_exchange_auth
|
|||||||
from catalyst.exchange.poloniex.poloniex import Poloniex
|
from catalyst.exchange.poloniex.poloniex import Poloniex
|
||||||
|
|
||||||
|
|
||||||
def get_exchange(exchange_name):
|
def get_exchange(exchange_name, base_currency=None):
|
||||||
exchange_auth = get_exchange_auth(exchange_name)
|
exchange_auth = get_exchange_auth(exchange_name)
|
||||||
if exchange_name == 'bitfinex':
|
if exchange_name == 'bitfinex':
|
||||||
return Bitfinex(
|
return Bitfinex(
|
||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
secret=exchange_auth['secret'],
|
secret=exchange_auth['secret'],
|
||||||
base_currency=None, # TODO: make optional at the exchange
|
base_currency=base_currency,
|
||||||
portfolio=None
|
portfolio=None
|
||||||
)
|
)
|
||||||
|
|
||||||
elif exchange_name == 'bittrex':
|
elif exchange_name == 'bittrex':
|
||||||
return Bittrex(
|
return Bittrex(
|
||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
secret=exchange_auth['secret'],
|
secret=exchange_auth['secret'],
|
||||||
base_currency=None,
|
base_currency=base_currency,
|
||||||
portfolio=None
|
portfolio=None
|
||||||
)
|
)
|
||||||
|
|
||||||
elif exchange_name == 'poloniex':
|
elif exchange_name == 'poloniex':
|
||||||
return Poloniex(
|
return Poloniex(
|
||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
secret=exchange_auth['secret'],
|
secret=exchange_auth['secret'],
|
||||||
base_currency=None,
|
base_currency=base_currency,
|
||||||
portfolio=None
|
portfolio=None
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ExchangeNotFoundError(exchange_name=exchange_name)
|
raise ExchangeNotFoundError(exchange_name=exchange_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_exchanges(exchange_names):
|
||||||
|
exchanges = dict()
|
||||||
|
for exchange_name in exchange_names:
|
||||||
|
exchanges[exchange_name] = get_exchange(exchange_name)
|
||||||
|
|
||||||
|
return exchanges
|
||||||
@@ -1,17 +1,3 @@
|
|||||||
#
|
|
||||||
# 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 datetime import timedelta
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from catalyst.gens.sim_engine import (
|
from catalyst.gens.sim_engine import (
|
||||||
BAR,
|
BAR,
|
||||||
@@ -19,11 +5,11 @@ from catalyst.gens.sim_engine import (
|
|||||||
)
|
)
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.exchange_errors import \
|
from catalyst.exchange.exchange_errors import \
|
||||||
MismatchingBaseCurrenciesExchanges
|
MismatchingBaseCurrenciesExchanges
|
||||||
|
|
||||||
|
log = Logger('LiveGraphClock', level=LOG_LEVEL)
|
||||||
log = Logger('LiveGraphClock')
|
|
||||||
|
|
||||||
|
|
||||||
class LiveGraphClock(object):
|
class LiveGraphClock(object):
|
||||||
@@ -34,8 +20,8 @@ class LiveGraphClock(object):
|
|||||||
|
|
||||||
This mixes the clock with a live graph.
|
This mixes the clock with a live graph.
|
||||||
|
|
||||||
Note
|
Notes
|
||||||
----
|
-----
|
||||||
This seemingly awkward approach allows us to run the program using a single
|
This seemingly awkward approach allows us to run the program using a single
|
||||||
thread. This is important because Matplotlib does not play nice with
|
thread. This is important because Matplotlib does not play nice with
|
||||||
multi-threaded environments. Zipline probably does not either.
|
multi-threaded environments. Zipline probably does not either.
|
||||||
@@ -96,11 +82,12 @@ class LiveGraphClock(object):
|
|||||||
"""
|
"""
|
||||||
Trying to assign reasonable parameters to the time axis.
|
Trying to assign reasonable parameters to the time axis.
|
||||||
|
|
||||||
TODO: room for improvement
|
Parameters
|
||||||
|
----------
|
||||||
|
ax:
|
||||||
|
|
||||||
:param ax:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
# TODO: room for improvement
|
||||||
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
|
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
|
||||||
ax.xaxis.set_major_formatter(self.fmt)
|
ax.xaxis.set_major_formatter(self.fmt)
|
||||||
|
|
||||||
@@ -114,9 +101,21 @@ class LiveGraphClock(object):
|
|||||||
ax.grid(True)
|
ax.grid(True)
|
||||||
|
|
||||||
def set_legend(self, ax):
|
def set_legend(self, ax):
|
||||||
|
"""
|
||||||
|
Set legend on the chart.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ax
|
||||||
|
|
||||||
|
"""
|
||||||
ax.legend(loc='upper left', ncol=1, fontsize=10, numpoints=1)
|
ax.legend(loc='upper left', ncol=1, fontsize=10, numpoints=1)
|
||||||
|
|
||||||
def draw_pnl(self):
|
def draw_pnl(self):
|
||||||
|
"""
|
||||||
|
Draw p&l line on the chart.
|
||||||
|
|
||||||
|
"""
|
||||||
ax = self.ax_pnl
|
ax = self.ax_pnl
|
||||||
df = self.context.pnl_stats
|
df = self.context.pnl_stats
|
||||||
|
|
||||||
@@ -137,6 +136,10 @@ class LiveGraphClock(object):
|
|||||||
self.format_ax(ax)
|
self.format_ax(ax)
|
||||||
|
|
||||||
def draw_custom_signals(self):
|
def draw_custom_signals(self):
|
||||||
|
"""
|
||||||
|
Draw custom signals on the chart.
|
||||||
|
|
||||||
|
"""
|
||||||
ax = self.ax_custom_signals
|
ax = self.ax_custom_signals
|
||||||
df = self.context.custom_signals_stats
|
df = self.context.custom_signals_stats
|
||||||
|
|
||||||
@@ -155,6 +158,10 @@ class LiveGraphClock(object):
|
|||||||
self.format_ax(ax)
|
self.format_ax(ax)
|
||||||
|
|
||||||
def draw_exposure(self):
|
def draw_exposure(self):
|
||||||
|
"""
|
||||||
|
Draw exposure line on the chart.
|
||||||
|
|
||||||
|
"""
|
||||||
ax = self.ax_exposure
|
ax = self.ax_exposure
|
||||||
context = self.context
|
context = self.context
|
||||||
df = context.exposure_stats
|
df = context.exposure_stats
|
||||||
|
|||||||
@@ -1,44 +1,39 @@
|
|||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import json
|
import json
|
||||||
import re
|
import json
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytz
|
import pytz
|
||||||
import requests
|
|
||||||
# import six
|
|
||||||
from six import iteritems
|
|
||||||
from catalyst.assets._assets import TradingPair
|
from catalyst.assets._assets import TradingPair
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
# import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.poloniex.poloniex_api import Poloniex_api
|
|
||||||
|
|
||||||
# from websocket import create_connection
|
# from websocket import create_connection
|
||||||
from catalyst.exchange.exchange import Exchange
|
from catalyst.exchange.exchange import Exchange
|
||||||
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError,
|
||||||
InvalidHistoryFrequencyError,
|
InvalidHistoryFrequencyError,
|
||||||
InvalidOrderStyle, OrderCancelError,
|
InvalidOrderStyle, OrphanOrderReverseError)
|
||||||
OrphanOrderReverseError)
|
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \
|
||||||
ExchangeStopLimitOrder, ExchangeStopOrder
|
ExchangeStopLimitOrder
|
||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
|
||||||
from catalyst.protocol import Account
|
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \
|
||||||
download_exchange_symbols
|
download_exchange_symbols, get_symbols_string
|
||||||
|
from catalyst.exchange.poloniex.poloniex_api import Poloniex_api
|
||||||
|
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
|
||||||
|
|
||||||
log = Logger('Poloniex')
|
log = Logger('Poloniex', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
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.encode('UTF-8'))
|
self.api = Poloniex_api(key=key, secret=secret)
|
||||||
self.name = 'poloniex'
|
self.name = 'poloniex'
|
||||||
self.assets = {}
|
self.assets = {}
|
||||||
self.load_assets()
|
self.load_assets()
|
||||||
@@ -124,9 +119,9 @@ class Poloniex(Exchange):
|
|||||||
return order, executed_price
|
return order, executed_price
|
||||||
|
|
||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
log.debug('retrieving wallets balances')
|
|
||||||
try:
|
|
||||||
balances = self.api.returnbalances()
|
balances = self.api.returnbalances()
|
||||||
|
try:
|
||||||
|
log.debug('retrieving wallets balances')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(e)
|
log.debug(e)
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
@@ -176,12 +171,12 @@ class Poloniex(Exchange):
|
|||||||
# TODO: fetch account data and keep in cache
|
# TODO: fetch account data and keep in cache
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
def get_candles(self, freq, assets, bar_count=None,
|
||||||
start_dt=None, end_dt=None):
|
start_dt=None, end_dt=None):
|
||||||
"""
|
"""
|
||||||
Retrieve OHLVC candles from Poloniex
|
Retrieve OHLVC candles from Poloniex
|
||||||
|
|
||||||
:param data_frequency:
|
:param freq:
|
||||||
:param assets:
|
:param assets:
|
||||||
:param bar_count:
|
:param bar_count:
|
||||||
:return:
|
:return:
|
||||||
@@ -191,25 +186,40 @@ class Poloniex(Exchange):
|
|||||||
'5m', '15m', '30m', '2h', '4h', '1D'
|
'5m', '15m', '30m', '2h', '4h', '1D'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: implement end_dt and start_dt filters
|
if end_dt is None:
|
||||||
|
end_dt = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
if (
|
log.debug(
|
||||||
data_frequency == '5m' or data_frequency == 'minute'): # TODO: Polo does not have '1m'
|
'retrieving {bars} {freq} candles on {exchange} from '
|
||||||
|
'{end_dt} for markets {symbols}, '.format(
|
||||||
|
bars=bar_count,
|
||||||
|
freq=freq,
|
||||||
|
exchange=self.name,
|
||||||
|
end_dt=end_dt,
|
||||||
|
symbols=get_symbols_string(assets)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if freq == '1T' and (bar_count == 1 or bar_count is None):
|
||||||
|
# TODO: use the order book instead
|
||||||
|
# We use the 5m to fetch the last bar
|
||||||
frequency = 300
|
frequency = 300
|
||||||
elif (data_frequency == '15m'):
|
elif freq == '5T':
|
||||||
|
frequency = 300
|
||||||
|
elif freq == '15T':
|
||||||
frequency = 900
|
frequency = 900
|
||||||
elif (data_frequency == '30m'):
|
elif freq == '30T':
|
||||||
frequency = 1800
|
frequency = 1800
|
||||||
elif (data_frequency == '2h'):
|
elif freq == '120T':
|
||||||
frequency = 7200
|
frequency = 7200
|
||||||
elif (data_frequency == '4h'):
|
elif freq == '240T':
|
||||||
frequency = 14400
|
frequency = 14400
|
||||||
elif (data_frequency == '1D' or data_frequency == 'daily'):
|
elif freq == '1D':
|
||||||
frequency = 86400
|
frequency = 86400
|
||||||
else:
|
else:
|
||||||
raise InvalidHistoryFrequencyError(
|
# Poloniex does not offer 1m data candles
|
||||||
frequency=data_frequency
|
# It is likely to error out there frequently
|
||||||
)
|
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||||
|
|
||||||
# Making sure that assets are iterable
|
# Making sure that assets are iterable
|
||||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||||
@@ -217,15 +227,18 @@ class Poloniex(Exchange):
|
|||||||
|
|
||||||
for asset in asset_list:
|
for asset in asset_list:
|
||||||
|
|
||||||
|
# TODO: what's wrong with this?
|
||||||
|
# end = int(time.mktime(end_dt.timetuple()))
|
||||||
end = int(time.time())
|
end = int(time.time())
|
||||||
if (bar_count is None):
|
if bar_count is None:
|
||||||
start = end - 2 * frequency
|
start = end - 2 * frequency
|
||||||
else:
|
else:
|
||||||
start = end - bar_count * frequency
|
start = end - bar_count * frequency
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.api.returnchartdata(self.get_symbol(asset),
|
response = self.api.returnchartdata(
|
||||||
frequency, start, end)
|
self.get_symbol(asset), frequency, start, end
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,21 @@ class Poloniex_api(object):
|
|||||||
self.public = ['returnTicker', 'return24Volume', 'returnOrderBook',
|
self.public = ['returnTicker', 'return24Volume', 'returnOrderBook',
|
||||||
'returnTradeHistory', 'returnChartData',
|
'returnTradeHistory', 'returnChartData',
|
||||||
'returnCurrencies', 'returnLoanOrders']
|
'returnCurrencies', 'returnLoanOrders']
|
||||||
self.trading = ['returnBalances','returnCompleteBalances','returnDepositAddresses',
|
self.trading = ['returnBalances', 'returnCompleteBalances',
|
||||||
'generateNewAddress','returnDepositsWithdrawals','returnOpenOrders',
|
'returnDepositAddresses',
|
||||||
|
'generateNewAddress', 'returnDepositsWithdrawals',
|
||||||
|
'returnOpenOrders',
|
||||||
'returnTradeHistory', 'returnOrderTrades',
|
'returnTradeHistory', 'returnOrderTrades',
|
||||||
'buy', 'sell', 'cancelOrder', 'moveOrder',
|
'buy', 'sell', 'cancelOrder', 'moveOrder',
|
||||||
'withdraw', 'returnFeeInfo','returnAvailableAccountBalances',
|
'withdraw', 'returnFeeInfo',
|
||||||
|
'returnAvailableAccountBalances',
|
||||||
'returnTradableBalances', 'transferBalance',
|
'returnTradableBalances', 'transferBalance',
|
||||||
'returnMarginAccountSummary','marginBuy','marginSell',
|
'returnMarginAccountSummary', 'marginBuy',
|
||||||
'getMarginPosition', 'closeMarginPosition','createLoanOffer',
|
'marginSell',
|
||||||
'cancelLoanOffer','returnOpenLoanOffers','returnActiveLoans',
|
'getMarginPosition', 'closeMarginPosition',
|
||||||
|
'createLoanOffer',
|
||||||
|
'cancelLoanOffer', 'returnOpenLoanOffers',
|
||||||
|
'returnActiveLoans',
|
||||||
'returnLendingHistory', 'toggleAutoRenew']
|
'returnLendingHistory', 'toggleAutoRenew']
|
||||||
|
|
||||||
def ask_request(self):
|
def ask_request(self):
|
||||||
@@ -50,7 +56,7 @@ class Poloniex_api(object):
|
|||||||
self.request_cpt[now] = 0
|
self.request_cpt[now] = 0
|
||||||
return True
|
return True
|
||||||
|
|
||||||
cpt_date = self.request_cpt.keys()[0]
|
cpt_date = list(self.request_cpt.keys())[0]
|
||||||
cpt = self.request_cpt[cpt_date]
|
cpt = self.request_cpt[cpt_date]
|
||||||
|
|
||||||
if now > cpt_date + 1:
|
if now > cpt_date + 1:
|
||||||
@@ -60,8 +66,7 @@ class Poloniex_api(object):
|
|||||||
|
|
||||||
if cpt >= self.max_requests_per_second:
|
if cpt >= self.max_requests_per_second:
|
||||||
|
|
||||||
log.debug('max requests 6 reached, sleeping for 1 seconds')
|
time.sleep(1)
|
||||||
sleep(1)
|
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
self.request_cpt = dict()
|
self.request_cpt = dict()
|
||||||
@@ -73,7 +78,8 @@ class Poloniex_api(object):
|
|||||||
def query(self, method, req={}):
|
def query(self, method, req={}):
|
||||||
|
|
||||||
if method in self.public:
|
if method in self.public:
|
||||||
url = 'https://poloniex.com/public?command=' + method + '&' + urllib.parse.urlencode(req)
|
url = 'https://poloniex.com/public?command=' + method + '&' + \
|
||||||
|
urllib.parse.urlencode(req)
|
||||||
headers = {}
|
headers = {}
|
||||||
post_data = None
|
post_data = None
|
||||||
elif method in self.trading:
|
elif method in self.trading:
|
||||||
@@ -81,13 +87,25 @@ class Poloniex_api(object):
|
|||||||
req['command'] = method
|
req['command'] = method
|
||||||
req['nonce'] = int(time.time() * 1000)
|
req['nonce'] = int(time.time() * 1000)
|
||||||
post_data = urllib.parse.urlencode(req)
|
post_data = urllib.parse.urlencode(req)
|
||||||
signature = hmac.new(self.secret, post_data, hashlib.sha512).hexdigest()
|
|
||||||
|
signature = hmac.new(self.secret.encode('utf-8'),
|
||||||
|
post_data.encode('utf-8'),
|
||||||
|
hashlib.sha512).hexdigest()
|
||||||
headers = {'Sign': signature, 'Key': self.key}
|
headers = {'Sign': signature, 'Key': self.key}
|
||||||
|
|
||||||
|
post_data = post_data.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
raise ValueError('Method "' + method + '" not found in neither the Public API or Trading API endpoints')
|
raise ValueError(
|
||||||
|
'Method "' + method + '" not found in neither the Public API '
|
||||||
|
'or Trading API endpoints'
|
||||||
|
)
|
||||||
|
|
||||||
self.ask_request()
|
self.ask_request()
|
||||||
req = urllib.request.Request(url, data=post_data, headers=headers)
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=post_data,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
return json.loads(urlopen(req).read())
|
return json.loads(urlopen(req).read())
|
||||||
|
|
||||||
def returnticker(self):
|
def returnticker(self):
|
||||||
@@ -102,12 +120,14 @@ class Poloniex_api(object):
|
|||||||
def returntradehistory(self, market, start=None, end=None):
|
def returntradehistory(self, market, start=None, end=None):
|
||||||
if (start is not None and end is not None):
|
if (start is not None and end is not None):
|
||||||
return self.query('returntradehistory',
|
return self.query('returntradehistory',
|
||||||
{'currencyPair': market, 'start': start, 'end': end })
|
{'currencyPair': market, 'start': start,
|
||||||
|
'end': end})
|
||||||
else:
|
else:
|
||||||
return self.query('returntradehistory', {'currencyPair': market})
|
return self.query('returntradehistory', {'currencyPair': market})
|
||||||
|
|
||||||
def returnchartdata(self, market, period, start, end=9999999999):
|
def returnchartdata(self, market, period, start, end=9999999999):
|
||||||
return self.query('returnChartData', {'currencyPair': market, 'period': period,
|
return self.query('returnChartData',
|
||||||
|
{'currencyPair': market, 'period': period,
|
||||||
'start': start, 'end': end})
|
'start': start, 'end': end})
|
||||||
|
|
||||||
def returncurrencies(self):
|
def returncurrencies(self):
|
||||||
@@ -132,7 +152,8 @@ class Poloniex_api(object):
|
|||||||
return self.query('generateNewAddress', {'currency': currency})
|
return self.query('generateNewAddress', {'currency': currency})
|
||||||
|
|
||||||
def returnDepositsWithdrawals(self, start, end):
|
def returnDepositsWithdrawals(self, start, end):
|
||||||
return self.query('returnDepositsWithdrawals', {'start': start, 'end': end})
|
return self.query('returnDepositsWithdrawals',
|
||||||
|
{'start': start, 'end': end})
|
||||||
|
|
||||||
def returnopenorders(self, market):
|
def returnopenorders(self, market):
|
||||||
return self.query('returnOpenOrders', {'currencyPair': market})
|
return self.query('returnOpenOrders', {'currencyPair': market})
|
||||||
@@ -144,31 +165,41 @@ class Poloniex_api(object):
|
|||||||
def returnordertrades(self, ordernumber):
|
def returnordertrades(self, ordernumber):
|
||||||
return self.query('returnOrderTrades', {'orderNumber': ordernumber})
|
return self.query('returnOrderTrades', {'orderNumber': ordernumber})
|
||||||
|
|
||||||
def buy(self, market, amount, rate, fillorkill=0, immediateorcancel=0, postonly=0):
|
def buy(self, market, amount, rate, fillorkill=0, immediateorcancel=0,
|
||||||
|
postonly=0):
|
||||||
if (fillorkill):
|
if (fillorkill):
|
||||||
return self.query('buy', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'fillOrKill': fillorkill, })
|
'fillOrKill': fillorkill, })
|
||||||
elif (immediateorcancel):
|
elif (immediateorcancel):
|
||||||
return self.query('buy', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'immediateOrCancel': immediateorcancel, })
|
'immediateOrCancel': immediateorcancel, })
|
||||||
elif (postonly):
|
elif (postonly):
|
||||||
return self.query('buy', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'postOnly': postonly, })
|
'postOnly': postonly, })
|
||||||
else:
|
else:
|
||||||
return self.query('buy', {'currencyPair': market, 'rate':rate, 'amount': amount, })
|
return self.query('buy', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount, })
|
||||||
|
|
||||||
def sell(self, market, amount, rate, fillorkill=0, immediateorcancel=0, postonly=0):
|
def sell(self, market, amount, rate, fillorkill=0, immediateorcancel=0,
|
||||||
|
postonly=0):
|
||||||
if (fillorkill):
|
if (fillorkill):
|
||||||
return self.query('sell', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'fillOrKill': fillorkill, })
|
'fillOrKill': fillorkill, })
|
||||||
elif (immediateorcancel):
|
elif (immediateorcancel):
|
||||||
return self.query('sell', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'immediateOrCancel': immediateorcancel, })
|
'immediateOrCancel': immediateorcancel, })
|
||||||
elif (postonly):
|
elif (postonly):
|
||||||
return self.query('sell', {'currencyPair': market, 'rate':rate, 'amount': amount,
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount,
|
||||||
'postOnly': postonly, })
|
'postOnly': postonly, })
|
||||||
else:
|
else:
|
||||||
return self.query('sell', {'currencyPair': market, 'rate':rate, 'amount': amount, })
|
return self.query('sell', {'currencyPair': market, 'rate': rate,
|
||||||
|
'amount': amount, })
|
||||||
|
|
||||||
def cancelorder(self, ordernumber):
|
def cancelorder(self, ordernumber):
|
||||||
return self.query('cancelOrder', {'orderNumber': ordernumber})
|
return self.query('cancelOrder', {'orderNumber': ordernumber})
|
||||||
@@ -180,4 +211,3 @@ class Poloniex_api(object):
|
|||||||
|
|
||||||
def returnfeeinfo(self):
|
def returnfeeinfo(self):
|
||||||
return self.query('returnFeeInfo')
|
return self.query('returnFeeInfo')
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ from time import sleep
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from catalyst.gens.sim_engine import (
|
from catalyst.gens.sim_engine import (
|
||||||
BAR,
|
BAR,
|
||||||
SESSION_START,
|
SESSION_START
|
||||||
MINUTE_END,
|
|
||||||
SESSION_END
|
|
||||||
)
|
)
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('ExchangeClock')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('ExchangeClock', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class SimpleClock(object):
|
class SimpleClock(object):
|
||||||
|
|||||||
@@ -1,14 +1,73 @@
|
|||||||
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def crossover(source, target):
|
||||||
|
"""
|
||||||
|
The `x`-series is defined as having crossed over `y`-series if the value
|
||||||
|
of `x` is greater than the value of `y` and the value of `x` was less than
|
||||||
|
the value of `y` on the bar immediately preceding the current bar.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
source: Series
|
||||||
|
target: Series
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
|
||||||
|
"""
|
||||||
|
if source[-1] is np.nan or source[-2] is np.nan \
|
||||||
|
or target[-1] is np.nan or target[-2] is np.nan:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if source[-1] > target[-1] and source[-2] < target[-2]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def crossunder(source, target):
|
||||||
|
"""
|
||||||
|
The `x`-series is defined as having crossed under `y`-series if the value
|
||||||
|
of `x` is less than the value of `y` and the value of `x` was greater than
|
||||||
|
the value of `y` on the bar immediately preceding the current bar.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
source: Series
|
||||||
|
target: Series
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
|
||||||
|
"""
|
||||||
|
if source[-1] is np.nan or source[-2] is np.nan \
|
||||||
|
or target[-1] is np.nan or target[-2] is np.nan:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if source[-1] < target[-1] and source[-2] > target[-2]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10):
|
def get_pretty_stats(stats_df, 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.
|
||||||
|
|
||||||
:param stats_df:
|
Parameters
|
||||||
:param num_rows:
|
----------
|
||||||
:return:
|
stats_df: DataFrame
|
||||||
|
num_rows: int
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
stats_df.set_index('period_close', drop=True, inplace=True)
|
stats_df.set_index('period_close', drop=True, inplace=True)
|
||||||
stats_df.dropna(axis=1, how='all', inplace=True)
|
stats_df.dropna(axis=1, how='all', inplace=True)
|
||||||
@@ -49,3 +108,24 @@ def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10):
|
|||||||
columns=columns,
|
columns=columns,
|
||||||
formatters=formatters
|
formatters=formatters
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def df_to_string(df):
|
||||||
|
"""
|
||||||
|
Create a formatted str representation of the DataFrame.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
df: DataFrame
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
|
pd.set_option('display.expand_frame_repr', False)
|
||||||
|
pd.set_option('precision', 8)
|
||||||
|
pd.set_option('display.width', 1000)
|
||||||
|
pd.set_option('display.max_colwidth', 1000)
|
||||||
|
|
||||||
|
return df.to_string()
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ from catalyst.finance.commission import (
|
|||||||
from catalyst.finance.cancel_policy import NeverCancel
|
from catalyst.finance.cancel_policy import NeverCancel
|
||||||
from catalyst.utils.input_validation import expect_types
|
from catalyst.utils.input_validation import expect_types
|
||||||
|
|
||||||
log = Logger('Blotter')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('Blotter', level=LOG_LEVEL)
|
||||||
warning_logger = Logger('AlgoWarning')
|
warning_logger = Logger('AlgoWarning')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ from catalyst.errors import (
|
|||||||
TradingControlViolation,
|
TradingControlViolation,
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logbook.Logger('TradingControl')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('TradingControl', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class TradingControl(with_metaclass(abc.ABCMeta)):
|
class TradingControl(with_metaclass(abc.ABCMeta)):
|
||||||
|
|||||||
@@ -88,7 +88,10 @@ from six import itervalues, iteritems
|
|||||||
|
|
||||||
import catalyst.protocol as zp
|
import catalyst.protocol as zp
|
||||||
|
|
||||||
log = logbook.Logger('Performance')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Performance', level=LOG_LEVEL)
|
||||||
|
|
||||||
TRADE_TYPE = zp.DATASOURCE_TYPE.TRADE
|
TRADE_TYPE = zp.DATASOURCE_TYPE.TRADE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ import logbook
|
|||||||
from catalyst.assets import Future, Asset
|
from catalyst.assets import Future, Asset
|
||||||
from catalyst.utils.input_validation import expect_types
|
from catalyst.utils.input_validation import expect_types
|
||||||
|
|
||||||
log = logbook.Logger('Performance')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Performance', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class Position(object):
|
class Position(object):
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ from catalyst.assets import (
|
|||||||
)
|
)
|
||||||
from . position import positiondict
|
from . position import positiondict
|
||||||
|
|
||||||
log = logbook.Logger('Performance')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Performance', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
PositionStats = namedtuple('PositionStats',
|
PositionStats = namedtuple('PositionStats',
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ import catalyst.finance.risk as risk
|
|||||||
|
|
||||||
from . position_tracker import PositionTracker
|
from . position_tracker import PositionTracker
|
||||||
|
|
||||||
log = logbook.Logger('Performance')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Performance', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class PerformanceTracker(object):
|
class PerformanceTracker(object):
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ from empyrical import (
|
|||||||
sortino_ratio,
|
sortino_ratio,
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logbook.Logger('Risk Cumulative')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Risk Cumulative', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
choose_treasury = functools.partial(choose_treasury, lambda *args: '10year',
|
choose_treasury = functools.partial(choose_treasury, lambda *args: '10year',
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ from empyrical import (
|
|||||||
sortino_ratio
|
sortino_ratio
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logbook.Logger('Risk Period')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Risk Period', level=LOG_LEVEL)
|
||||||
|
|
||||||
choose_treasury = functools.partial(risk.choose_treasury,
|
choose_treasury = functools.partial(risk.choose_treasury,
|
||||||
risk.select_treasury_duration)
|
risk.select_treasury_duration)
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ from dateutil.relativedelta import relativedelta
|
|||||||
|
|
||||||
from . period import RiskMetricsPeriod
|
from . period import RiskMetricsPeriod
|
||||||
|
|
||||||
log = logbook.Logger('Risk Report')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Risk Report', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class RiskReport(object):
|
class RiskReport(object):
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ Risk Report
|
|||||||
import logbook
|
import logbook
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
log = logbook.Logger('Risk')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Risk', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
TREASURY_DURATIONS = [
|
TREASURY_DURATIONS = [
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ from catalyst.data.loader import load_market_data
|
|||||||
from catalyst.utils.calendars import get_calendar
|
from catalyst.utils.calendars import get_calendar
|
||||||
from catalyst.utils.memoize import remember_last
|
from catalyst.utils.memoize import remember_last
|
||||||
|
|
||||||
log = logbook.Logger('Trading')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = logbook.Logger('Trading', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CAPITAL_BASE = 1e5
|
DEFAULT_CAPITAL_BASE = 1e5
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ from catalyst.gens.sim_engine import (
|
|||||||
BEFORE_TRADING_START_BAR
|
BEFORE_TRADING_START_BAR
|
||||||
)
|
)
|
||||||
|
|
||||||
log = Logger('Trade Simulation')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('Trade Simulation', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class AlgorithmSimulator(object):
|
class AlgorithmSimulator(object):
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ from catalyst.protocol import (
|
|||||||
)
|
)
|
||||||
from catalyst.assets import Equity
|
from catalyst.assets import Equity
|
||||||
|
|
||||||
logger = Logger('Requests Source Logger')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
logger = Logger('Requests Source Logger', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
def roll_dts_to_midnight(dts, trading_day):
|
def roll_dts_to_midnight(dts, trading_day):
|
||||||
|
|||||||
@@ -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,140 @@
|
|||||||
|
"""
|
||||||
|
Requires Catalyst version 0.3.0 or above
|
||||||
|
Tested on Catalyst version 0.3.2
|
||||||
|
|
||||||
|
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 = 'eth' # 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.
|
||||||
|
open = fill(data.history(coin, 'open', bar_count=lookback,
|
||||||
|
frequency='1m')).resample('30T').first()
|
||||||
|
high = fill(data.history(coin, 'high', bar_count=lookback,
|
||||||
|
frequency='1m')).resample('30T').max()
|
||||||
|
low = fill(data.history(coin, 'low', bar_count=lookback,
|
||||||
|
frequency='1m')).resample('30T').min()
|
||||||
|
close = fill(data.history(coin, 'price', bar_count=lookback,
|
||||||
|
frequency='1m')).resample('30T').last()
|
||||||
|
volume = fill(data.history(coin, 'volume', bar_count=lookback,
|
||||||
|
frequency='1m')).resample('30T').sum()
|
||||||
|
|
||||||
|
# close[-1] is the equivalent to current price
|
||||||
|
# displays the minute price for each pair every 30 minutes
|
||||||
|
print(
|
||||||
|
today, pair, open[-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
|
||||||
|
print(universe_df.head(), len(universe_df))
|
||||||
|
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-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='eth',
|
||||||
|
live=False,
|
||||||
|
live_graph=False,
|
||||||
|
algo_namespace='simple_universe')
|
||||||
|
|
||||||
|
"""
|
||||||
|
Run in Terminal (inside catalyst environment):
|
||||||
|
python simple_universe.py
|
||||||
|
"""
|
||||||
@@ -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'
|
||||||
|
)
|
||||||
@@ -126,7 +126,7 @@ def catalyst_root(environ=None):
|
|||||||
|
|
||||||
root = environ.get('ZIPLINE_ROOT', None)
|
root = environ.get('ZIPLINE_ROOT', None)
|
||||||
if root is None:
|
if root is None:
|
||||||
root = expanduser('~/.catalyst')
|
root = os.path.join(expanduser('~'),'.catalyst')
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
|||||||
@@ -31,19 +31,21 @@ import catalyst.utils.paths as pth
|
|||||||
|
|
||||||
from catalyst.exchange.exchange_algorithm import ExchangeTradingAlgorithmLive, \
|
from catalyst.exchange.exchange_algorithm import ExchangeTradingAlgorithmLive, \
|
||||||
ExchangeTradingAlgorithmBacktest
|
ExchangeTradingAlgorithmBacktest
|
||||||
from catalyst.exchange.data_portal_exchange 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_portfolio import ExchangePortfolio
|
||||||
from catalyst.exchange.exchange_errors import (
|
from catalyst.exchange.exchange_errors import (
|
||||||
ExchangeRequestError,
|
ExchangeRequestError, ExchangeAuthEmpty,
|
||||||
ExchangeRequestErrorTooManyAttempts,
|
ExchangeRequestErrorTooManyAttempts,
|
||||||
BaseCurrencyNotFoundError, ExchangeNotFoundError)
|
BaseCurrencyNotFoundError, ExchangeNotFoundError)
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth, \
|
from catalyst.exchange.exchange_utils import get_exchange_auth, \
|
||||||
get_algo_object
|
get_algo_object, get_exchange_folder
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('run_algo')
|
from catalyst.constants import LOG_LEVEL
|
||||||
|
|
||||||
|
log = Logger('run_algo', level=LOG_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
class _RunAlgoError(click.ClickException, ValueError):
|
class _RunAlgoError(click.ClickException, ValueError):
|
||||||
@@ -164,6 +166,12 @@ def _run(handle_data,
|
|||||||
|
|
||||||
# This corresponds to the json file containing api token info
|
# This corresponds to the json file containing api token info
|
||||||
exchange_auth = get_exchange_auth(exchange_name)
|
exchange_auth = get_exchange_auth(exchange_name)
|
||||||
|
|
||||||
|
if live and (exchange_auth['key'] == '' or exchange_auth['secret'] == ''):
|
||||||
|
raise ExchangeAuthEmpty(
|
||||||
|
exchange=exchange_name.title(),
|
||||||
|
filename=os.path.join(get_exchange_folder(exchange_name, environ), 'auth.json') )
|
||||||
|
|
||||||
if exchange_name == 'bitfinex':
|
if exchange_name == 'bitfinex':
|
||||||
exchanges[exchange_name] = Bitfinex(
|
exchanges[exchange_name] = Bitfinex(
|
||||||
key=exchange_auth['key'],
|
key=exchange_auth['key'],
|
||||||
@@ -235,8 +243,11 @@ def _run(handle_data,
|
|||||||
balances = exchange.get_balances()
|
balances = exchange.get_balances()
|
||||||
except ExchangeRequestError as e:
|
except ExchangeRequestError as e:
|
||||||
if attempt_index < 20:
|
if attempt_index < 20:
|
||||||
log.warn('exchange error when retrieving balances, {} '
|
log.warn(
|
||||||
'trying again in 5 seconds'.format(e))
|
'could not retrieve balances on {}: {}'.format(
|
||||||
|
exchange.name, e
|
||||||
|
)
|
||||||
|
)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
return fetch_capital_base(exchange, attempt_index + 1)
|
return fetch_capital_base(exchange, attempt_index + 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
<h1>Live Trading</h1>
|
|
||||||
This document explains how to get started with live trading.
|
|
||||||
|
|
||||||
<h2>Supported Exchanges</h2>
|
|
||||||
Catalyst can trade against these exchanges:
|
|
||||||
|
|
||||||
* Bitfinex, id=`bitfinex`
|
|
||||||
* Bittrex, id=`bittrex`
|
|
||||||
|
|
||||||
<h3>Authentication</h3>
|
|
||||||
Most exchanges require key/token combination for authentication. By
|
|
||||||
convention, Catalyst uses an "auth.json" file to hold this data.
|
|
||||||
|
|
||||||
This example illustrates the convention using the Bitfinex exchange.
|
|
||||||
Here is how to generate key and secret values for bitfinex:
|
|
||||||
https://docs.bitfinex.com/v1/docs/api-access. Most exchanges follow
|
|
||||||
a similar process.
|
|
||||||
|
|
||||||
The auth.json file:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "bitfinex",
|
|
||||||
"key": "my-key",
|
|
||||||
"secret": "my-secret"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The file goes here:
|
|
||||||
```
|
|
||||||
~/.catalyst/data/exchanges/bitfinex/auth.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the 'bitfinex' directory corresponds to the id of the Bitfinex
|
|
||||||
exchange as defined in the "Supported Exchanges" section above.
|
|
||||||
Attempting to run an algorithm where the targeted exchange is missing
|
|
||||||
its "auth.json" file will create the directory structure but result
|
|
||||||
in an error.
|
|
||||||
|
|
||||||
<h3>Currency Symbols</h3>
|
|
||||||
Catalyst introduces a universal convention to reference
|
|
||||||
trading pairs and individual currencies. This
|
|
||||||
is required to ensure that the `symbol()` api predictably
|
|
||||||
returns the correct asset regardless of the targeted exchange.
|
|
||||||
|
|
||||||
Exchanges tend to use their own convention to represent currencies
|
|
||||||
(e.g. XBT and BTC both represent Bitcoin on different exchanges).
|
|
||||||
Trading pairs are also inconsistent. For example, Bitfinex
|
|
||||||
puts the market currency before the base currency without a
|
|
||||||
separator, Bittrex puts the base currency first and uses a dash
|
|
||||||
seperator.
|
|
||||||
|
|
||||||
Here is the Catalyst convention:
|
|
||||||
|
|
||||||
*[Market Currency]_[Base Currency]* all lowercase.
|
|
||||||
|
|
||||||
Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention.
|
|
||||||
|
|
||||||
Here are some examples:
|
|
||||||
```python
|
|
||||||
# With Bitfinex
|
|
||||||
bitcoin_usd_asset = symbol('btc_usd')
|
|
||||||
ethereum_bitcoin_asset = symbol('eth_btc')
|
|
||||||
|
|
||||||
# With Bittrex
|
|
||||||
ethereum_bitcoin_asset = symbol('eth_btc')
|
|
||||||
neo_ethereum_asset = symbol('neo_eth)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the trading pairs are always referenced in the same manner.
|
|
||||||
However, not all trading pairs are available on all exchanges. An
|
|
||||||
error will occur if the specified trading pair is not trading
|
|
||||||
on the exchange.
|
|
||||||
|
|
||||||
<h2>Trading an Algorithm</h2>
|
|
||||||
There is no special convention to follow when writing an
|
|
||||||
algorithm for live trading. The same algorithm should work in
|
|
||||||
backtest and live execution mode without modification.
|
|
||||||
|
|
||||||
What differs are the arguments provided to the catalyst client or
|
|
||||||
`run_algorithm()` interface. Here is example:
|
|
||||||
|
|
||||||
```python
|
|
||||||
run_algorithm(
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
live=True,
|
|
||||||
algo_namespace='my_algo_trading_xrp',
|
|
||||||
base_currency='btc'
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is the breakdown of the new arguments:
|
|
||||||
* live: Boolean flag which enables live trading.
|
|
||||||
* exchange_name: The name of the targeted exchange
|
|
||||||
(supported values: *bitfinex*, *bittrex*).
|
|
||||||
* algo_namespace: A arbitrary label assigned to your algorithm for
|
|
||||||
data storage purposes.
|
|
||||||
* base_currency: The base currency used to calculate the
|
|
||||||
statistics of your algorithm. Currently, the base currency of all
|
|
||||||
trading pairs of your algorithm must match this value.
|
|
||||||
|
|
||||||
Here is a complete algorithm for reference:
|
|
||||||
[Buy Low and Sell High](../catalyst/examples/buy_low_sell_high_live.py)
|
|
||||||
@@ -52,7 +52,7 @@ My first algorithm
|
|||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Lets take a look at a very simple algorithm from the ``examples``
|
Lets take a look at a very simple algorithm from the ``examples``
|
||||||
directory, ``buy_btc.py``:
|
directory: `buy_btc_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_btc_simple.py>`_:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -225,16 +225,16 @@ Thus, to execute our algorithm from above and save the results to
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2016-9-29 -o buy_simple_btc_out.pickle
|
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
||||||
|
|
||||||
|
|
||||||
..
|
.. parsed-literal::
|
||||||
.. parsed-literal
|
|
||||||
|
|
||||||
.. AAPL
|
INFO: run_algo: running algo in backtest mode
|
||||||
.. [2015-11-04 22:45:32.820166] INFO: Performance: Simulated 3521 trading days out of 3521.
|
INFO: exchange_algorithm: initialized trading algorithm in backtest mode
|
||||||
.. [2015-11-04 22:45:32.820314] INFO: Performance: first open: 2000-01-03 14:31:00+00:00
|
INFO: Performance: Simulated 639 trading days out of 639.
|
||||||
.. [2015-11-04 22:45:32.820401] INFO: Performance: last close: 2013-12-31 21:00:00+00:00
|
INFO: Performance: first open: 2016-01-01 00:00:00+00:00
|
||||||
|
INFO: Performance: last close: 2017-09-30 23:59:00+00:00
|
||||||
|
|
||||||
|
|
||||||
``run`` first calls the ``initialize()`` function, and then
|
``run`` first calls the ``initialize()`` function, and then
|
||||||
@@ -265,17 +265,200 @@ outputting so it's worth spending some time to learn it.
|
|||||||
perf = pd.read_pickle('buy_btc_simple_out.pickle') # read in perf DataFrame
|
perf = pd.read_pickle('buy_btc_simple_out.pickle') # read in perf DataFrame
|
||||||
perf.head()
|
perf.head()
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<div style="max-height:1000px;max-width:1500px;overflow:auto;">
|
||||||
|
<table border="1" class="dataframe">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align: right;">
|
||||||
|
<th></th>
|
||||||
|
<th>algo_volatility</th>
|
||||||
|
<th>algorithm_period_return</th>
|
||||||
|
<th>alpha</th>
|
||||||
|
<th>benchmark_period_return</th>
|
||||||
|
<th>benchmark_volatility</th>
|
||||||
|
<th>beta</th>
|
||||||
|
<th>btc</th>
|
||||||
|
<th>capital_used</th>
|
||||||
|
<th>ending_cash</th>
|
||||||
|
<th>ending_exposure</th>
|
||||||
|
<th>...</th>
|
||||||
|
<th>short_exposure</th>
|
||||||
|
<th>short_value</th>
|
||||||
|
<th>shorts_count</th>
|
||||||
|
<th>sortino</th>
|
||||||
|
<th>starting_cash</th>
|
||||||
|
<th>starting_exposure</th>
|
||||||
|
<th>starting_value</th>
|
||||||
|
<th>trading_days</th>
|
||||||
|
<th>transactions</th>
|
||||||
|
<th>treasury_period_return</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>2016-01-01 23:59:00+00:00</th>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>0.000000e+00</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>-0.010937</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>433.979999</td>
|
||||||
|
<td>0.000000</td>
|
||||||
|
<td>1.000000e+07</td>
|
||||||
|
<td>0.00</td>
|
||||||
|
<td>...</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>1.000000e+07</td>
|
||||||
|
<td>0.00</td>
|
||||||
|
<td>0.00</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>[]</td>
|
||||||
|
<td>0.0227</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2016-01-02 23:59:00+00:00</th>
|
||||||
|
<td>0.000011</td>
|
||||||
|
<td>-9.536708e-07</td>
|
||||||
|
<td>-0.000170</td>
|
||||||
|
<td>-0.006480</td>
|
||||||
|
<td>0.173338</td>
|
||||||
|
<td>-0.000062</td>
|
||||||
|
<td>432.700000</td>
|
||||||
|
<td>-442.236708</td>
|
||||||
|
<td>9.999558e+06</td>
|
||||||
|
<td>432.70</td>
|
||||||
|
<td>...</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>-11.224972</td>
|
||||||
|
<td>1.000000e+07</td>
|
||||||
|
<td>0.00</td>
|
||||||
|
<td>0.00</td>
|
||||||
|
<td>2</td>
|
||||||
|
<td>[{u'order_id': u'7869f7828fa140328eb40477bb7de...</td>
|
||||||
|
<td>0.0227</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2016-01-03 23:59:00+00:00</th>
|
||||||
|
<td>0.000011</td>
|
||||||
|
<td>-2.328842e-06</td>
|
||||||
|
<td>-0.000176</td>
|
||||||
|
<td>-0.026512</td>
|
||||||
|
<td>0.197857</td>
|
||||||
|
<td>0.000009</td>
|
||||||
|
<td>428.390000</td>
|
||||||
|
<td>-437.831716</td>
|
||||||
|
<td>9.999120e+06</td>
|
||||||
|
<td>856.78</td>
|
||||||
|
<td>...</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>-12.754262</td>
|
||||||
|
<td>9.999558e+06</td>
|
||||||
|
<td>432.70</td>
|
||||||
|
<td>432.70</td>
|
||||||
|
<td>3</td>
|
||||||
|
<td>[{u'order_id': u'be62ff77760c4599abaac43be9cc9...</td>
|
||||||
|
<td>0.0227</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2016-01-04 23:59:00+00:00</th>
|
||||||
|
<td>0.000011</td>
|
||||||
|
<td>-2.380954e-06</td>
|
||||||
|
<td>-0.000139</td>
|
||||||
|
<td>-0.008640</td>
|
||||||
|
<td>0.269790</td>
|
||||||
|
<td>0.000020</td>
|
||||||
|
<td>432.900000</td>
|
||||||
|
<td>-442.441116</td>
|
||||||
|
<td>9.998677e+06</td>
|
||||||
|
<td>1298.70</td>
|
||||||
|
<td>...</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>-11.287205</td>
|
||||||
|
<td>9.999120e+06</td>
|
||||||
|
<td>856.78</td>
|
||||||
|
<td>856.78</td>
|
||||||
|
<td>4</td>
|
||||||
|
<td>[{u'order_id': u'd6dca79513214346a646079213526...</td>
|
||||||
|
<td>0.0224</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2016-01-05 23:59:00+00:00</th>
|
||||||
|
<td>0.000011</td>
|
||||||
|
<td>-3.650729e-06</td>
|
||||||
|
<td>-0.000158</td>
|
||||||
|
<td>-0.021426</td>
|
||||||
|
<td>0.245989</td>
|
||||||
|
<td>0.000024</td>
|
||||||
|
<td>431.840000</td>
|
||||||
|
<td>-441.357754</td>
|
||||||
|
<td>9.998236e+06</td>
|
||||||
|
<td>1727.36</td>
|
||||||
|
<td>...</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>0</td>
|
||||||
|
<td>-12.333847</td>
|
||||||
|
<td>9.998677e+06</td>
|
||||||
|
<td>1298.70</td>
|
||||||
|
<td>1298.70</td>
|
||||||
|
<td>5</td>
|
||||||
|
<td>[{u'order_id': u'505275d6646a41f3856b22b16678d...</td>
|
||||||
|
<td>0.0225</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
There is a row for each trading day, starting on the first day of our
|
There is a row for each trading day, starting on the first day of our
|
||||||
simulation Jan 1st, 2016. In the columns you can find various
|
simulation Jan 1st, 2016. In the columns you can find various
|
||||||
information about the state of your algorithm. The very first column
|
information about the state of your algorithm. The column
|
||||||
``btc`` was placed there by the ``record()`` function mentioned earlier
|
``btc`` was placed there by the ``record()`` function mentioned earlier
|
||||||
and allows us to plot the price of bitcoin. For example, we could easily
|
and allows us to plot the price of bitcoin. For example, we could easily
|
||||||
examine now how our portfolio value changed over time compared to the
|
examine now how our portfolio value changed over time compared to the
|
||||||
bitcoin price.
|
bitcoin price.
|
||||||
|
|
||||||
Our algorithm performance as assessed by the
|
.. code-block:: python
|
||||||
``portfolio_value`` closely matches that of the bitcoin price. This
|
|
||||||
is not surprising as our algorithm only bought bitcoin every chance it got.
|
%load_ext catalyst
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
%pylab inline
|
||||||
|
figsize(12, 12)
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
ax1 = plt.subplot(211)
|
||||||
|
perf.portfolio_value.plot(ax=ax1)
|
||||||
|
ax1.set_ylabel('portfolio value')
|
||||||
|
ax2 = plt.subplot(212, sharex=ax1)
|
||||||
|
perf.btc.plot(ax=ax2)
|
||||||
|
ax2.set_ylabel('bitcoin price')
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
Populating the interactive namespace from numpy and matplotlib
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
<matplotlib.text.Text at 0x10eaeadd0>
|
||||||
|
|
||||||
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/buy_btc_simple_graph.png
|
||||||
|
|
||||||
|
Our algorithm performance as assessed by the ``portfolio_value`` closely
|
||||||
|
matches that of the bitcoin price. This is not surprising as our algorithm
|
||||||
|
only bought bitcoin every chance it got.
|
||||||
|
|
||||||
|
|
||||||
Access to previous prices using ``history``
|
Access to previous prices using ``history``
|
||||||
@@ -305,23 +488,29 @@ a function we use in the ``handle_data()`` section:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from catalyst.api import order, record, symbol
|
%load_ext catalyst
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
%%catalyst --start 2016-4-1 --end 2017-9-30 -x bitfinex
|
||||||
|
|
||||||
|
from catalyst.api import order, record, symbol, order_target
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.i = 0
|
context.i = 0
|
||||||
context.asset = symbol('btc_usd')
|
context.asset = symbol('btc_usd')
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
# Skip first 300 days to get full windows
|
# Skip first 150 days to get full windows
|
||||||
context.i += 1
|
context.i += 1
|
||||||
if context.i < 300:
|
if context.i < 150:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Compute averages
|
# Compute averages
|
||||||
# data.history() has to be called with the same params
|
# data.history() has to be called with the same params
|
||||||
# from above and returns a pandas dataframe.
|
# from above and returns a pandas dataframe.
|
||||||
short_mavg = data.history(context.asset, 'price', bar_count=100, frequency="1d").mean()
|
short_mavg = data.history(context.asset, 'price', bar_count=50, frequency="1d").mean()
|
||||||
long_mavg = data.history(context.asset, 'price', bar_count=300, frequency="1d").mean()
|
long_mavg = data.history(context.asset, 'price', bar_count=150, frequency="1d").mean()
|
||||||
|
|
||||||
# Trading logic
|
# Trading logic
|
||||||
if short_mavg > long_mavg:
|
if short_mavg > long_mavg:
|
||||||
@@ -336,6 +525,46 @@ a function we use in the ``handle_data()`` section:
|
|||||||
short_mavg=short_mavg,
|
short_mavg=short_mavg,
|
||||||
long_mavg=long_mavg)
|
long_mavg=long_mavg)
|
||||||
|
|
||||||
|
def analyze(context, perf):
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
fig = plt.figure(figsize=(12,12))
|
||||||
|
ax1 = fig.add_subplot(211)
|
||||||
|
perf.portfolio_value.plot(ax=ax1)
|
||||||
|
ax1.set_ylabel('portfolio value in $')
|
||||||
|
|
||||||
|
ax2 = fig.add_subplot(212)
|
||||||
|
perf['btc'].plot(ax=ax2)
|
||||||
|
perf[['short_mavg', 'long_mavg']].plot(ax=ax2)
|
||||||
|
|
||||||
|
perf_trans = perf.ix[[t != [] for t in perf.transactions]]
|
||||||
|
buys = perf_trans.ix[[t[0]['amount'] > 0 for t in perf_trans.transactions]]
|
||||||
|
sells = perf_trans.ix[
|
||||||
|
[t[0]['amount'] < 0 for t in perf_trans.transactions]]
|
||||||
|
ax2.plot(buys.index, perf.short_mavg.ix[buys.index],
|
||||||
|
'^', markersize=10, color='m')
|
||||||
|
ax2.plot(sells.index, perf.short_mavg.ix[sells.index],
|
||||||
|
'v', markersize=10, color='k')
|
||||||
|
ax2.set_ylabel('price in $')
|
||||||
|
plt.legend(loc=0)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
Here we are explicitly defining an ``analyze()`` function that gets
|
||||||
|
automatically called once the backtest is done.
|
||||||
|
|
||||||
|
Although it might not be directly apparent, the power of ``history()``
|
||||||
|
(pun intended) can not be under-estimated as most algorithms make use of
|
||||||
|
prior market developments in one form or another. You could easily
|
||||||
|
devise a strategy that trains a classifier with
|
||||||
|
`scikit-learn <http://scikit-learn.org/stable/>`__ which tries to
|
||||||
|
predict future market movements based on past prices (note, that most of
|
||||||
|
the ``scikit-learn`` functions require ``numpy.ndarray``\ s rather than
|
||||||
|
``pandas.DataFrame``\ s, so you can simply pass the underlying
|
||||||
|
``ndarray`` of a ``DataFrame`` via ``.values``).
|
||||||
|
|
||||||
|
We also used the ``order_target()`` function above. This and other
|
||||||
|
functions like it can make order management and portfolio rebalancing
|
||||||
|
much easier.
|
||||||
|
|
||||||
|
|
||||||
Conclusions
|
Conclusions
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
Development Guidelines
|
Development Guidelines
|
||||||
======================
|
======================
|
||||||
This page is intended for developers of Zipline, people who want to contribute to the Zipline codebase or documentation, or people who want to install from source and make local changes to their copy of Zipline.
|
This page is intended for developers of Catalyst, people who want to contribute to the Catalyst codebase or documentation, or people who want to install from source and make local changes to their copy of Catalyst.
|
||||||
|
|
||||||
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We `track issues`__ on `GitHub`__ and also have a `mailing list`__ where you can ask questions.
|
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We `track issues <https://github.com/enigmampc/catalyst/issues>`_ on `GitHub <https://github.com/enigmampc/catalyst>`_ and also have a `discord group <https://discord.gg/SJK32GY>`_ where you can ask questions.
|
||||||
|
|
||||||
__ https://github.com/quantopian/zipline/issues
|
|
||||||
__ https://github.com/
|
|
||||||
__ https://groups.google.com/forum/#!forum/zipline
|
|
||||||
|
|
||||||
Creating a Development Environment
|
Creating a Development Environment
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
First, you'll need to clone Zipline by running:
|
First, you'll need to clone Catalyst by running:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ git clone git@github.com:your-github-username/zipline.git
|
$ git clone git@github.com:enigmampc/catalyst.git
|
||||||
|
|
||||||
Then check out to a new branch where you can make your changes:
|
Then check out to a new branch where you can make your changes:
|
||||||
|
|
||||||
@@ -23,15 +19,13 @@ Then check out to a new branch where you can make your changes:
|
|||||||
|
|
||||||
$ git checkout -b some-short-descriptive-name
|
$ git checkout -b some-short-descriptive-name
|
||||||
|
|
||||||
If you don't already have them, you'll need some C library dependencies. You can follow the `install guide`__ to get the appropriate dependencies.
|
If you don't already have them, you'll need some C library dependencies. You can follow the `install guide <install.html>`_ to get the appropriate dependencies.
|
||||||
|
|
||||||
__ install.html
|
|
||||||
|
|
||||||
The following section assumes you already have virtualenvwrapper and pip installed on your system. Suggested installation of Python library dependencies used for development:
|
The following section assumes you already have virtualenvwrapper and pip installed on your system. Suggested installation of Python library dependencies used for development:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ mkvirtualenv zipline
|
$ mkvirtualenv catalyst
|
||||||
$ ./etc/ordered_pip.sh ./etc/requirements.txt
|
$ ./etc/ordered_pip.sh ./etc/requirements.txt
|
||||||
$ pip install -r ./etc/requirements_dev.txt
|
$ pip install -r ./etc/requirements_dev.txt
|
||||||
$ pip install -r ./etc/requirements_blaze.txt
|
$ pip install -r ./etc/requirements_blaze.txt
|
||||||
@@ -42,104 +36,39 @@ Finally, you can build the C extensions by running:
|
|||||||
|
|
||||||
$ python setup.py build_ext --inplace
|
$ python setup.py build_ext --inplace
|
||||||
|
|
||||||
To finish, make sure `tests`__ pass.
|
.. To finish, make sure `tests`__ pass.
|
||||||
|
|
||||||
__ #style-guide-running-tests
|
.. __ #style-guide-running-tests
|
||||||
|
|
||||||
If you get an error running nosetests after setting up a fresh virtualenv, please try running
|
.. If you get an error running nosetests after setting up a fresh virtualenv, please try running
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block
|
||||||
|
|
||||||
# where zipline is the name of your virtualenv
|
.. # where zipline is the name of your virtualenv
|
||||||
$ deactivate zipline
|
.. $ deactivate zipline
|
||||||
$ workon zipline
|
.. $ workon zipline
|
||||||
|
|
||||||
|
|
||||||
Development with Docker
|
.. Development with Docker
|
||||||
|
.. -----------------------
|
||||||
|
|
||||||
|
..If you want to work with zipline using a `Docker`__ container, you'll need to build the ``Dockerfile`` in the Zipline root directory, and then build ``Dockerfile-dev``. Instructions for building both containers can be found in ``Dockerfile`` and ``Dockerfile-dev``, respectively.
|
||||||
|
|
||||||
|
.. __ https://docs.docker.com/get-started/
|
||||||
|
|
||||||
|
Git Branching Structure
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
If you want to work with zipline using a `Docker`__ container, you'll need to build the ``Dockerfile`` in the Zipline root directory, and then build ``Dockerfile-dev``. Instructions for building both containers can be found in ``Dockerfile`` and ``Dockerfile-dev``, respectively.
|
If you want to contribute to the codebase of Catalyst, familiarize yourself with our branching structure, a fairly standardized one for that matter, that follows what is documented in the following article: `A successful Git branching model <http://nvie.com/posts/a-successful-git-branching-model/>`_. To contribute, create your local branch and submit a Pull Request (PR) to the **develop** branch.
|
||||||
|
|
||||||
__ https://docs.docker.com/get-started/
|
.. image:: https://camo.githubusercontent.com/9bde6fb64a9542a572e0e2017cbb58d9d2c440ac/687474703a2f2f6e7669652e636f6d2f696d672f6769742d6d6f64656c4032782e706e67
|
||||||
|
|
||||||
|
|
||||||
Style Guide & Running Tests
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
We use `flake8`__ for checking style requirements and `nosetests`__ to run Zipline tests. Our `continuous integration`__ tools will run these commands.
|
|
||||||
|
|
||||||
__ http://flake8.pycqa.org/en/latest/
|
|
||||||
__ http://nose.readthedocs.io/en/latest/
|
|
||||||
__ https://en.wikipedia.org/wiki/Continuous_integration
|
|
||||||
|
|
||||||
Before submitting patches or pull requests, please ensure that your changes pass when running:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ flake8 zipline tests
|
|
||||||
|
|
||||||
In order to run tests locally, you'll need `TA-lib`__, which you can install on Linux by running:
|
|
||||||
|
|
||||||
__ https://mrjbq7.github.io/ta-lib/install.html
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
|
|
||||||
$ tar -xvzf ta-lib-0.4.0-src.tar.gz
|
|
||||||
$ cd ta-lib/
|
|
||||||
$ ./configure --prefix=/usr
|
|
||||||
$ make
|
|
||||||
$ sudo make install
|
|
||||||
|
|
||||||
And for ``TA-lib`` on OS X you can just run:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ brew install ta-lib
|
|
||||||
|
|
||||||
Then run ``pip install`` TA-lib:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ pip install -r ./etc/requirements_talib.txt
|
|
||||||
|
|
||||||
You should now be free to run tests:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ nosetests
|
|
||||||
|
|
||||||
|
|
||||||
Continuous Integration
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
We use `Travis CI`__ for Linux-64 bit builds and `AppVeyor`__ for Windows-64 bit builds.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
We do not currently have CI for OSX-64 bit builds. 32-bit builds may work but are not included in our integration tests.
|
|
||||||
|
|
||||||
__ https://travis-ci.org/quantopian/zipline
|
|
||||||
__ https://ci.appveyor.com/project/quantopian/zipline
|
|
||||||
|
|
||||||
|
|
||||||
Packaging
|
|
||||||
---------
|
|
||||||
To learn about how we build Zipline conda packages, you can read `this`__ section in our release process notes.
|
|
||||||
|
|
||||||
__ release-process.html#uploading-conda-packages
|
|
||||||
|
|
||||||
Contributing to the Docs
|
Contributing to the Docs
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
If you'd like to contribute to the documentation on zipline.io, you can navigate to ``docs/source/`` where each `reStructuredText`__ (``.rst``) file is a separate section there. To add a section, create a new file called ``some-descriptive-name.rst`` and add ``some-descriptive-name`` to ``appendix.rst``. To edit a section, simply open up one of the existing files, make your changes, and save them.
|
If you'd like to contribute to the documentation on enigmampc.github.io, you can navigate to ``docs/source/`` where each `reStructuredText <https://en.wikipedia.org/wiki/ReStructuredText>`_ file is a separate section there. To add a section, create a new file called ``some-descriptive-name.rst`` and add ``some-descriptive-name`` to ``index.rst``. To edit a section, simply open up one of the existing files, make your changes, and save them.
|
||||||
|
|
||||||
__ https://en.wikipedia.org/wiki/ReStructuredText
|
|
||||||
|
|
||||||
We use `Sphinx`__ to generate documentation for Zipline, which you will need to install by running:
|
|
||||||
|
|
||||||
__ http://www.sphinx-doc.org/en/stable/
|
|
||||||
|
|
||||||
|
We use `Sphinx <http://www.sphinx-doc.org/en/stable/>`_ to generate documentation for Catalyst, which you will need to install by running:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -149,7 +78,7 @@ To build and view the docs locally, run:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
# assuming you're in the Zipline root directory
|
# assuming you're in the Catalyst root directory
|
||||||
$ cd docs
|
$ cd docs
|
||||||
$ make html
|
$ make html
|
||||||
$ {BROWSER} build/html/index.html
|
$ {BROWSER} build/html/index.html
|
||||||
@@ -162,7 +91,7 @@ Standard prefixes to start a commit message:
|
|||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
BLD: change related to building Zipline
|
BLD: change related to building Catalyst
|
||||||
BUG: bug fix
|
BUG: bug fix
|
||||||
DEP: deprecate something, or remove a deprecated object
|
DEP: deprecate something, or remove a deprecated object
|
||||||
DEV: development tool or utility
|
DEV: development tool or utility
|
||||||
@@ -172,15 +101,13 @@ Standard prefixes to start a commit message:
|
|||||||
REV: revert an earlier commit
|
REV: revert an earlier commit
|
||||||
STY: style fix (whitespace, PEP8, flake8, etc)
|
STY: style fix (whitespace, PEP8, flake8, etc)
|
||||||
TST: addition or modification of tests
|
TST: addition or modification of tests
|
||||||
REL: related to releasing Zipline
|
REL: related to releasing Catalyst
|
||||||
PERF: performance enhancements
|
PERF: performance enhancements
|
||||||
|
|
||||||
|
|
||||||
Some commit style guidelines:
|
Some commit style guidelines:
|
||||||
|
|
||||||
Commit lines should be no longer than `72 characters`__. The first line of the commit should include one of the above prefixes. There should be an empty line between the commit subject and the body of the commit. In general, the message should be in the imperative tense. Best practice is to include not only what the change is, but why the change was made.
|
Commit lines should be no longer than `72 characters <https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project>`_. The first line of the commit should include one of the above prefixes. There should be an empty line between the commit subject and the body of the commit. In general, the message should be in the imperative tense. Best practice is to include not only what the change is, but why the change was made.
|
||||||
|
|
||||||
__ https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project
|
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
@@ -203,8 +130,6 @@ __ https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project
|
|||||||
Formatting Docstrings
|
Formatting Docstrings
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
When adding or editing docstrings for classes, functions, etc, we use `numpy`__ as the canonical reference.
|
When adding or editing docstrings for classes, functions, etc, we use `numpy <https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt>`_ as the canonical reference.
|
||||||
|
|
||||||
__ https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,15 @@ Table of Contents
|
|||||||
|
|
||||||
install
|
install
|
||||||
beginner-tutorial
|
beginner-tutorial
|
||||||
|
jupyter
|
||||||
|
live-trading
|
||||||
naming-convention
|
naming-convention
|
||||||
|
videos
|
||||||
|
resources
|
||||||
|
development-guidelines
|
||||||
|
releases
|
||||||
.. bundles
|
.. bundles
|
||||||
.. development-guidelines
|
.. development-guidelines
|
||||||
.. appendix
|
.. appendix
|
||||||
.. release-process
|
.. release-process
|
||||||
.. releases
|
|
||||||
|
|||||||
+136
-70
@@ -1,6 +1,13 @@
|
|||||||
Install
|
Install
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
To get started with Catalyst, you will need to install it in your computer.
|
||||||
|
Like any other piece of software, Catalyst has a number of dependencies
|
||||||
|
(other software on which it depends to run) that you will need to install, as
|
||||||
|
well. We recommend using a software named ``Conda`` that will manage all
|
||||||
|
these dependencies for you, and set up the environment needed to get you up
|
||||||
|
and running as easily as possible. See :ref:`Installing with Conda <conda>`.
|
||||||
|
|
||||||
Installing with ``pip``
|
Installing with ``pip``
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
@@ -9,19 +16,20 @@ Python package.
|
|||||||
|
|
||||||
There are two reasons for the additional complexity:
|
There are two reasons for the additional complexity:
|
||||||
|
|
||||||
1. Catalyst ships several C extensions that require access to the CPython C API.
|
1. Catalyst ships several C extensions that require access to the CPython C
|
||||||
In order to build the C extensions, ``pip`` needs access to the CPython
|
API. In order to build the C extensions, ``pip`` needs access to the
|
||||||
header files for your Python installation.
|
CPython header files for your Python installation.
|
||||||
|
|
||||||
2. Catalyst depends on `numpy <http://www.numpy.org/>`_, the core library for
|
2. Catalyst depends on `numpy <http://www.numpy.org/>`_, the core library for
|
||||||
numerical array computing in Python. Numpy depends on having the `LAPACK
|
numerical array computing in Python. Numpy depends on having the `LAPACK
|
||||||
<http://www.netlib.org/lapack>`_ linear algebra routines available.
|
<http://www.netlib.org/lapack>`_ linear algebra routines available.
|
||||||
|
|
||||||
Because LAPACK and the CPython headers are non-Python dependencies, the correct
|
Because LAPACK and the CPython headers are non-Python dependencies, the
|
||||||
way to install them varies from platform to platform. If you'd rather use a
|
correctway to install them varies from platform to platform. If you'd rather
|
||||||
single tool to install Python and non-Python dependencies, or if you're already
|
use a single tool to install Python and non-Python dependencies, or if you're
|
||||||
using `Anaconda <http://continuum.io/downloads>`_ as your Python distribution,
|
already using `Anaconda <http://continuum.io/downloads>`_ as your Python
|
||||||
you can skip to the :ref:`Installing with Conda <conda>` section.
|
distribution, you can skip to the :ref:`Installing with Conda <conda>`
|
||||||
|
section.
|
||||||
|
|
||||||
Once you've installed the necessary additional dependencies (see below for
|
Once you've installed the necessary additional dependencies (see below for
|
||||||
your particular platform), you should be able to simply run
|
your particular platform), you should be able to simply run
|
||||||
@@ -34,18 +42,20 @@ If you use Python for anything other than Catalyst, we **strongly** recommend
|
|||||||
that you install in a `virtualenv
|
that you install in a `virtualenv
|
||||||
<https://virtualenv.readthedocs.org/en/latest>`_. The `Hitchhiker's Guide to
|
<https://virtualenv.readthedocs.org/en/latest>`_. The `Hitchhiker's Guide to
|
||||||
Python`_ provides an `excellent tutorial on virtualenv
|
Python`_ provides an `excellent tutorial on virtualenv
|
||||||
<http://docs.python-guide.org/en/latest/dev/virtualenvs/>`_. Here's a summarized
|
<http://docs.python-guide.org/en/latest/dev/virtualenvs/>`_. Here's a
|
||||||
version:
|
summarized version:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip install virtualenv
|
||||||
$ virtualenv catalyst-venv
|
$ virtualenv catalyst-venv
|
||||||
$ source ./catalyst-venv/bin/activate
|
$ source ./catalyst-venv/bin/activate
|
||||||
$ pip install enigma-
|
$ pip install enigma-catalyst
|
||||||
|
|
||||||
Though not required by Catalyst directly, our example algorithms use matplotlib
|
Though not required by Catalyst directly, our example algorithms use
|
||||||
to visually display the results of the trading algorithms. If you wish to run
|
matplotlib to visually display the results of the trading algorithms. If you
|
||||||
any examples or use matplotlib during development, it can be installed using:
|
wish to run any examples or use matplotlib during development, it can be
|
||||||
|
installed using:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -90,12 +100,12 @@ On `Arch Linux`_, you can acquire the additional dependencies via ``pacman``:
|
|||||||
OSX
|
OSX
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
The version of Python shipped with OSX by default is generally out of date, and
|
The version of Python shipped with OSX by default is generally out of date,
|
||||||
has a number of quirks because it's used directly by the operating system. For
|
and has a number of quirks because it's used directly by the operating system.
|
||||||
these reasons, many developers choose to install and use a separate Python
|
For these reasons, many developers choose to install and use a separate Python
|
||||||
installation. The `Hitchhiker's Guide to Python`_ provides an excellent guide
|
installation. The `Hitchhiker's Guide to Python`_ provides an excellent guide
|
||||||
to `Installing Python on OSX <http://docs.python-guide.org/en/latest/>`_, which
|
to `Installing Python on OSX <http://docs.python-guide.org/en/latest/>`_,
|
||||||
explains how to install Python with the `Homebrew`_ manager.
|
which explains how to install Python with the `Homebrew`_ manager.
|
||||||
|
|
||||||
Assuming you've installed Python with Homebrew, you'll also likely need the
|
Assuming you've installed Python with Homebrew, you'll also likely need the
|
||||||
following brew packages:
|
following brew packages:
|
||||||
@@ -107,49 +117,85 @@ following brew packages:
|
|||||||
OSX + virtualenv + matplotlib
|
OSX + virtualenv + matplotlib
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A note about using matplotlib in virtual enviroments on OSX: it may be necessary to run
|
A note about using matplotlib in virtual enviroments on OSX: it may be
|
||||||
|
necessary to run
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc
|
echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc
|
||||||
|
|
||||||
in order to override the default ``macosx`` backend for your system, which may not
|
in order to override the default ``macosx`` backend for your system, which
|
||||||
be accessible from inside the virtual environment. This will allow Catalyst to open
|
may not be accessible from inside the virtual environment. This will allow
|
||||||
matplotlib charts from within a virtual environment, which is useful for displaying
|
Catalyst to open matplotlib charts from within a virtual environment, which
|
||||||
the performance of your backtests. To learn more about matplotlib backends, please refer to the
|
is useful for displaying the performance of your backtests. To learn more
|
||||||
|
about matplotlib backends, please refer to the
|
||||||
`matplotlib backend documentation <https://matplotlib.org/faq/usage_faq.html#what-is-a-backend>`_.
|
`matplotlib backend documentation <https://matplotlib.org/faq/usage_faq.html#what-is-a-backend>`_.
|
||||||
|
|
||||||
|
.. _windows:
|
||||||
|
|
||||||
Windows
|
Windows
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
In Windows, you will need the `Microsoft Visual C++ Compiler for Python 2.7
|
In Windows, you will need the `Microsoft Visual C++ Compiler for Python 2.7
|
||||||
<https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_. This package
|
<https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_. This
|
||||||
contains the compiler and the set of system headers necessary for producing
|
package contains the compiler and the set of system headers necessary for
|
||||||
binary wheels for Python 2.7 packages. If it's not already in your system, download
|
producing binary wheels for Python 2.7 packages. If it's not already in your
|
||||||
it and install it before proceeding to the next step.
|
system, download it and install it before proceeding to the next step.
|
||||||
|
|
||||||
For windows, the easiest and best supported way to install Catalyst is to use
|
For windows, the easiest and best supported way to install Catalyst is to use
|
||||||
:ref:`Conda <conda>`.
|
:ref:`Conda <conda>`.
|
||||||
|
|
||||||
|
Some problems we have encountered installing the **Visual C++ Compiler**
|
||||||
|
mentioned above are as follows:
|
||||||
|
|
||||||
|
- **The system administrator has set policies to prevent this installation**.
|
||||||
|
|
||||||
|
In some systems, there is a default *Windows Software Restriction* policy
|
||||||
|
that prevents the installation of some software packages like this one.
|
||||||
|
You'll have to change the Registry to circumvent this:
|
||||||
|
|
||||||
|
- Click ``Start``, and search for ``regedit`` and launch the
|
||||||
|
``Registry Editor``
|
||||||
|
- Navigate to the following folder:
|
||||||
|
``HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer``
|
||||||
|
- If there is an entry for ``DisableMSI``, set the Value data to 0.
|
||||||
|
- If there is no such entry, click on the ``Edit`` menu -> ``New`` ->
|
||||||
|
``DWORD (32-bit) Value`` and enter ``DisableMSI`` as the Name (and by
|
||||||
|
default you get 0 as the Value Data)
|
||||||
|
|
||||||
|
|
|
||||||
|
- **The installer has encountered an unexpected error installing this package.
|
||||||
|
This may indicate a problem with this package. The error code is 2503.**
|
||||||
|
|
||||||
|
We have observed this when trying to install a package without enough
|
||||||
|
administrator permissions. Even when you are logged in as an Administrator,
|
||||||
|
you have to explictily install this package with administrator privileges:
|
||||||
|
|
||||||
|
- Click ``Start`` and find ``CMD`` or ``Command Prompt``
|
||||||
|
- Right click on it and choose ``Run as administrator``
|
||||||
|
- ``cd`` into the folder where you downloaded ``VCForPython27.msi``
|
||||||
|
- Run ``msiexec /i VCForPython27.msi``
|
||||||
|
|
||||||
|
|
||||||
Amazon Linux AMI
|
Amazon Linux AMI
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The packages ``pip`` and ``setuptools`` that come shipped by default are very outdated.
|
The packages ``pip`` and ``setuptools`` that come shipped by default are very
|
||||||
Thus, you first need to run:
|
outdated. Thus, you first need to run:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pip install --upgrade pip setuptools
|
pip install --upgrade pip setuptools
|
||||||
|
|
||||||
The default installation is also missing the C and C++ compilers, which you install by:
|
The default installation is also missing the C and C++ compilers, which you
|
||||||
|
install by:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
sudo yum install gcc gcc-c++
|
sudo yum install gcc gcc-c++
|
||||||
|
|
||||||
Then you should follow the regular installation instructions outlined at the beginning
|
Then you should follow the regular installation instructions outlined at the
|
||||||
of this page.
|
beginning of this page.
|
||||||
|
|
||||||
|
|
||||||
Troubleshooting ``pip`` Install
|
Troubleshooting ``pip`` Install
|
||||||
@@ -174,17 +220,24 @@ Troubleshooting ``pip`` Install
|
|||||||
----
|
----
|
||||||
|
|
||||||
**Issue**:
|
**Issue**:
|
||||||
Package enigma-catalyst cannot still be found, even after upgrading pip (see above), with an error similar to:
|
Package enigma-catalyst cannot still be found, even after upgrading pip
|
||||||
|
(see above), with an error similar to:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
Downloading/unpacking enigma-catalyst
|
Downloading/unpacking enigma-catalyst
|
||||||
Could not find a version that satisfies the requirement enigma-catalyst (from versions: 0.1.dev9, 0.2.dev2, 0.1.dev4, 0.1.dev5, 0.1.dev3, 0.2.dev1, 0.1.dev8, 0.1.dev6)
|
Could not find a version that satisfies the requirement enigma-catalyst
|
||||||
|
(from versions: 0.1.dev9, 0.2.dev2, 0.1.dev4, 0.1.dev5, 0.1.dev3,
|
||||||
|
0.2.dev1, 0.1.dev8, 0.1.dev6)
|
||||||
Cleaning up...
|
Cleaning up...
|
||||||
No distributions matching the version for enigma-catalyst
|
No distributions matching the version for enigma-catalyst
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
In some systems (this error has been reported in Ubuntu), pip is configured to only find stable versions by default. Since Catalyst is in alpha version, pip cannot find a matching version that satisfies the installation requirements. The solution is to include the `--pre` flag to include pre-release and development versions:
|
In some systems (this error has been reported in Ubuntu), pip is configured
|
||||||
|
to only find stable versions by default. Since Catalyst is in alpha
|
||||||
|
version, pip cannot find a matching version that satisfies the installation
|
||||||
|
requirements. The solution is to include the `--pre` flag to include
|
||||||
|
pre-release and development versions:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -220,10 +273,14 @@ Troubleshooting ``pip`` Install
|
|||||||
----
|
----
|
||||||
|
|
||||||
**Issue**:
|
**Issue**:
|
||||||
Installation fails with error: ``fatal error: Python.h: No such file or directory``
|
Installation fails with error:
|
||||||
|
``fatal error: Python.h: No such file or directory``
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
Some systems (this issue has been reported in Ubuntu) require `python-dev` for the proper build and installation of package dependencies. The solution is to install python-dev, which is independent of the virtual environment. In Ubuntu, you would need to run:
|
Some systems (this issue has been reported in Ubuntu) require `python-dev`
|
||||||
|
for the proper build and installation of package dependencies. The solution
|
||||||
|
is to install python-dev, which is independent of the virtual environment.
|
||||||
|
In Ubuntu, you would need to run:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -241,36 +298,41 @@ comes as part of Continuum Analytics' `Anaconda
|
|||||||
|
|
||||||
The primary advantage of using Conda over ``pip`` is that conda natively
|
The primary advantage of using Conda over ``pip`` is that conda natively
|
||||||
understands the complex binary dependencies of packages like ``numpy`` and
|
understands the complex binary dependencies of packages like ``numpy`` and
|
||||||
``scipy``. This means that ``conda`` can install Catalyst and its dependencies
|
``scipy``. This means that ``conda`` can install Catalyst and its
|
||||||
without requiring the use of a second tool to acquire Catalyst's non-Python
|
dependencies without requiring the use of a second tool to acquire Catalyst's
|
||||||
dependencies.
|
non-Python dependencies.
|
||||||
|
|
||||||
|
For Windows, you will need the *Microsoft Visual C++ Compiler for Python
|
||||||
|
2.7*. Follow the instructions on the :ref:`Windows` section and come back
|
||||||
|
here.
|
||||||
|
|
||||||
For instructions on how to install ``conda``, see the `Conda Installation
|
For instructions on how to install ``conda``, see the `Conda Installation
|
||||||
Documentation <http://conda.pydata.org/docs/download.html>`_. Alternatively, you
|
Documentation <http://conda.pydata.org/docs/download.html>`_. Alternatively,
|
||||||
can install MiniConda, which is a smaller footprint (fewer packages and smaller
|
you can install MiniConda, which is a smaller footprint (fewer packages and
|
||||||
size) than its big brother Anaconda, but it still contains all the main packages
|
smaller size) than its big brother Anaconda, but it still contains all the
|
||||||
needed. To install MiniConda, you can follow these steps:
|
main packages needed. To install MiniConda, you can follow these steps:
|
||||||
|
|
||||||
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select Python 2.7 for
|
1. Download `MiniConda <https://conda.io/miniconda.html>`_. Select Python 2.7
|
||||||
your Operating System.
|
for your Operating System.
|
||||||
2. Install MiniConda. See the `Installation Instructions <https://conda.io/docs/user-guide/install/index.html>`_
|
2. Install MiniConda. See the `Installation Instructions
|
||||||
if you need help.
|
<https://conda.io/docs/user-guide/install/index.html>`_ if you need help.
|
||||||
3. Ensure the correct installation by running ``conda list`` in a Terminal window,
|
3. Ensure the correct installation by running ``conda list`` in a Terminal
|
||||||
which should print the list of packages installed with Conda.
|
window, which should print the list of packages installed with Conda.
|
||||||
|
|
||||||
Once either Conda or MiniConda has been set up you can install Catalyst:
|
Once either Conda or MiniConda has been set up you can install Catalyst:
|
||||||
|
|
||||||
1. Download the file `python2.7-environment.yml <https://github.com/enigmampc/catalyst/blob/master/etc/python2.7-environment.yml>`_.
|
1. Download the file `python2.7-environment.yml
|
||||||
2. Open a Terminal window and enter [``cd/dir``] into the directory where you saved
|
<https://github.com/enigmampc/catalyst/blob/master/etc/python2.7-environment.yml>`_.
|
||||||
the above ``python2.7-environment.yml`` file.
|
2. Open a Terminal window and enter [``cd/dir``] into the directory where you
|
||||||
|
saved the above ``python2.7-environment.yml`` file.
|
||||||
3. Install using this file. This step can take about 5-10 minutes to install.
|
3. Install using this file. This step can take about 5-10 minutes to install.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
conda env create -f python2.7-environment.yml
|
conda env create -f python2.7-environment.yml
|
||||||
|
|
||||||
4. Activate the environment (which you need to do every time you start a new session
|
4. Activate the environment (which you need to do every time you start a new
|
||||||
to run Catalyst):
|
session to run Catalyst):
|
||||||
|
|
||||||
**Linux or OSX:**
|
**Linux or OSX:**
|
||||||
|
|
||||||
@@ -289,14 +351,15 @@ Congratulations! You now have Catalyst installed.
|
|||||||
Troubleshooting ``conda`` Install
|
Troubleshooting ``conda`` Install
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
If the command ``conda env create -f python2.7-environment.yml`` in step 3 above failed
|
If the command ``conda env create -f python2.7-environment.yml`` in step 3
|
||||||
for any reason, you can try setting up the environment manually with the following steps:
|
above failed for any reason, you can try setting up the environment manually
|
||||||
|
with the following steps:
|
||||||
|
|
||||||
1. Create the environment:
|
1. Create the environment:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
conda create --name catalyst python=2.7 scipy
|
conda create --name catalyst python=2.7 scipy zlib
|
||||||
|
|
||||||
2. Activate the environment:
|
2. Activate the environment:
|
||||||
|
|
||||||
@@ -321,20 +384,23 @@ for any reason, you can try setting up the environment manually with the followi
|
|||||||
Getting Help
|
Getting Help
|
||||||
------------
|
------------
|
||||||
|
|
||||||
If after following the instructions above, and going through the *Troubleshooting* sections,
|
If after following the instructions above, and going through the
|
||||||
you still experience problems installing Catalyst, you can seek additional help through the
|
*Troubleshooting* sections, you still experience problems installing Catalyst,
|
||||||
following channels:
|
you can seek additional help through the following channels:
|
||||||
|
|
||||||
- Join our `Discord community <https://discord.gg/SJK32GY>`_, and head over the #catalyst_dev
|
- Join our `Discord community <https://discord.gg/SJK32GY>`_, and head over
|
||||||
channel where many other users (as well as the project developers) hang out, and can assist
|
the #catalyst_dev channel where many other users (as well as the project
|
||||||
you with your particular issue. The more descriptive and the more information you can provide,
|
developers) hang out, and can assist you with your particular issue. The
|
||||||
the easiest will be for others to help you out.
|
more descriptive and the more information you can provide, the easiest will
|
||||||
|
be for others to help you out.
|
||||||
|
|
||||||
- Report the problem you are experiencing on our
|
- Report the problem you are experiencing on our
|
||||||
`GitHub repository <https://github.com/enigmampc/catalyst/issues>`_ following the guidelines
|
`GitHub repository <https://github.com/enigmampc/catalyst/issues>`_
|
||||||
provided therein. Before you do so, take a moment to browse through all `previous reported issues
|
following the guidelines provided therein. Before you do so, take a moment
|
||||||
<https://github.com/enigmampc/catalyst/issues?utf8=%E2%9C%93&q=is%3Aissue>`_ in the likely case
|
to browse through all `previous reported issues
|
||||||
that someone else experienced that same issue before, and you get a hint on how to solve it.
|
<https://github.com/enigmampc/catalyst/issues?utf8=%E2%9C%93&q=is%3Aissue>`_
|
||||||
|
in the likely case that someone else experienced that same issue before,
|
||||||
|
and you get a hint on how to solve it.
|
||||||
|
|
||||||
|
|
||||||
.. _`Debian-derived`: https://www.debian.org/misc/children-distros
|
.. _`Debian-derived`: https://www.debian.org/misc/children-distros
|
||||||
|
|||||||
+15794
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
|||||||
|
Live Trading
|
||||||
|
============
|
||||||
|
This document explains how to get started with live trading.
|
||||||
|
|
||||||
|
Supported Exchanges
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
Catalyst can trade against these exchanges:
|
||||||
|
|
||||||
|
- Bitfinex, id= ``bitfinex``
|
||||||
|
- Bittrex, id= ``bittrex``
|
||||||
|
- Poloniex, id= ``poloniex``
|
||||||
|
|
||||||
|
Authentication
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
Most exchanges require token key/secret combination for authentication. By
|
||||||
|
convention, Catalyst uses an ``auth.json`` file to hold this data.
|
||||||
|
|
||||||
|
This example illustrates the convention using the *Bitfinex* exchange.
|
||||||
|
Here is how to generate key and secret values for the Bitfinex exchange:
|
||||||
|
https://docs.bitfinex.com/v1/docs/api-access. Most exchanges follow
|
||||||
|
a similar process.
|
||||||
|
|
||||||
|
The auth.json file:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "bitfinex",
|
||||||
|
"key": "my-key",
|
||||||
|
"secret": "my-secret"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
The file goes here: ``~/.catalyst/data/exchanges/bitfinex/auth.json``
|
||||||
|
|
||||||
|
Note that the `bitfinex` part in the directory above corresponds to the id of the Bitfinex
|
||||||
|
exchange as defined in the "Supported Exchanges" section above.
|
||||||
|
Attempting to run an algorithm where the targeted exchange is missing
|
||||||
|
its ``auth.json`` file will create the directory structure and create an empty
|
||||||
|
auth.json file, but will result in an error.
|
||||||
|
|
||||||
|
Currency Symbols
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
Catalyst introduces a universal convention to reference
|
||||||
|
trading pairs and individual currencies. This
|
||||||
|
is required to ensure that the ``symbol()`` api predictably
|
||||||
|
returns the correct asset regardless of the targeted exchange.
|
||||||
|
|
||||||
|
Exchanges tend to use their own convention to represent currencies
|
||||||
|
(e.g. XBT and BTC both represent Bitcoin on different exchanges).
|
||||||
|
Trading pairs are also inconsistent. For example, Bitfinex
|
||||||
|
puts the market currency before the base currency without a
|
||||||
|
separator, Bittrex puts the base currency first and uses a dash
|
||||||
|
seperator.
|
||||||
|
|
||||||
|
Here is the Catalyst convention:
|
||||||
|
|
||||||
|
*[Market Currency]_[Base Currency]* all lowercase.
|
||||||
|
|
||||||
|
Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention.
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
# With Bitfinex
|
||||||
|
bitcoin_usd_asset = symbol('btc_usd')
|
||||||
|
ethereum_bitcoin_asset = symbol('eth_btc')
|
||||||
|
|
||||||
|
# With Bittrex
|
||||||
|
ethereum_bitcoin_asset = symbol('eth_btc')
|
||||||
|
neo_ethereum_asset = symbol('neo_eth)
|
||||||
|
|
||||||
|
Note that the trading pairs are always referenced in the same manner.
|
||||||
|
However, not all trading pairs are available on all exchanges. An
|
||||||
|
error will occur if the specified trading pair is not trading
|
||||||
|
on the exchange. To check which currency pairs are available on each
|
||||||
|
of the supported exchanges, see `Catalyst Market Coverage <https://www.enigma.co/catalyst/status`_.
|
||||||
|
|
||||||
|
Trading an Algorithm
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
There is no special convention to follow when writing an
|
||||||
|
algorithm for live trading. The same algorithm should work in
|
||||||
|
backtest and live execution mode without modification.
|
||||||
|
|
||||||
|
What differs are the arguments provided to the catalyst client or
|
||||||
|
`run_algorithm()` interface. Here is the same example in both interfaces:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
catalyst live -f my_algo_code -x bitfinex -c btc -n my_algo_name
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
run_algorithm(
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
live=True,
|
||||||
|
algo_namespace='my_algo_name',
|
||||||
|
base_currency='btc'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Here is the breakdown of the new arguments:
|
||||||
|
|
||||||
|
- ``live``: Boolean flag which enables live trading.
|
||||||
|
- ``exchange_name``: The name of the targeted exchange
|
||||||
|
(supported values: *bitfinex*, *bittrex*).
|
||||||
|
- ``algo_namespace``: A arbitrary label assigned to your algorithm for
|
||||||
|
data storage purposes.
|
||||||
|
- ``base_currency``: The base currency used to calculate the
|
||||||
|
statistics of your algorithm. Currently, the base currency of all
|
||||||
|
trading pairs of your algorithm must match this value.
|
||||||
|
|
||||||
|
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>`_
|
||||||
+172
-11
@@ -2,24 +2,185 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
.. include:: whatsnew/1.1.1.txt
|
Version 0.3.5
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-11-2
|
||||||
|
|
||||||
.. include:: whatsnew/1.1.0.txt
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
.. include:: whatsnew/1.0.2.txt
|
- Added workaround for: KeyError: Timestamp error (:issue:`53`)
|
||||||
|
|
||||||
.. include:: whatsnew/1.0.1.txt
|
Version 0.3.4
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-11-2
|
||||||
|
|
||||||
.. include:: whatsnew/1.0.0.txt
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
.. include:: whatsnew/0.9.0.txt
|
- Fixed issue with auto-ingestion of minute data (:issue:`47`)
|
||||||
|
- Fixed issue with sell orders in backtesting
|
||||||
|
- Fixed data frequency issues with data.history() in backtesting
|
||||||
|
- Fixed an issue with can_trade()
|
||||||
|
- Reduced the commission and slippage values to account for lower volume transactions
|
||||||
|
|
||||||
.. include:: whatsnew/0.8.4.txt
|
Build
|
||||||
|
~~~~~
|
||||||
|
|
||||||
.. include:: whatsnew/0.8.3.txt
|
- Added more unit tests
|
||||||
|
|
||||||
.. include:: whatsnew/0.8.0.txt
|
Documentation
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. include:: whatsnew/0.7.0.txt
|
- Improved installation notes for Windows C++ compiler and Conda
|
||||||
|
- Addition of `Jupyter Notebook guide <https://enigmampc.github.io/catalyst/jupyter.html>`_
|
||||||
|
- Addition of `Live Trading page <https://enigmampc.github.io/catalyst/live-trading.html>`_
|
||||||
|
- Addition of `Videos page <https://enigmampc.github.io/catalyst/videos.html>`_
|
||||||
|
- Addition of `Resources page <https://enigmampc.github.io/catalyst/resources.html>`_
|
||||||
|
- Addition of `Development Guidelines <https://enigmampc.github.io/catalyst/development-guidelines.html>`_
|
||||||
|
- Addition of `Release Notes <https://enigmampc.github.io/catalyst/releases.html>`_
|
||||||
|
- Updated code docstrings
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.3.3
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-10-26
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Fix missing -x in ingest-exchange
|
||||||
|
- Fix issue with daily chunks end date (data bundles)
|
||||||
|
- Fix issue in the prepare_chunk logic (data bundles)
|
||||||
|
|
||||||
|
Build
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Added data validation unit tests
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.3.2
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-10-25
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Fix to work with empty data bundles
|
||||||
|
- Fix Windows path of ``$HOME/.catalyst`` folder
|
||||||
|
- Fix ``etc/python2.7-environment.yml`` for Windows Conda install
|
||||||
|
- Fix hash method to create sid numbers compatible across platforms
|
||||||
|
- Fix an issue with asset date in chunks
|
||||||
|
|
||||||
|
Build
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Python3 adjustments
|
||||||
|
- Added method to clean bundle folders, and remove symbols.json
|
||||||
|
- Implemented and improved unit tests
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.3.1
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-10-22
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Fixed OS-dependent path issue in data bundle
|
||||||
|
- Changed handling of empty ``auth.json``, instead of throwing an error for missing file
|
||||||
|
- Updated ``etc/python2.7-environment.yml`` to work with Catalyst version 0.3
|
||||||
|
- Updated ``catalyst/examples/buy_and_hodl.py`` and ``catalyst/examples/buy_low_sell_high.py`` to work with Catalyst version 0.3
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.3
|
||||||
|
^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-10-20
|
||||||
|
|
||||||
|
- Standardized live and backtesting syntax
|
||||||
|
- Added a repository for historical data
|
||||||
|
- Added supported for multiple exchanges per algorithm
|
||||||
|
- Added a standardized dictionary of symbols for each exchange
|
||||||
|
- Added auto-ingestion of bundle data while backtesting
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.2.dev5
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-10-03
|
||||||
|
|
||||||
|
- Fixes bug in data.history function that was formatting 'volume' data as integers, now they are returned as floats with up to 9 decimals of precision. Data bundles redone.
|
||||||
|
|
||||||
|
Version 0.2.dev4
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-09-20
|
||||||
|
|
||||||
|
- Fixes bug in the pricing resolution of 1-minute data, now set to 8 decimal places. Pricing resolution of daily data remains set to 9 decimal places.
|
||||||
|
- The current data bundle takes 340MB compressed for download, and 460MB uncompressed on disk for Catalyst to use.
|
||||||
|
|
||||||
|
Version 0.2.dev3
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-09-20
|
||||||
|
|
||||||
|
- 1-minute resolution OHLCV data bundle for backtesting from Poloniex exchange
|
||||||
|
- Implementation of trading of fractional crypto assets (i.e. 0.01 BTC)
|
||||||
|
- Minimum trade size of a coin can be configured on a per-coin basis, defaults to 0.00000001 in backtesting (most exchanges set the minimum trade to larger amounts, which will impact live trading)
|
||||||
|
- Increased pricing resolution from 3 to 9 decimal places
|
||||||
|
- The current data bundle takes 40MB compressed for download, and 99MB uncompressed on disk for Catalyst to use.
|
||||||
|
|
||||||
|
Version 0.2.dev2
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-09-07
|
||||||
|
|
||||||
|
- Fix path issue
|
||||||
|
|
||||||
|
Version 0.2.dev1
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-09-03
|
||||||
|
|
||||||
|
- Implementation of live trading:
|
||||||
|
|
||||||
|
- Comprehensive trading functionality against exchanges Bitfinex and Bittrex.
|
||||||
|
- Support for all trading pairs available on each exchange.
|
||||||
|
- Multiple algorithms can trade simultaneously against a single exchange using the same account.
|
||||||
|
- Each algorithm has a persisted state (i.e. algorithm can be stopped and restarted preserving the state without data loss) that tracks all open orders, executed transactions and portfolio positions.
|
||||||
|
|
||||||
|
- Minute by minute portfolio performance metrics.
|
||||||
|
|
||||||
|
- Daily summary performance statistics compatible with pyfolio, a Python library for performance and risk analysis of financial portfolios
|
||||||
|
|
||||||
|
Version 0.1.dev9
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-08-28
|
||||||
|
|
||||||
|
- Retrieval of crypto benchmark from bundle, instead of hitting Poloniex exchange directly
|
||||||
|
- Change of bundle storage provider from Dropbox to AWS
|
||||||
|
- Fix issue with 1/1000 scaling issue of prices in bundle
|
||||||
|
|
||||||
|
Version 0.1.dev8
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-08-18
|
||||||
|
|
||||||
|
- Fixes issue in the creation of bundles (:issue:`27`)
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.1.dev7
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
- Fixes issues in empty benchmark (:issue:`16`)
|
||||||
|
- Fixes issue of normalizing timestamps before comparison (:issue:`24`)
|
||||||
|
- Generic data bundles
|
||||||
|
- CLI UI improvements
|
||||||
|
|
||||||
|
Version 0.1.dev6
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Release Date**: 2017-07-13
|
||||||
|
|
||||||
|
- Initial public release
|
||||||
|
|
||||||
.. include:: whatsnew/0.6.1.txt
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
Resources
|
||||||
|
=========
|
||||||
|
|
||||||
|
- `Catalyst Whitepaper <https://www.enigma.co/enigma_catalyst.pdf>`_
|
||||||
|
|
||||||
|
|
||||||
|
Related 3rd Party APIs
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- `Zipline <http://www.zipline.io/appendix.html>`_ is a Pythonic Algorithmic
|
||||||
|
Trading Library, and the project Catalyst forked off in the spring of 2017.
|
||||||
|
- `Quantopian <https://www.quantopian.com/help>`_ provides a platform for
|
||||||
|
freelance quantitative analysts develop, test, and use trading algorithms to
|
||||||
|
buy and sell securities. They aim to create a crowd-sourced hedge fund by
|
||||||
|
fostering their community of freelance traders. Quantopian's backtesting and
|
||||||
|
live-trading engine is powered by *Zipline*.
|
||||||
|
- `Pandas <https://pandas.pydata.org/pandas-docs/stable/api.html>`_ is a Python
|
||||||
|
library providing high-performance, easy-to-use data structures and data
|
||||||
|
analysis tools. Catalyst relies heavily on pandas, and many API functions
|
||||||
|
return data as Pandas dataframes.
|
||||||
|
- `Numpy <https://docs.scipy.org/doc/numpy/reference/>`_ is the fundamental
|
||||||
|
package for scientific computing with Python. Some of the data computation
|
||||||
|
that your algorithms will need, will be optimized leveraging Numpy.
|
||||||
|
- `Matplotlib <https://matplotlib.org/1.5.3/api/index.html>`_ is a Python 2D
|
||||||
|
plotting library that many of examples rely on to plot the performance of
|
||||||
|
trading algorithms
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
Videos
|
||||||
|
======
|
||||||
|
|
||||||
|
|
||||||
|
Installation: MacOS
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZnsslmHljvw" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
Installation: Windows
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Where things go smoothly:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/H8HqcEbZmkk" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
|
|
||||||
|
Where things don't:
|
||||||
|
|
||||||
|
Coming up next!
|
||||||
+19
-4
@@ -1,9 +1,22 @@
|
|||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg
|
||||||
|
|
|
|
||||||
Catalyst is a data-driven crypto investment platform. It supports both
|
Catalyst is an algorithmic trading library for crypto-assets written in Python.
|
||||||
backtesting and live-trading in a number of different crypto-exchanges.
|
It allows trading strategies to be easily expressed and backtested against
|
||||||
Catalyst empowers users to share and curate data and build profitable,
|
historical data (with daily and minute resolution), providing analytics and
|
||||||
data-driven investment strategies.
|
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
|
Features
|
||||||
========
|
========
|
||||||
@@ -26,3 +39,5 @@ Features
|
|||||||
- Statistic and machine learning libraries like matplotlib, scipy,
|
- Statistic and machine learning libraries like matplotlib, scipy,
|
||||||
statsmodels, and sklearn support development, analysis, and
|
statsmodels, and sklearn support development, analysis, and
|
||||||
visualization of state-of-the-art trading systems.
|
visualization of state-of-the-art trading systems.
|
||||||
|
- Addition of Bitcoin price (btc_usdt) as a benchmark for comparing
|
||||||
|
performance across trading algorithms.
|
||||||
@@ -1,30 +1,22 @@
|
|||||||
name: catalyst
|
name: catalyst
|
||||||
channels:
|
channels:
|
||||||
- statiskit
|
|
||||||
- defaults
|
- defaults
|
||||||
dependencies:
|
dependencies:
|
||||||
- certifi=2016.2.28=py27_0
|
- certifi=2016.2.28=py27_0
|
||||||
- coverage=4.4.1=py27_0
|
- mkl=2017.0.3
|
||||||
- nose=1.3.7=py27_1
|
- numpy=1.13.1=py27_0
|
||||||
- openssl=1.0.2l=0
|
- openssl=1.0.2l
|
||||||
- path.py=10.3.1=py27_0
|
|
||||||
- pip=9.0.1=py27_1
|
- pip=9.0.1=py27_1
|
||||||
- python=2.7.13=0
|
- python=2.7.13
|
||||||
- pyyaml=3.12=py27_0
|
- scipy=0.19.1=np113py27_0
|
||||||
- readline=6.2=2
|
- setuptools=36.4.0=py27_1
|
||||||
- setuptools=36.4.0=py27_0
|
- sqlite=3.13.0
|
||||||
- six=1.10.0=py27_0
|
- tk=8.5.18
|
||||||
- sqlite=3.13.0=0
|
|
||||||
- tk=8.5.18=0
|
|
||||||
- wheel=0.29.0=py27_0
|
- wheel=0.29.0=py27_0
|
||||||
- yaml=0.1.6=0
|
- zlib=1.2.11
|
||||||
- zlib=1.2.11=0
|
|
||||||
- libdev=1.0.0=py27_0
|
|
||||||
- python-dev=1.0.0=py27_0
|
|
||||||
- python-scons=3.0.0=py27_0
|
|
||||||
- pip:
|
- pip:
|
||||||
- alembic==0.9.5
|
- alembic==0.9.6
|
||||||
- backports.shutil-get-terminal-size==1.0.0
|
- backports.functools-lru-cache==1.4
|
||||||
- bcolz==0.12.1
|
- bcolz==0.12.1
|
||||||
- bottleneck==1.2.1
|
- bottleneck==1.2.1
|
||||||
- chardet==3.0.4
|
- chardet==3.0.4
|
||||||
@@ -32,36 +24,22 @@ dependencies:
|
|||||||
- contextlib2==0.5.5
|
- contextlib2==0.5.5
|
||||||
- cycler==0.10.0
|
- cycler==0.10.0
|
||||||
- cyordereddict==1.0.0
|
- cyordereddict==1.0.0
|
||||||
- cython==0.26.1
|
- cython==0.27.1
|
||||||
- decorator==4.1.2
|
- decorator==4.1.2
|
||||||
- empyrical==0.2.1
|
- empyrical==0.2.1
|
||||||
- enigma-catalyst>=0.2.dev2
|
|
||||||
- enum34==1.1.6
|
|
||||||
- functools32==3.2.3.post2
|
|
||||||
- idna==2.6
|
- idna==2.6
|
||||||
- intervaltree==2.1.0
|
- intervaltree==2.1.0
|
||||||
- ipdb==0.10.3
|
|
||||||
- ipdbplugin==1.4.5
|
|
||||||
- ipython==5.5.0
|
|
||||||
- ipython-genutils==0.2.0
|
|
||||||
- logbook==1.1.0
|
- logbook==1.1.0
|
||||||
- lru-dict==1.1.6
|
- lru-dict==1.1.6
|
||||||
- mako==1.0.7
|
- mako==1.0.7
|
||||||
- markupsafe==1.0
|
- markupsafe==1.0
|
||||||
- matplotlib==2.0.2
|
- matplotlib==2.1.0
|
||||||
- multipledispatch==0.4.9
|
- multipledispatch==0.4.9
|
||||||
- networkx==1.11
|
- networkx==2.0
|
||||||
- numexpr==2.6.4
|
- numexpr==2.6.4
|
||||||
- numpy==1.13.1
|
|
||||||
- pandas==0.19.2
|
- pandas==0.19.2
|
||||||
- pandas-datareader==0.5.0
|
- pandas-datareader==0.5.0
|
||||||
- pathlib2==2.3.0
|
|
||||||
- patsy==0.4.1
|
- patsy==0.4.1
|
||||||
- pexpect==4.2.1
|
|
||||||
- pickleshare==0.7.4
|
|
||||||
- prompt-toolkit==1.0.15
|
|
||||||
- ptyprocess==0.5.2
|
|
||||||
- pygments==2.2.0
|
|
||||||
- pyparsing==2.2.0
|
- pyparsing==2.2.0
|
||||||
- python-dateutil==2.6.1
|
- python-dateutil==2.6.1
|
||||||
- python-editor==1.0.3
|
- python-editor==1.0.3
|
||||||
@@ -69,16 +47,12 @@ dependencies:
|
|||||||
- requests==2.18.4
|
- requests==2.18.4
|
||||||
- requests-file==1.4.2
|
- requests-file==1.4.2
|
||||||
- requests-ftp==0.3.1
|
- requests-ftp==0.3.1
|
||||||
- scandir==1.5
|
- six==1.11.0
|
||||||
- scipy==0.19.1
|
|
||||||
- scons==3.0.0a20170821
|
|
||||||
- simplegeneric==0.8.1
|
|
||||||
- sortedcontainers==1.5.7
|
- sortedcontainers==1.5.7
|
||||||
- sqlalchemy==1.1.14
|
- sqlalchemy==1.1.14
|
||||||
- statsmodels==0.8.0
|
- statsmodels==0.8.0
|
||||||
- subprocess32==3.2.7
|
- subprocess32==3.2.7
|
||||||
- tables==3.4.2
|
- tables==3.4.2
|
||||||
- toolz==0.8.2
|
- toolz==0.8.2
|
||||||
- traitlets==4.3.2
|
|
||||||
- urllib3==1.22
|
- urllib3==1.22
|
||||||
- wcwidth==0.1.7
|
- enigma-catalyst>=0.3
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Incompatible with earlier PIP versions
|
# Incompatible with earlier PIP versions
|
||||||
pip>=7.1.0
|
pip>=7.1.0
|
||||||
# bcolz fails to install if this is not in the build_requires.
|
# bcolz fails to install if this is not in the build_requires.
|
||||||
setuptools>18.0
|
setuptools>36.0
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
Logbook==0.12.5
|
Logbook==0.12.5
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import unittest
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import shutil
|
||||||
|
import random
|
||||||
|
import tempfile
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarWriter, \
|
||||||
|
BcolzExchangeBarReader
|
||||||
|
|
||||||
|
from catalyst.exchange.bundle_utils import get_df_from_arrays
|
||||||
|
|
||||||
|
from nose.tools import assert_equals
|
||||||
|
|
||||||
|
|
||||||
|
class TestBcolzWriter(object):
|
||||||
|
@classmethod
|
||||||
|
def setup_class(cls):
|
||||||
|
cls.columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.root_dir = tempfile.mkdtemp() # Create a temporary directory
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.root_dir) # Remove the directory after the test
|
||||||
|
|
||||||
|
def generate_df(self, exchange_name, freq, start, end):
|
||||||
|
bundle = ExchangeBundle(exchange_name)
|
||||||
|
index = bundle.get_calendar_periods_range(start, end, freq)
|
||||||
|
df = pd.DataFrame(index=index, columns=self.columns)
|
||||||
|
df.fillna(random.random(), inplace=True)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def test_bcolz_write_daily_past(self):
|
||||||
|
start = pd.to_datetime('2016-01-01')
|
||||||
|
end = pd.to_datetime('2016-12-31')
|
||||||
|
freq = 'daily'
|
||||||
|
|
||||||
|
df = self.generate_df('bitfinex', freq, start, end)
|
||||||
|
|
||||||
|
writer = BcolzExchangeBarWriter(
|
||||||
|
rootdir=self.root_dir,
|
||||||
|
start_session=start,
|
||||||
|
end_session=end,
|
||||||
|
data_frequency=freq,
|
||||||
|
write_metadata=True)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data.append((1, df))
|
||||||
|
writer.write(data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bcolz_write_daily_present(self):
|
||||||
|
start = pd.to_datetime('2017-01-01')
|
||||||
|
end = pd.to_datetime('today')
|
||||||
|
freq = 'daily'
|
||||||
|
|
||||||
|
df = self.generate_df('bitfinex', freq, start, end)
|
||||||
|
|
||||||
|
writer = BcolzExchangeBarWriter(
|
||||||
|
rootdir=self.root_dir,
|
||||||
|
start_session=start,
|
||||||
|
end_session=end,
|
||||||
|
data_frequency=freq,
|
||||||
|
write_metadata=True)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data.append((1, df))
|
||||||
|
writer.write(data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bcolz_write_minute_past(self):
|
||||||
|
start = pd.to_datetime('2015-04-01 00:00')
|
||||||
|
end = pd.to_datetime('2015-04-30 23:59')
|
||||||
|
freq = 'minute'
|
||||||
|
|
||||||
|
df = self.generate_df('bitfinex', freq, start, end)
|
||||||
|
|
||||||
|
writer = BcolzExchangeBarWriter(
|
||||||
|
rootdir=self.root_dir,
|
||||||
|
start_session=start,
|
||||||
|
end_session=end,
|
||||||
|
data_frequency=freq,
|
||||||
|
write_metadata=True)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data.append((1, df))
|
||||||
|
writer.write(data)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bcolz_write_minute_present(self):
|
||||||
|
start = pd.to_datetime('2017-10-01 00:00')
|
||||||
|
end = pd.to_datetime('today')
|
||||||
|
freq = 'minute'
|
||||||
|
|
||||||
|
df = self.generate_df('bitfinex', freq, start, end)
|
||||||
|
|
||||||
|
writer = BcolzExchangeBarWriter(
|
||||||
|
rootdir=self.root_dir,
|
||||||
|
start_session=start,
|
||||||
|
end_session=end,
|
||||||
|
data_frequency=freq,
|
||||||
|
write_metadata=True)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data.append((1, df))
|
||||||
|
writer.write(data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def bcolz_exchange_daily_write_read(self, exchange_name):
|
||||||
|
start = pd.to_datetime('2017-10-01 00:00')
|
||||||
|
end = pd.to_datetime('today')
|
||||||
|
freq = 'daily'
|
||||||
|
|
||||||
|
bundle = ExchangeBundle(exchange_name)
|
||||||
|
|
||||||
|
df = self.generate_df(exchange_name, freq, start, end)
|
||||||
|
|
||||||
|
print df.index[0],df.index[-1]
|
||||||
|
|
||||||
|
writer = BcolzExchangeBarWriter(
|
||||||
|
rootdir=self.root_dir,
|
||||||
|
start_session=df.index[0],
|
||||||
|
end_session=df.index[-1],
|
||||||
|
data_frequency=freq,
|
||||||
|
write_metadata=True)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
data.append((1, df))
|
||||||
|
writer.write(data)
|
||||||
|
|
||||||
|
reader = BcolzExchangeBarReader(rootdir=self.root_dir,
|
||||||
|
data_frequency=freq)
|
||||||
|
|
||||||
|
arrays = reader.load_raw_arrays(self.columns, start, end, [1, ])
|
||||||
|
|
||||||
|
periods = bundle.get_calendar_periods_range(
|
||||||
|
start, end, freq
|
||||||
|
)
|
||||||
|
|
||||||
|
dx = get_df_from_arrays(arrays, periods)
|
||||||
|
|
||||||
|
assert_equals(df.equals(df), True)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bcolz_bitfinex_daily_write_read(self):
|
||||||
|
self.bcolz_exchange_daily_write_read('bitfinex')
|
||||||
|
|
||||||
|
def test_bcolz_poloniex_daily_write_read(self):
|
||||||
|
self.bcolz_exchange_daily_write_read('poloniex')
|
||||||
@@ -8,7 +8,7 @@ from catalyst.finance.execution import (LimitOrder)
|
|||||||
log = Logger('test_bitfinex')
|
log = Logger('test_bitfinex')
|
||||||
|
|
||||||
|
|
||||||
class BitfinexTestCase(BaseExchangeTestCase):
|
class TestBitfinex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
log.info('creating bitfinex object')
|
log.info('creating bitfinex object')
|
||||||
@@ -48,7 +48,7 @@ class BitfinexTestCase(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(
|
||||||
data_frequency='1m',
|
freq='1T',
|
||||||
assets=self.exchange.get_asset('neo_btc')
|
assets=self.exchange.get_asset('neo_btc')
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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
|
||||||
@@ -7,15 +8,15 @@ from catalyst.exchange.exchange_utils import get_exchange_auth
|
|||||||
log = Logger('test_bittrex')
|
log = Logger('test_bittrex')
|
||||||
|
|
||||||
|
|
||||||
class BittrexTestCase(BaseExchangeTestCase):
|
class TestBittrex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
print ('creating bittrex object')
|
|
||||||
auth = get_exchange_auth('bittrex')
|
auth = get_exchange_auth('bittrex')
|
||||||
self.exchange = Bittrex(
|
self.exchange = Bittrex(
|
||||||
key=auth['key'],
|
key=auth['key'],
|
||||||
secret=auth['secret'],
|
secret=auth['secret'],
|
||||||
base_currency='btc'
|
base_currency=None,
|
||||||
|
portfolio=None
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_order(self):
|
def test_order(self):
|
||||||
@@ -51,16 +52,19 @@ class BittrexTestCase(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(
|
||||||
data_frequency='5m',
|
freq='5T',
|
||||||
assets=self.exchange.get_asset('neo_btc')
|
assets=self.exchange.get_asset('neo_btc'),
|
||||||
|
bar_count=20,
|
||||||
|
end_dt=pd.to_datetime('2017-10-20', utc=True)
|
||||||
)
|
)
|
||||||
ohlcv_neo_ubq = self.exchange.get_candles(
|
ohlcv_neo_ubq = self.exchange.get_candles(
|
||||||
data_frequency='5m',
|
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)
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
+251
-17
@@ -1,22 +1,26 @@
|
|||||||
from logging import Logger
|
import hashlib
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from catalyst import get_calendar
|
from catalyst import get_calendar
|
||||||
from catalyst.exchange.bundle_utils import get_bcolz_chunk, get_periods, \
|
from catalyst.exchange.bundle_utils import get_bcolz_chunk, \
|
||||||
get_periods_range
|
get_start_dt, get_df_from_arrays
|
||||||
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \
|
||||||
BcolzExchangeBarWriter
|
BcolzExchangeBarWriter
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle, \
|
from catalyst.exchange.exchange_bundle import ExchangeBundle, \
|
||||||
BUNDLE_NAME_TEMPLATE
|
BUNDLE_NAME_TEMPLATE
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_folder
|
from catalyst.exchange.exchange_utils import get_exchange_folder
|
||||||
from catalyst.exchange.init_utils import get_exchange
|
from catalyst.exchange.factory import get_exchange
|
||||||
|
from catalyst.exchange.stats_utils import df_to_string
|
||||||
from catalyst.utils.paths import ensure_directory
|
from catalyst.utils.paths import ensure_directory
|
||||||
|
|
||||||
log = Logger('test_exchange_bundle')
|
log = getLogger('test_exchange_bundle')
|
||||||
|
|
||||||
|
|
||||||
class ExchangeBundleTestCase:
|
class TestExchangeBundle:
|
||||||
def test_spot_value(self):
|
def test_spot_value(self):
|
||||||
data_frequency = 'daily'
|
data_frequency = 'daily'
|
||||||
exchange_name = 'poloniex'
|
exchange_name = 'poloniex'
|
||||||
@@ -43,11 +47,11 @@ class ExchangeBundleTestCase:
|
|||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
assets = [
|
assets = [
|
||||||
exchange.get_asset('neo_eth')
|
exchange.get_asset('xmr_btc')
|
||||||
]
|
]
|
||||||
|
|
||||||
# start = pd.to_datetime('2017-09-01', utc=True)
|
# start = pd.to_datetime('2017-09-01', utc=True)
|
||||||
start = pd.to_datetime('2017-9-15', utc=True)
|
start = pd.to_datetime('2016-01-01', utc=True)
|
||||||
end = pd.to_datetime('2017-9-30', utc=True)
|
end = pd.to_datetime('2017-9-30', utc=True)
|
||||||
|
|
||||||
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
||||||
@@ -93,19 +97,44 @@ class ExchangeBundleTestCase:
|
|||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_ingest_daily(self):
|
def test_ingest_exchange(self):
|
||||||
# exchange_name = 'bitfinex'
|
# exchange_name = 'bitfinex'
|
||||||
# data_frequency = 'daily'
|
# data_frequency = 'daily'
|
||||||
# include_symbols = 'neo_btc,bch_btc,eth_btc'
|
# include_symbols = 'neo_btc,bch_btc,eth_btc'
|
||||||
|
|
||||||
exchange_name = 'poloniex'
|
exchange_name = 'bitfinex'
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
|
||||||
|
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
||||||
|
exchange_bundle.ingest(
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
include_symbols=None,
|
||||||
|
exclude_symbols=None,
|
||||||
|
start=None,
|
||||||
|
end=None,
|
||||||
|
show_progress=True
|
||||||
|
)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_ingest_daily(self):
|
||||||
|
exchange_name = 'bitfinex'
|
||||||
data_frequency = 'daily'
|
data_frequency = 'daily'
|
||||||
include_symbols = 'btc_usdt'
|
include_symbols = 'btc_usd'
|
||||||
|
|
||||||
start = pd.to_datetime('2016-1-1', utc=True)
|
# exchange_name = 'poloniex'
|
||||||
end = pd.to_datetime('2017-10-16', utc=True)
|
# data_frequency = 'daily'
|
||||||
periods = get_periods_range(start, end, data_frequency)
|
# include_symbols = 'eth_btc'
|
||||||
|
|
||||||
|
# start = pd.to_datetime('2017-1-1', utc=True)
|
||||||
|
# end = pd.to_datetime('2017-10-16', utc=True)
|
||||||
|
# periods = get_periods_range(start, end, data_frequency)
|
||||||
|
|
||||||
|
start = None
|
||||||
|
end = None
|
||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
exchange_bundle = ExchangeBundle(exchange)
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
|
||||||
@@ -125,12 +154,18 @@ class ExchangeBundleTestCase:
|
|||||||
assets.append(exchange.get_asset(pair_symbol))
|
assets.append(exchange.get_asset(pair_symbol))
|
||||||
|
|
||||||
reader = exchange_bundle.get_reader(data_frequency)
|
reader = exchange_bundle.get_reader(data_frequency)
|
||||||
|
start_dt = reader.first_trading_day
|
||||||
|
end_dt = reader.last_available_dt
|
||||||
|
|
||||||
|
if data_frequency == 'daily':
|
||||||
|
end_dt = end_dt - pd.Timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
arrays = reader.load_raw_arrays(
|
arrays = reader.load_raw_arrays(
|
||||||
sids=[asset.sid],
|
sids=[asset.sid],
|
||||||
fields=['close'],
|
fields=['close'],
|
||||||
start_dt=start,
|
start_dt=start_dt,
|
||||||
end_dt=end
|
end_dt=end_dt
|
||||||
)
|
)
|
||||||
print('found {} rows for {} ingestion\n{}'.format(
|
print('found {} rows for {} ingestion\n{}'.format(
|
||||||
len(arrays[0]), asset.symbol, arrays[0])
|
len(arrays[0]), asset.symbol, arrays[0])
|
||||||
@@ -274,7 +309,7 @@ class ExchangeBundleTestCase:
|
|||||||
data_frequency = 'minute'
|
data_frequency = 'minute'
|
||||||
|
|
||||||
exchange = get_exchange(exchange_name)
|
exchange = get_exchange(exchange_name)
|
||||||
asset = exchange.get_asset('neo_btc')
|
asset = exchange.get_asset('neos_btc')
|
||||||
|
|
||||||
path = get_bcolz_chunk(
|
path = get_bcolz_chunk(
|
||||||
exchange_name=exchange_name,
|
exchange_name=exchange_name,
|
||||||
@@ -284,3 +319,202 @@ class ExchangeBundleTestCase:
|
|||||||
)
|
)
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_hash_symbol(self):
|
||||||
|
symbol = 'etc_btc'
|
||||||
|
sid = int(
|
||||||
|
hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16
|
||||||
|
) % 10 ** 6
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_validate_data(self):
|
||||||
|
exchange_name = 'bitfinex'
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
exchange_bundle = ExchangeBundle(exchange)
|
||||||
|
assets = [exchange.get_asset('iot_btc')]
|
||||||
|
|
||||||
|
end_dt = pd.to_datetime('2017-9-2 1:00', utc=True)
|
||||||
|
bar_count = 60
|
||||||
|
|
||||||
|
bundle_series = exchange_bundle.get_history_window_series(
|
||||||
|
assets=assets,
|
||||||
|
end_dt=end_dt,
|
||||||
|
bar_count=bar_count * 5,
|
||||||
|
field='close',
|
||||||
|
data_frequency='minute',
|
||||||
|
)
|
||||||
|
candles = exchange.get_candles(
|
||||||
|
assets=assets,
|
||||||
|
end_dt=end_dt,
|
||||||
|
bar_count=bar_count,
|
||||||
|
freq='1T'
|
||||||
|
)
|
||||||
|
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
for asset in assets:
|
||||||
|
bundle_df = pd.DataFrame(
|
||||||
|
data=dict(bundle_price=bundle_series[asset]),
|
||||||
|
index=bundle_series[asset].index
|
||||||
|
)
|
||||||
|
exchange_series = exchange.get_series_from_candles(
|
||||||
|
candles=candles[asset],
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
field='close'
|
||||||
|
)
|
||||||
|
exchange_df = pd.DataFrame(
|
||||||
|
data=dict(exchange_price=exchange_series),
|
||||||
|
index=exchange_series.index
|
||||||
|
)
|
||||||
|
|
||||||
|
df = exchange_df.join(bundle_df, how='left')
|
||||||
|
df['last_traded'] = df.index
|
||||||
|
df['asset'] = asset.symbol
|
||||||
|
df.set_index(['asset', 'last_traded'], inplace=True)
|
||||||
|
|
||||||
|
frames.append(df)
|
||||||
|
|
||||||
|
df = pd.concat(frames)
|
||||||
|
print('\n' + df_to_string(df))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_ingest_candles(self):
|
||||||
|
exchange_name = 'bitfinex'
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
bundle = ExchangeBundle(exchange)
|
||||||
|
assets = [exchange.get_asset('iot_btc')]
|
||||||
|
|
||||||
|
end_dt = pd.to_datetime('2017-10-20', utc=True)
|
||||||
|
bar_count = 100
|
||||||
|
|
||||||
|
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||||
|
candles = exchange.get_candles(
|
||||||
|
assets=assets,
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt,
|
||||||
|
bar_count=bar_count,
|
||||||
|
freq='1T'
|
||||||
|
)
|
||||||
|
|
||||||
|
writer = bundle.get_writer(start_dt, end_dt, data_frequency)
|
||||||
|
for asset in assets:
|
||||||
|
dates = [candle['last_traded'] for candle in candles[asset]]
|
||||||
|
|
||||||
|
values = dict()
|
||||||
|
for field in ['open', 'high', 'low', 'close', 'volume']:
|
||||||
|
values[field] = [candle[field] for candle in candles[asset]]
|
||||||
|
|
||||||
|
periods = bundle.get_calendar_periods_range(
|
||||||
|
start_dt, end_dt, data_frequency
|
||||||
|
)
|
||||||
|
df = pd.DataFrame(values, index=dates)
|
||||||
|
df = df.loc[periods].fillna(method='ffill')
|
||||||
|
|
||||||
|
# TODO: why do I get an extra bar?
|
||||||
|
bundle.ingest_df(
|
||||||
|
ohlcv_df=df,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
asset=asset,
|
||||||
|
writer=writer,
|
||||||
|
empty_rows_behavior='raise'
|
||||||
|
)
|
||||||
|
|
||||||
|
bundle_series = bundle.get_history_window_series(
|
||||||
|
assets=assets,
|
||||||
|
end_dt=end_dt,
|
||||||
|
bar_count=bar_count,
|
||||||
|
field='close',
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
reset_reader=True
|
||||||
|
)
|
||||||
|
df = pd.DataFrame(bundle_series)
|
||||||
|
print('\n' + df_to_string(df))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main_bundle_to_csv(self):
|
||||||
|
exchange_name = 'bitfinex'
|
||||||
|
data_frequency = 'minute'
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
asset = exchange.get_asset('neo_usd')
|
||||||
|
|
||||||
|
self._bundle_to_csv(
|
||||||
|
asset=asset,
|
||||||
|
exchange=exchange,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
filename='{}_{}_{}'.format(
|
||||||
|
exchange_name, data_frequency, asset.symbol
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def bundle_to_csv(self):
|
||||||
|
exchange_name = 'bitfinex'
|
||||||
|
data_frequency = 'minute'
|
||||||
|
period = '2017-10'
|
||||||
|
symbol = 'neo_btc'
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
asset = exchange.get_asset(symbol)
|
||||||
|
|
||||||
|
path = get_bcolz_chunk(
|
||||||
|
exchange_name=exchange.name,
|
||||||
|
symbol=asset.symbol,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
period=period
|
||||||
|
)
|
||||||
|
self._bundle_to_csv(
|
||||||
|
asset=asset,
|
||||||
|
exchange=exchange,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
path=path,
|
||||||
|
filename=period
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _bundle_to_csv(self, asset, exchange, data_frequency, filename,
|
||||||
|
path=None):
|
||||||
|
bundle = ExchangeBundle(exchange)
|
||||||
|
reader = bundle.get_reader(data_frequency, path=path)
|
||||||
|
|
||||||
|
start_dt = reader.first_trading_day
|
||||||
|
end_dt = reader.last_available_dt
|
||||||
|
|
||||||
|
if data_frequency == 'daily':
|
||||||
|
end_dt = end_dt - pd.Timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
|
arrays = None
|
||||||
|
try:
|
||||||
|
arrays = reader.load_raw_arrays(
|
||||||
|
sids=[asset.sid],
|
||||||
|
fields=['open', 'high', 'low', 'close', 'volume'],
|
||||||
|
start_dt=start_dt,
|
||||||
|
end_dt=end_dt
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.warn('skipping ctable for {} from {} to {}: {}'.format(
|
||||||
|
asset.symbol, start_dt, end_dt, e
|
||||||
|
))
|
||||||
|
|
||||||
|
periods = bundle.get_calendar_periods_range(
|
||||||
|
start_dt, end_dt, data_frequency
|
||||||
|
)
|
||||||
|
df = get_df_from_arrays(arrays, periods)
|
||||||
|
|
||||||
|
folder = os.path.join(
|
||||||
|
tempfile.gettempdir(), 'catalyst', exchange.name, asset.symbol
|
||||||
|
)
|
||||||
|
ensure_directory(folder)
|
||||||
|
|
||||||
|
path = os.path.join(folder, filename + '.csv')
|
||||||
|
|
||||||
|
log.info('creating csv file: {}'.format(path))
|
||||||
|
print('HEAD\n{}'.format(df.head(10)))
|
||||||
|
print('TAIL\n{}'.format(df.tail(10)))
|
||||||
|
df.to_csv(path)
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
from logbook import Logger
|
|
||||||
from mock import patch, sentinel
|
|
||||||
from catalyst.exchange.simple_clock import SimpleClock
|
|
||||||
from catalyst.utils.calendars.trading_calendar import days_at_time
|
|
||||||
from datetime import time
|
|
||||||
from collections import defaultdict
|
|
||||||
from catalyst.utils.calendars import get_calendar
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
log = Logger('ExchangeClockTestCase')
|
|
||||||
|
|
||||||
|
|
||||||
class ExchangeClockTestCase(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
cls.open_calendar = get_calendar("OPEN")
|
|
||||||
|
|
||||||
cls.sessions = pd.Timestamp.utcnow()
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.internal_clock = None
|
|
||||||
self.events = defaultdict(list)
|
|
||||||
|
|
||||||
def advance_clock(self, x):
|
|
||||||
"""Mock function for sleep. Advances the internal clock by 1 min"""
|
|
||||||
# The internal clock advance time must be 1 minute to match
|
|
||||||
# MinutesSimulationClock's update frequency
|
|
||||||
self.internal_clock += pd.Timedelta('1 min')
|
|
||||||
|
|
||||||
def get_clock(self, arg, *args, **kwargs):
|
|
||||||
"""Mock function for pandas.to_datetime which is used to query the
|
|
||||||
current time in RealtimeClock"""
|
|
||||||
assert arg == "now"
|
|
||||||
return self.internal_clock
|
|
||||||
|
|
||||||
def test_clock(self):
|
|
||||||
with patch('catalyst.exchange.simple_clock.pd.to_datetime') as to_dt, \
|
|
||||||
patch('catalyst.exchange.simple_clock.sleep') as sleep:
|
|
||||||
clock = SimpleClock(sessions=self.sessions)
|
|
||||||
to_dt.side_effect = self.get_clock
|
|
||||||
sleep.side_effect = self.advance_clock
|
|
||||||
start_time = pd.Timestamp.utcnow()
|
|
||||||
self.internal_clock = start_time
|
|
||||||
|
|
||||||
events = list(clock)
|
|
||||||
|
|
||||||
# Event 0 is SESSION_START which always happens at 00:00.
|
|
||||||
ts, event_type = events[1]
|
|
||||||
pass
|
|
||||||
@@ -1,47 +1,37 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest, \
|
||||||
|
DataPortalExchangeLive
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
from test_utils import rnd_history_date_days, rnd_bar_count
|
||||||
|
|
||||||
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.bitfinex.bitfinex import Bitfinex
|
from catalyst.exchange.bitfinex.bitfinex import Bitfinex
|
||||||
from catalyst.exchange.bittrex.bittrex import Bittrex
|
from catalyst.exchange.bittrex.bittrex import Bittrex
|
||||||
from catalyst.exchange.data_portal_exchange import DataPortalExchangeBacktest, \
|
from catalyst.exchange.exchange_utils import get_exchange_auth, \
|
||||||
DataPortalExchangeLive
|
get_common_assets
|
||||||
from catalyst.exchange.exchange_utils import get_exchange_auth
|
from catalyst.exchange.factory import get_exchange, get_exchanges
|
||||||
|
|
||||||
log = Logger('test_bitfinex')
|
log = Logger('test_bitfinex')
|
||||||
|
|
||||||
|
|
||||||
class ExchangeDataPortalTestCase:
|
class TestExchangeDataPortal:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
log.info('creating bitfinex exchange')
|
log.info('creating bitfinex exchange')
|
||||||
auth_bitfinex = get_exchange_auth('bitfinex')
|
exchanges = get_exchanges(['bitfinex', 'bittrex', 'poloniex'])
|
||||||
self.bitfinex = Bitfinex(
|
|
||||||
key=auth_bitfinex['key'],
|
|
||||||
secret=auth_bitfinex['secret'],
|
|
||||||
base_currency='usd'
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info('creating bittrex exchange')
|
|
||||||
auth_bitfinex = get_exchange_auth('bittrex')
|
|
||||||
self.bittrex = Bittrex(
|
|
||||||
key=auth_bitfinex['key'],
|
|
||||||
secret=auth_bitfinex['secret'],
|
|
||||||
base_currency='usd'
|
|
||||||
)
|
|
||||||
|
|
||||||
open_calendar = get_calendar('OPEN')
|
open_calendar = get_calendar('OPEN')
|
||||||
asset_finder = AssetFinderExchange()
|
asset_finder = AssetFinderExchange()
|
||||||
|
|
||||||
self.data_portal_live = DataPortalExchangeLive(
|
self.data_portal_live = DataPortalExchangeLive(
|
||||||
exchanges=dict(bitfinex=self.bitfinex, bittrex=self.bittrex),
|
exchanges=exchanges,
|
||||||
asset_finder=asset_finder,
|
asset_finder=asset_finder,
|
||||||
trading_calendar=open_calendar,
|
trading_calendar=open_calendar,
|
||||||
first_trading_day=pd.to_datetime('today', utc=True)
|
first_trading_day=pd.to_datetime('today', utc=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.data_portal_backtest = DataPortalExchangeBacktest(
|
self.data_portal_backtest = DataPortalExchangeBacktest(
|
||||||
exchanges=dict(bitfinex=self.bitfinex),
|
exchanges=exchanges,
|
||||||
asset_finder=asset_finder,
|
asset_finder=asset_finder,
|
||||||
trading_calendar=open_calendar,
|
trading_calendar=open_calendar,
|
||||||
first_trading_day=None # will set dynamically based on assets
|
first_trading_day=None # will set dynamically based on assets
|
||||||
@@ -106,3 +96,20 @@ class ExchangeDataPortalTestCase:
|
|||||||
assets, 'close', date, 'minute')
|
assets, 'close', date, 'minute')
|
||||||
log.info('found spot value {}'.format(value))
|
log.info('found spot value {}'.format(value))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_history_compare_exchanges(self):
|
||||||
|
exchanges = get_exchanges(['bittrex', 'bitfinex', 'poloniex'])
|
||||||
|
assets = get_common_assets(exchanges)
|
||||||
|
|
||||||
|
date = rnd_history_date_days()
|
||||||
|
bar_count = rnd_bar_count()
|
||||||
|
data = self.data_portal_backtest.get_history_window(
|
||||||
|
assets=assets,
|
||||||
|
end_dt=date,
|
||||||
|
bar_count=bar_count,
|
||||||
|
frequency='1d',
|
||||||
|
field='close',
|
||||||
|
data_frequency='daily'
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info('found history window: {}'.format(data))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from catalyst.exchange.exchange_utils import get_exchange_auth
|
|||||||
log = Logger('test_poloniex')
|
log = Logger('test_poloniex')
|
||||||
|
|
||||||
|
|
||||||
class PoloniexTestCase(BaseExchangeTestCase):
|
class TestPoloniex(BaseExchangeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
print ('creating poloniex object')
|
print ('creating poloniex object')
|
||||||
@@ -21,7 +21,7 @@ class PoloniexTestCase(BaseExchangeTestCase):
|
|||||||
|
|
||||||
def test_order(self):
|
def test_order(self):
|
||||||
log.info('creating order')
|
log.info('creating order')
|
||||||
asset = self.exchange.get_asset('neo_btc')
|
asset = self.exchange.get_asset('neos_btc')
|
||||||
order_id = self.exchange.order(
|
order_id = self.exchange.order(
|
||||||
asset=asset,
|
asset=asset,
|
||||||
limit_price=0.0005,
|
limit_price=0.0005,
|
||||||
@@ -33,7 +33,7 @@ class PoloniexTestCase(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('neos_btc')
|
||||||
orders = self.exchange.get_open_orders(asset)
|
orders = self.exchange.get_open_orders(asset)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ class PoloniexTestCase(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(
|
||||||
data_frequency='5m',
|
freq='5T',
|
||||||
assets=self.exchange.get_asset('neo_btc')
|
assets=self.exchange.get_asset('eth_btc')
|
||||||
)
|
)
|
||||||
ohlcv_neo_ubq = self.exchange.get_candles(
|
ohlcv_neo_ubq = self.exchange.get_candles(
|
||||||
data_frequency='5m',
|
freq='5T',
|
||||||
assets=[
|
assets=[
|
||||||
self.exchange.get_asset('neo_btc'),
|
self.exchange.get_asset('neos_btc'),
|
||||||
self.exchange.get_asset('ubq_btc')
|
self.exchange.get_asset('via_btc')
|
||||||
],
|
],
|
||||||
bar_count=14
|
bar_count=14
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
import importlib
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from catalyst import get_calendar
|
||||||
|
|
||||||
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex']
|
||||||
|
exchanges = dict((e, getattr(importlib.import_module(
|
||||||
|
'catalyst.exchange.{0}.{0}'.format(e)), e.capitalize()))
|
||||||
|
for e in EXCHANGE_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateChunks(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
|
|
||||||
|
def chunk_to_df(self, exchange_name, symbol, data_frequency, period):
|
||||||
|
|
||||||
|
exchange = get_exchange(exchange_name)
|
||||||
|
asset = exchange.get_asset(symbol)
|
||||||
|
|
||||||
|
filename = get_bcolz_chunk(
|
||||||
|
exchange_name=exchange_name,
|
||||||
|
symbol=symbol,
|
||||||
|
data_frequency=data_frequency,
|
||||||
|
period=period
|
||||||
|
)
|
||||||
|
|
||||||
|
reader = BcolzExchangeBarReader(rootdir=filename,
|
||||||
|
data_frequency=data_frequency)
|
||||||
|
|
||||||
|
# metadata = BcolzMinuteBarMetadata.read(filename)
|
||||||
|
|
||||||
|
start = reader.first_trading_day
|
||||||
|
end = reader.last_available_dt
|
||||||
|
|
||||||
|
if data_frequency == 'daily':
|
||||||
|
end = end - pd.Timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
|
print start, end, data_frequency
|
||||||
|
|
||||||
|
arrays = reader.load_raw_arrays(self.columns, start, end,
|
||||||
|
[asset.sid, ])
|
||||||
|
|
||||||
|
bundle = ExchangeBundle(exchange_name)
|
||||||
|
|
||||||
|
periods = bundle.get_calendar_periods_range(
|
||||||
|
start, end, data_frequency
|
||||||
|
)
|
||||||
|
|
||||||
|
return get_df_from_arrays(arrays, periods)
|
||||||
|
|
||||||
|
def plot_ohlcv(self, df):
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
|
||||||
|
# Plot the candlestick
|
||||||
|
candlestick2_ohlc(ax, df['open'], df['high'], df['low'], df['close'],
|
||||||
|
width=1, colorup='g', colordown='r', alpha=0.5)
|
||||||
|
|
||||||
|
# shift y-limits of the candlestick plot so that there is space
|
||||||
|
# at the bottom for the volume bar chart
|
||||||
|
pad = 0.25
|
||||||
|
yl = ax.get_ylim()
|
||||||
|
ax.set_ylim(yl[0] - (yl[1] - yl[0]) * pad, yl[1])
|
||||||
|
|
||||||
|
# Add a seconds axis for the volume overlay
|
||||||
|
ax2 = ax.twinx()
|
||||||
|
|
||||||
|
ax2.set_position(
|
||||||
|
matplotlib.transforms.Bbox([[0.125, 0.1], [0.9, 0.26]]))
|
||||||
|
|
||||||
|
# Plot the volume overlay
|
||||||
|
bc = volume_overlay(ax2, df['open'], df['close'], df['volume'],
|
||||||
|
colorup='g', alpha=0.5, width=1)
|
||||||
|
|
||||||
|
ax.xaxis.set_major_locator(ticker.MaxNLocator(6))
|
||||||
|
|
||||||
|
def mydate(x, pos):
|
||||||
|
try:
|
||||||
|
return df.index[int(x)]
|
||||||
|
except IndexError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
ax.xaxis.set_major_formatter(ticker.FuncFormatter(mydate))
|
||||||
|
plt.margins(0)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def plot(self, filename):
|
||||||
|
df = self.chunk_to_df(filename)
|
||||||
|
self.plot_ohlcv(df)
|
||||||
|
|
||||||
|
def to_csv(self, filename):
|
||||||
|
df = self.chunk_to_df(filename)
|
||||||
|
df.to_csv(os.path.basename(filename).split('.')[0] + '.csv')
|
||||||
|
|
||||||
|
|
||||||
|
v = ValidateChunks()
|
||||||
|
|
||||||
|
df = v.chunk_to_df(
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
symbol='eth_btc',
|
||||||
|
data_frequency='daily',
|
||||||
|
period='2016'
|
||||||
|
)
|
||||||
|
print(df.tail())
|
||||||
|
v.plot_ohlcv(df)
|
||||||
|
# v.plot(
|
||||||
|
# ex
|
||||||
|
# )
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def rnd_history_date_days(max_days=30):
|
||||||
|
now = pd.Timestamp.utcnow()
|
||||||
|
days = randint(0, max_days)
|
||||||
|
|
||||||
|
return now - timedelta(days=days)
|
||||||
|
|
||||||
|
|
||||||
|
def rnd_bar_count(max_bars=21):
|
||||||
|
now = pd.Timestamp.utcnow()
|
||||||
|
|
||||||
|
return randint(0, max_bars)
|
||||||
Reference in New Issue
Block a user