mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a3cc7e5fa |
@@ -14,6 +14,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
|||||||
from catalyst.exchange.utils.exchange_utils import delete_algo_folder
|
from catalyst.exchange.utils.exchange_utils import delete_algo_folder
|
||||||
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
|
||||||
|
from catalyst.utils.run_server import run_server
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__IPYTHON__
|
__IPYTHON__
|
||||||
@@ -505,6 +506,179 @@ def live(ctx,
|
|||||||
return perf
|
return perf
|
||||||
|
|
||||||
|
|
||||||
|
@main.command(name='serve-live')
|
||||||
|
@click.option(
|
||||||
|
'-f',
|
||||||
|
'--algofile',
|
||||||
|
default=None,
|
||||||
|
type=click.File('r'),
|
||||||
|
help='The file that contains the algorithm to run.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--capital-base',
|
||||||
|
type=float,
|
||||||
|
show_default=True,
|
||||||
|
help='The amount of capital (in base_currency) allocated to trading.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-t',
|
||||||
|
'--algotext',
|
||||||
|
help='The algorithm script to run.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-D',
|
||||||
|
'--define',
|
||||||
|
multiple=True,
|
||||||
|
help="Define a name to be bound in the namespace before executing"
|
||||||
|
" the algotext. For example '-Dname=value'. The value may be"
|
||||||
|
" any python expression. These are evaluated in order so they"
|
||||||
|
" may refer to previously defined names.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-o',
|
||||||
|
'--output',
|
||||||
|
default='-',
|
||||||
|
metavar='FILENAME',
|
||||||
|
show_default=True,
|
||||||
|
help="The location to write the perf data. If this is '-' the perf will"
|
||||||
|
" be written to stdout.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--print-algo/--no-print-algo',
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
help='Print the algorithm to stdout.',
|
||||||
|
)
|
||||||
|
@ipython_only(click.option(
|
||||||
|
'--local-namespace/--no-local-namespace',
|
||||||
|
is_flag=True,
|
||||||
|
default=None,
|
||||||
|
help='Should the algorithm methods be resolved in the local namespace.'
|
||||||
|
))
|
||||||
|
@click.option(
|
||||||
|
'-x',
|
||||||
|
'--exchange-name',
|
||||||
|
help='The name of the targeted exchange.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-n',
|
||||||
|
'--algo-namespace',
|
||||||
|
help='A label assigned to the algorithm for data storage purposes.'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-c',
|
||||||
|
'--base-currency',
|
||||||
|
help='The base currency used to calculate statistics '
|
||||||
|
'(e.g. usd, btc, eth).',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-e',
|
||||||
|
'--end',
|
||||||
|
type=Date(tz='utc', as_timestamp=True),
|
||||||
|
help='An optional end date at which to stop the execution.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--live-graph/--no-live-graph',
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
help='Display live graph.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--simulate-orders/--no-simulate-orders',
|
||||||
|
is_flag=True,
|
||||||
|
default=True,
|
||||||
|
help='Simulating orders enable the paper trading mode. No orders will be '
|
||||||
|
'sent to the exchange unless set to false.',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--auth-aliases',
|
||||||
|
default=None,
|
||||||
|
help='Authentication file aliases for the specified exchanges. By default,'
|
||||||
|
'each exchange uses the "auth.json" file in the exchange folder. '
|
||||||
|
'Specifying an "auth2" alias would use "auth2.json". It should be '
|
||||||
|
'specified like this: "[exchange_name],[alias],..." For example, '
|
||||||
|
'"binance,auth2" or "binance,auth2,bittrex,auth2".',
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def serve_live(ctx,
|
||||||
|
algofile,
|
||||||
|
capital_base,
|
||||||
|
algotext,
|
||||||
|
define,
|
||||||
|
output,
|
||||||
|
print_algo,
|
||||||
|
local_namespace,
|
||||||
|
exchange_name,
|
||||||
|
algo_namespace,
|
||||||
|
base_currency,
|
||||||
|
end,
|
||||||
|
live_graph,
|
||||||
|
auth_aliases,
|
||||||
|
simulate_orders):
|
||||||
|
"""Trade live with the given algorithm on the server.
|
||||||
|
"""
|
||||||
|
if (algotext is not None) == (algofile is not None):
|
||||||
|
ctx.fail(
|
||||||
|
"must specify exactly one of '-f' / '--algofile' or"
|
||||||
|
" '-t' / '--algotext'",
|
||||||
|
)
|
||||||
|
|
||||||
|
if exchange_name is None:
|
||||||
|
ctx.fail("must specify an exchange name '-x'")
|
||||||
|
|
||||||
|
if algo_namespace is None:
|
||||||
|
ctx.fail("must specify an algorithm name '-n' in live execution mode")
|
||||||
|
|
||||||
|
if base_currency is None:
|
||||||
|
ctx.fail("must specify a base currency '-c' in live execution mode")
|
||||||
|
|
||||||
|
if capital_base is None:
|
||||||
|
ctx.fail("must specify a capital base with '--capital-base'")
|
||||||
|
|
||||||
|
if simulate_orders:
|
||||||
|
click.echo('Running in paper trading mode.', sys.stdout)
|
||||||
|
|
||||||
|
else:
|
||||||
|
click.echo('Running in live trading mode.', sys.stdout)
|
||||||
|
|
||||||
|
perf = run_server(
|
||||||
|
initialize=None,
|
||||||
|
handle_data=None,
|
||||||
|
before_trading_start=None,
|
||||||
|
analyze=None,
|
||||||
|
algofile=algofile,
|
||||||
|
algotext=algotext,
|
||||||
|
defines=define,
|
||||||
|
data_frequency=None,
|
||||||
|
capital_base=capital_base,
|
||||||
|
data=None,
|
||||||
|
bundle=None,
|
||||||
|
bundle_timestamp=None,
|
||||||
|
start=None,
|
||||||
|
end=end,
|
||||||
|
output=output,
|
||||||
|
print_algo=print_algo,
|
||||||
|
local_namespace=local_namespace,
|
||||||
|
environ=os.environ,
|
||||||
|
live=True,
|
||||||
|
exchange=exchange_name,
|
||||||
|
algo_namespace=algo_namespace,
|
||||||
|
base_currency=base_currency,
|
||||||
|
live_graph=live_graph,
|
||||||
|
analyze_live=None,
|
||||||
|
simulate_orders=simulate_orders,
|
||||||
|
auth_aliases=auth_aliases,
|
||||||
|
stats_output=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if output == '-':
|
||||||
|
click.echo(str(perf), sys.stdout)
|
||||||
|
elif output != os.devnull: # make the catalyst magic not write any data
|
||||||
|
perf.to_pickle(output)
|
||||||
|
|
||||||
|
return perf
|
||||||
|
|
||||||
|
|
||||||
@main.command(name='ingest-exchange')
|
@main.command(name='ingest-exchange')
|
||||||
@click.option(
|
@click.option(
|
||||||
'-x',
|
'-x',
|
||||||
|
|||||||
+10
-59
@@ -433,7 +433,7 @@ cdef class TradingPair(Asset):
|
|||||||
'taker',
|
'taker',
|
||||||
'trading_state',
|
'trading_state',
|
||||||
'data_source',
|
'data_source',
|
||||||
'decimals',
|
'decimals'
|
||||||
})
|
})
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
object symbol,
|
object symbol,
|
||||||
@@ -455,7 +455,7 @@ cdef class TradingPair(Asset):
|
|||||||
float taker=0.0025,
|
float taker=0.0025,
|
||||||
float lot=0,
|
float lot=0,
|
||||||
int decimals = 8,
|
int decimals = 8,
|
||||||
int trading_state=1,
|
int trading_state=0,
|
||||||
object data_source='catalyst'):
|
object data_source='catalyst'):
|
||||||
"""
|
"""
|
||||||
Replicates the Asset constructor with some built-in conventions
|
Replicates the Asset constructor with some built-in conventions
|
||||||
@@ -600,51 +600,14 @@ cdef class TradingPair(Asset):
|
|||||||
cpdef to_dict(self):
|
cpdef to_dict(self):
|
||||||
"""
|
"""
|
||||||
Convert to a python dict.
|
Convert to a python dict.
|
||||||
|
|
||||||
Repeat constructor params:
|
|
||||||
object symbol,
|
|
||||||
object exchange,
|
|
||||||
object start_date=None,
|
|
||||||
object asset_name=None,
|
|
||||||
int sid=0,
|
|
||||||
float leverage=1.0,
|
|
||||||
object end_daily=None,
|
|
||||||
object end_minute=None,
|
|
||||||
object end_date=None,
|
|
||||||
object exchange_symbol=None,
|
|
||||||
object first_traded=None,
|
|
||||||
object auto_close_date=None,
|
|
||||||
object exchange_full=None,
|
|
||||||
float min_trade_size=0.0001,
|
|
||||||
float max_trade_size=1000000,
|
|
||||||
float maker=0.0015,
|
|
||||||
float taker=0.0025,
|
|
||||||
float lot=0,
|
|
||||||
int decimals = 8,
|
|
||||||
int trading_state=1,
|
|
||||||
object data_source='catalyst',
|
|
||||||
"""
|
"""
|
||||||
trading_pair_dict = dict(
|
#TODO: missing fields
|
||||||
symbol=self.symbol,
|
super_dict = super(TradingPair, self).to_dict()
|
||||||
exchange=self.exchange,
|
super_dict['end_daily'] = self.end_daily
|
||||||
start_date=self.start_date,
|
super_dict['end_minute'] = self.end_minute
|
||||||
asset_name=self.asset_name,
|
super_dict['leverage'] = self.leverage
|
||||||
leverage=self.leverage,
|
super_dict['min_trade_size'] = self.min_trade_size
|
||||||
end_daily=self.end_daily,
|
return super_dict
|
||||||
end_minute=self.end_minute,
|
|
||||||
end_date=self.end_date,
|
|
||||||
exchange_symbol=self.exchange_symbol,
|
|
||||||
exchange_full=self.exchange_full,
|
|
||||||
min_trade_size=self.min_trade_size,
|
|
||||||
max_trade_size=self.max_trade_size,
|
|
||||||
maker=self.maker,
|
|
||||||
taker=self.taker,
|
|
||||||
lot=self.lot,
|
|
||||||
decimals=self.decimals,
|
|
||||||
trading_state=self.trading_state,
|
|
||||||
data_source=self.data_source,
|
|
||||||
)
|
|
||||||
return trading_pair_dict
|
|
||||||
|
|
||||||
def is_exchange_open(self, dt_minute):
|
def is_exchange_open(self, dt_minute):
|
||||||
"""
|
"""
|
||||||
@@ -660,16 +623,6 @@ cdef class TradingPair(Asset):
|
|||||||
#TODO: make more dymanic to catch holds
|
#TODO: make more dymanic to catch holds
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_end_date(self, dt, data_frequency):
|
|
||||||
if data_frequency == 'minute':
|
|
||||||
self.end_minute = dt
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.end_daily = dt
|
|
||||||
|
|
||||||
def set_start_date(self, dt):
|
|
||||||
self.start_date = dt
|
|
||||||
|
|
||||||
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
|
||||||
@@ -693,9 +646,7 @@ cdef class TradingPair(Asset):
|
|||||||
self.lot,
|
self.lot,
|
||||||
self.decimals,
|
self.decimals,
|
||||||
self.taker,
|
self.taker,
|
||||||
self.maker,
|
self.maker))
|
||||||
self.trading_state,
|
|
||||||
self.data_source))
|
|
||||||
|
|
||||||
def make_asset_array(int size, Asset asset):
|
def make_asset_array(int size, Asset asset):
|
||||||
cdef np.ndarray out = np.empty([size], dtype=object)
|
cdef np.ndarray out = np.empty([size], dtype=object)
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO))
|
|||||||
|
|
||||||
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
||||||
'{exchange}/symbols.json'
|
'{exchange}/symbols.json'
|
||||||
EXCHANGE_CONFIG_URL = 'https://s3.amazonaws.com/enigmaco/exchanges/' \
|
|
||||||
'{exchange}/config.json'
|
|
||||||
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M'
|
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M'
|
||||||
DATE_FORMAT = '%Y-%m-%d'
|
DATE_FORMAT = '%Y-%m-%d'
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import pandas as pd
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import (record, symbol, order_target_percent,
|
from catalyst.api import (record, symbol, order_target_percent,)
|
||||||
get_open_orders)
|
|
||||||
from catalyst.exchange.utils.stats_utils import extract_transactions
|
from catalyst.exchange.utils.stats_utils import extract_transactions
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
NAMESPACE = 'dual_moving_average'
|
||||||
@@ -20,8 +19,8 @@ def initialize(context):
|
|||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
# define the windows for the moving averages
|
# define the windows for the moving averages
|
||||||
short_window = 2
|
short_window = 50
|
||||||
long_window = 3
|
long_window = 200
|
||||||
|
|
||||||
# Skip as many bars as long_window to properly compute the average
|
# Skip as many bars as long_window to properly compute the average
|
||||||
context.i += 1
|
context.i += 1
|
||||||
@@ -63,7 +62,7 @@ def handle_data(context, data):
|
|||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
# Since we are using limit orders, some orders may not execute immediately
|
||||||
# we wait until all orders are executed before considering more trades.
|
# we wait until all orders are executed before considering more trades.
|
||||||
orders = get_open_orders(context.asset)
|
orders = context.blotter.open_orders
|
||||||
if len(orders) > 0:
|
if len(orders) > 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -150,27 +149,16 @@ def analyze(context, perf):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=1000,
|
capital_base=1000,
|
||||||
data_frequency='minute',
|
data_frequency='minute',
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='bitfinex',
|
exchange_name='bitfinex',
|
||||||
algo_namespace=NAMESPACE,
|
algo_namespace=NAMESPACE,
|
||||||
base_currency='usd',
|
base_currency='usd',
|
||||||
simulate_orders=True,
|
start=pd.to_datetime('2017-9-22', utc=True),
|
||||||
live=True,
|
end=pd.to_datetime('2017-9-23', utc=True),
|
||||||
)
|
)
|
||||||
# run_algorithm(
|
|
||||||
# capital_base=1000,
|
|
||||||
# data_frequency='minute',
|
|
||||||
# initialize=initialize,
|
|
||||||
# handle_data=handle_data,
|
|
||||||
# analyze=analyze,
|
|
||||||
# exchange_name='bitfinex',
|
|
||||||
# algo_namespace=NAMESPACE,
|
|
||||||
# base_currency='usd',
|
|
||||||
# start=pd.to_datetime('2017-9-22', utc=True),
|
|
||||||
# end=pd.to_datetime('2017-9-23', utc=True),
|
|
||||||
# )
|
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ def initialize(context):
|
|||||||
# parameters or values you're going to use.
|
# parameters or values you're going to use.
|
||||||
|
|
||||||
# In our example, we're looking at Neo in Ether.
|
# In our example, we're looking at Neo in Ether.
|
||||||
context.market = symbol('eth_btc')
|
context.market = symbol('bnb_eth')
|
||||||
context.base_price = None
|
context.base_price = None
|
||||||
context.current_day = None
|
context.current_day = None
|
||||||
|
|
||||||
context.RSI_OVERSOLD = 55
|
context.RSI_OVERSOLD = 60
|
||||||
context.RSI_OVERBOUGHT = 60
|
context.RSI_OVERBOUGHT = 70
|
||||||
context.CANDLE_SIZE = '15T'
|
context.CANDLE_SIZE = '15T'
|
||||||
|
|
||||||
context.start_time = time.time()
|
context.start_time = time.time()
|
||||||
@@ -248,14 +248,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
if live:
|
if live:
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.03,
|
capital_base=0.1,
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
analyze=analyze,
|
analyze=analyze,
|
||||||
exchange_name='poloniex',
|
exchange_name='binance',
|
||||||
live=True,
|
live=True,
|
||||||
algo_namespace=NAMESPACE,
|
algo_namespace=NAMESPACE,
|
||||||
base_currency='btc',
|
base_currency='eth',
|
||||||
live_graph=False,
|
live_graph=False,
|
||||||
simulate_orders=False,
|
simulate_orders=False,
|
||||||
stats_output=None,
|
stats_output=None,
|
||||||
@@ -274,7 +274,7 @@ if __name__ == '__main__':
|
|||||||
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
|
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
|
||||||
# --data-frequency minute --capital-base 10000
|
# --data-frequency minute --capital-base 10000
|
||||||
run_algorithm(
|
run_algorithm(
|
||||||
capital_base=0.1,
|
capital_base=0.035,
|
||||||
data_frequency='minute',
|
data_frequency='minute',
|
||||||
initialize=initialize,
|
initialize=initialize,
|
||||||
handle_data=handle_data,
|
handle_data=handle_data,
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import six
|
import six
|
||||||
from catalyst.assets._assets import TradingPair
|
from ccxt import InvalidOrder, NetworkError, \
|
||||||
from redo import retry
|
ExchangeError
|
||||||
|
from logbook import Logger
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
from catalyst.algorithm import MarketOrder
|
from catalyst.algorithm import MarketOrder
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.exchange import Exchange
|
from catalyst.exchange.exchange import Exchange
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
||||||
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
|
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
|
||||||
UnsupportedHistoryFrequencyError, \
|
|
||||||
ExchangeNotFoundError, CreateOrderError, InvalidHistoryTimeframeError, \
|
ExchangeNotFoundError, CreateOrderError, InvalidHistoryTimeframeError, \
|
||||||
MarketsNotFoundError, InvalidMarketError
|
UnsupportedHistoryFrequencyError
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
||||||
from catalyst.exchange.utils.ccxt_utils import get_exchange_config
|
from catalyst.exchange.utils.exchange_utils import mixin_market_params, \
|
||||||
|
get_exchange_folder, get_catalyst_symbol, \
|
||||||
|
get_exchange_auth
|
||||||
from catalyst.exchange.utils.datetime_utils import from_ms_timestamp, \
|
from catalyst.exchange.utils.datetime_utils import from_ms_timestamp, \
|
||||||
get_epoch, \
|
get_epoch, \
|
||||||
get_periods_range
|
get_periods_range
|
||||||
from catalyst.exchange.utils.exchange_utils import get_catalyst_symbol
|
|
||||||
from catalyst.finance.order import Order, ORDER_STATUS
|
from catalyst.finance.order import Order, ORDER_STATUS
|
||||||
from catalyst.finance.transaction import Transaction
|
from catalyst.finance.transaction import Transaction
|
||||||
from ccxt import InvalidOrder, NetworkError, \
|
|
||||||
ExchangeError
|
|
||||||
from logbook import Logger
|
|
||||||
from six import string_types
|
|
||||||
|
|
||||||
log = Logger('CCXT', level=LOG_LEVEL)
|
log = Logger('CCXT', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ SUPPORTED_EXCHANGES = dict(
|
|||||||
|
|
||||||
class CCXT(Exchange):
|
class CCXT(Exchange):
|
||||||
def __init__(self, exchange_name, key,
|
def __init__(self, exchange_name, key,
|
||||||
secret, password, base_currency, config=None):
|
secret, password, base_currency):
|
||||||
log.debug(
|
log.debug(
|
||||||
'finding {} in CCXT exchanges:\n{}'.format(
|
'finding {} in CCXT exchanges:\n{}'.format(
|
||||||
exchange_name, ccxt.exchanges
|
exchange_name, ccxt.exchanges
|
||||||
@@ -63,8 +64,6 @@ class CCXT(Exchange):
|
|||||||
'password': password,
|
'password': password,
|
||||||
})
|
})
|
||||||
self.api.enableRateLimit = True
|
self.api.enableRateLimit = True
|
||||||
self.has = self.api.has
|
|
||||||
self.fees = self.api.fees
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ExchangeNotFoundError(exchange_name=exchange_name)
|
raise ExchangeNotFoundError(exchange_name=exchange_name)
|
||||||
@@ -72,7 +71,6 @@ class CCXT(Exchange):
|
|||||||
self._symbol_maps = [None, None]
|
self._symbol_maps = [None, None]
|
||||||
|
|
||||||
self.name = exchange_name
|
self.name = exchange_name
|
||||||
self.assets = []
|
|
||||||
|
|
||||||
self.base_currency = base_currency
|
self.base_currency = base_currency
|
||||||
self.transactions = defaultdict(list)
|
self.transactions = defaultdict(list)
|
||||||
@@ -84,123 +82,97 @@ class CCXT(Exchange):
|
|||||||
self._common_symbols = dict()
|
self._common_symbols = dict()
|
||||||
|
|
||||||
self.bundle = ExchangeBundle(self.name)
|
self.bundle = ExchangeBundle(self.name)
|
||||||
|
self.markets = None
|
||||||
self._is_init = False
|
self._is_init = False
|
||||||
self._config = config
|
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
if self._is_init:
|
if self._is_init:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._config is None:
|
exchange_folder = get_exchange_folder(self.name)
|
||||||
self._config = get_exchange_config(self.name)
|
filename = os.path.join(exchange_folder, 'cctx_markets.json')
|
||||||
log.debug(
|
|
||||||
'got exchange config {}:\n{}'.format(
|
if os.path.exists(filename):
|
||||||
self.name, self._config
|
timestamp = os.path.getmtime(filename)
|
||||||
|
dt = pd.to_datetime(timestamp, unit='s', utc=True)
|
||||||
|
|
||||||
|
if dt >= pd.Timestamp.utcnow().floor('1D'):
|
||||||
|
with open(filename) as f:
|
||||||
|
self.markets = json.load(f)
|
||||||
|
|
||||||
|
log.debug('loaded markets for {}'.format(self.name))
|
||||||
|
|
||||||
|
if self.markets is None:
|
||||||
|
try:
|
||||||
|
markets_symbols = self.api.load_markets()
|
||||||
|
log.debug(
|
||||||
|
'fetching {} markets:\n{}'.format(
|
||||||
|
self.name, markets_symbols
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
self.markets = self.api.fetch_markets()
|
||||||
|
with open(filename, 'w+') as f:
|
||||||
|
json.dump(self.markets, f, indent=4)
|
||||||
|
|
||||||
|
except (ExchangeError, NetworkError) as e:
|
||||||
|
log.warn(
|
||||||
|
'unable to fetch markets {}: {}'.format(
|
||||||
|
self.name, e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise ExchangeRequestError(error=e)
|
||||||
|
|
||||||
self.load_assets()
|
self.load_assets()
|
||||||
self._is_init = True
|
self._is_init = True
|
||||||
|
|
||||||
def load_assets(self):
|
@staticmethod
|
||||||
if self._config is None:
|
def find_exchanges(features=None, is_authenticated=False):
|
||||||
raise ValueError('Exchange config not available.')
|
ccxt_features = []
|
||||||
|
if features is not None:
|
||||||
|
for feature in features:
|
||||||
|
if not feature.endswith('Bundle'):
|
||||||
|
ccxt_features.append(feature)
|
||||||
|
|
||||||
self.assets = []
|
exchange_names = []
|
||||||
for asset_dict in self._config['assets']:
|
for exchange_name in ccxt.exchanges:
|
||||||
asset = TradingPair(**asset_dict)
|
if is_authenticated:
|
||||||
self.assets.append(asset)
|
exchange_auth = get_exchange_auth(exchange_name)
|
||||||
|
|
||||||
def _fetch_markets(self):
|
has_auth = (exchange_auth['key'] != ''
|
||||||
markets_symbols = self.api.load_markets()
|
and exchange_auth['secret'] != '')
|
||||||
log.debug(
|
|
||||||
'fetching {} markets:\n{}'.format(
|
|
||||||
self.name, markets_symbols
|
|
||||||
)
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
markets = self.api.fetch_markets()
|
|
||||||
|
|
||||||
except NetworkError as e:
|
if not has_auth:
|
||||||
raise ExchangeRequestError(error=e)
|
continue
|
||||||
|
|
||||||
if not markets:
|
log.debug('loading exchange: {}'.format(exchange_name))
|
||||||
raise MarketsNotFoundError(
|
exchange = getattr(ccxt, exchange_name)()
|
||||||
exchange=self.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
for market in markets:
|
if ccxt_features is None:
|
||||||
if 'id' not in market:
|
has_feature = True
|
||||||
raise InvalidMarketError(
|
|
||||||
exchange=self.name,
|
|
||||||
market=market,
|
|
||||||
)
|
|
||||||
return markets
|
|
||||||
|
|
||||||
def create_exchange_config(self):
|
else:
|
||||||
config = dict(
|
try:
|
||||||
name=self.name,
|
has_feature = all(
|
||||||
features=[feature for feature in self.has if self.has[feature]]
|
[exchange.has[feature] for feature in ccxt_features]
|
||||||
)
|
)
|
||||||
markets = retry(
|
|
||||||
action=self._fetch_markets,
|
|
||||||
attempts=5,
|
|
||||||
sleeptime=5,
|
|
||||||
retry_exceptions=(ExchangeRequestError,),
|
|
||||||
cleanup=lambda: log.warn(
|
|
||||||
'fetching markets again for {}'.format(self.name)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
config['assets'] = []
|
except Exception:
|
||||||
for market in markets:
|
has_feature = False
|
||||||
asset = self.create_trading_pair(market=market)
|
|
||||||
config['assets'].append(asset)
|
|
||||||
|
|
||||||
return config
|
if has_feature:
|
||||||
|
try:
|
||||||
|
log.info('initializing {}'.format(exchange_name))
|
||||||
|
exchange_names.append(exchange_name)
|
||||||
|
|
||||||
def create_trading_pair(self, market, start_dt=None, end_dt=None,
|
except Exception as e:
|
||||||
leverage=1, end_daily=None, end_minute=None):
|
log.warn(
|
||||||
"""
|
'unable to initialize exchange {}: {}'.format(
|
||||||
Creating a TradingPair from market and asset data.
|
exchange_name, e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
Parameters
|
return exchange_names
|
||||||
----------
|
|
||||||
market: dict[str, Object]
|
|
||||||
start_dt
|
|
||||||
end_dt
|
|
||||||
leverage
|
|
||||||
end_daily
|
|
||||||
end_minute
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
params = dict(
|
|
||||||
exchange=self.name,
|
|
||||||
data_source='catalyst',
|
|
||||||
exchange_symbol=market['id'],
|
|
||||||
symbol=get_catalyst_symbol(market),
|
|
||||||
start_date=start_dt,
|
|
||||||
end_date=end_dt,
|
|
||||||
leverage=leverage,
|
|
||||||
asset_name=market['symbol'],
|
|
||||||
end_daily=end_daily,
|
|
||||||
end_minute=end_minute,
|
|
||||||
)
|
|
||||||
self.apply_conditional_market_params(params, market)
|
|
||||||
|
|
||||||
return TradingPair(**params)
|
|
||||||
|
|
||||||
def load_assets(self):
|
|
||||||
if self._config is None or 'error' in self._config:
|
|
||||||
raise ValueError('Exchange config not available.')
|
|
||||||
|
|
||||||
self.assets = []
|
|
||||||
for asset_dict in self._config['assets']:
|
|
||||||
asset = TradingPair(**asset_dict)
|
|
||||||
self.assets.append(asset)
|
|
||||||
|
|
||||||
def account(self):
|
def account(self):
|
||||||
return None
|
return None
|
||||||
@@ -232,6 +204,42 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
return frequencies
|
return frequencies
|
||||||
|
|
||||||
|
def get_market(self, symbol):
|
||||||
|
"""
|
||||||
|
The CCXT market.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
symbol:
|
||||||
|
The CCXT symbol.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[str, Object]
|
||||||
|
|
||||||
|
"""
|
||||||
|
s = self.get_symbol(symbol)
|
||||||
|
market = next(
|
||||||
|
(market for market in self.markets if market['symbol'] == s),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
return market
|
||||||
|
|
||||||
|
def substitute_currency_code(self, currency, source='catalyst'):
|
||||||
|
if source == 'catalyst':
|
||||||
|
currency = currency.upper()
|
||||||
|
|
||||||
|
key = self.api.common_currency_code(currency)
|
||||||
|
self._common_symbols[key] = currency.lower()
|
||||||
|
return key
|
||||||
|
|
||||||
|
else:
|
||||||
|
if currency in self._common_symbols:
|
||||||
|
return self._common_symbols[currency]
|
||||||
|
|
||||||
|
else:
|
||||||
|
return currency.lower()
|
||||||
|
|
||||||
def get_symbol(self, asset_or_symbol, source='catalyst'):
|
def get_symbol(self, asset_or_symbol, source='catalyst'):
|
||||||
"""
|
"""
|
||||||
The CCXT symbol.
|
The CCXT symbol.
|
||||||
@@ -249,7 +257,13 @@ class CCXT(Exchange):
|
|||||||
if source == 'ccxt':
|
if source == 'ccxt':
|
||||||
if isinstance(asset_or_symbol, string_types):
|
if isinstance(asset_or_symbol, string_types):
|
||||||
parts = asset_or_symbol.split('/')
|
parts = asset_or_symbol.split('/')
|
||||||
return '{}_{}'.format(parts[0].lower(), parts[1].lower())
|
base_currency = self.substitute_currency_code(
|
||||||
|
parts[0], source
|
||||||
|
)
|
||||||
|
quote_currency = self.substitute_currency_code(
|
||||||
|
parts[1], source
|
||||||
|
)
|
||||||
|
return '{}_{}'.format(base_currency, quote_currency)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return asset_or_symbol.symbol
|
return asset_or_symbol.symbol
|
||||||
@@ -260,7 +274,13 @@ class CCXT(Exchange):
|
|||||||
) else asset_or_symbol.symbol
|
) else asset_or_symbol.symbol
|
||||||
|
|
||||||
parts = symbol.split('_')
|
parts = symbol.split('_')
|
||||||
return '{}/{}'.format(parts[0].upper(), parts[1].upper())
|
base_currency = self.substitute_currency_code(
|
||||||
|
parts[0], source
|
||||||
|
)
|
||||||
|
quote_currency = self.substitute_currency_code(
|
||||||
|
parts[1], source
|
||||||
|
)
|
||||||
|
return '{}/{}'.format(base_currency, quote_currency)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def map_frequency(value, source='ccxt', raise_error=True):
|
def map_frequency(value, source='ccxt', raise_error=True):
|
||||||
@@ -460,53 +480,144 @@ class CCXT(Exchange):
|
|||||||
except ExchangeSymbolsNotFound:
|
except ExchangeSymbolsNotFound:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def apply_conditional_market_params(self, params, market):
|
def get_asset_defs(self, market):
|
||||||
"""
|
"""
|
||||||
Applies a CCXT market dict to parameters of TradingPair init.
|
The local and Catalyst definitions of the specified market.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
params: dict[Object]
|
market: dict[str, Object]
|
||||||
market: dict[Object]
|
The CCXT market dicts.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[str, Object]
|
||||||
|
The asset definition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
asset_defs = []
|
||||||
|
|
||||||
|
for is_local in (False, True):
|
||||||
|
asset_def = self.get_asset_def(market, is_local)
|
||||||
|
asset_defs.append((asset_def, is_local))
|
||||||
|
|
||||||
|
return asset_defs
|
||||||
|
|
||||||
|
def get_asset_def(self, market, is_local=False):
|
||||||
|
"""
|
||||||
|
The asset definition (in symbols.json files) corresponding
|
||||||
|
to the the specified market.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
market: dict[str, Object]
|
||||||
|
The CCXT market dict.
|
||||||
|
is_local
|
||||||
|
Whether to search in local or Catalyst asset definitions.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[str, Object]
|
||||||
|
The asset definition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
exchange_symbol = market['id']
|
||||||
|
|
||||||
|
symbol_map = self._fetch_symbol_map(is_local)
|
||||||
|
if symbol_map is not None:
|
||||||
|
assets_lower = {k.lower(): v for k, v in symbol_map.items()}
|
||||||
|
key = exchange_symbol.lower()
|
||||||
|
|
||||||
|
asset = assets_lower[key] if key in assets_lower else None
|
||||||
|
if asset is not None:
|
||||||
|
return asset
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_trading_pair(self, market, asset_def=None, is_local=False):
|
||||||
|
"""
|
||||||
|
Creating a TradingPair from market and asset data.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
market: dict[str, Object]
|
||||||
|
asset_def: dict[str, Object]
|
||||||
|
is_local: bool
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: make this more externalized / configurable
|
data_source = 'local' if is_local else 'catalyst'
|
||||||
# Consider representing in some type of JSON structure
|
params = dict(
|
||||||
if 'active' in market:
|
exchange=self.name,
|
||||||
params['trading_state'] = 1 if market['active'] else 0
|
data_source=data_source,
|
||||||
|
exchange_symbol=market['id'],
|
||||||
|
)
|
||||||
|
mixin_market_params(self.name, params, market)
|
||||||
|
|
||||||
|
if asset_def is not None:
|
||||||
|
params['symbol'] = asset_def['symbol']
|
||||||
|
|
||||||
|
params['start_date'] = asset_def['start_date'] \
|
||||||
|
if 'start_date' in asset_def else None
|
||||||
|
|
||||||
|
params['end_date'] = asset_def['end_date'] \
|
||||||
|
if 'end_date' in asset_def else None
|
||||||
|
|
||||||
|
params['leverage'] = asset_def['leverage'] \
|
||||||
|
if 'leverage' in asset_def else 1.0
|
||||||
|
|
||||||
|
params['asset_name'] = asset_def['asset_name'] \
|
||||||
|
if 'asset_name' in asset_def else None
|
||||||
|
|
||||||
|
params['end_daily'] = asset_def['end_daily'] \
|
||||||
|
if 'end_daily' in asset_def \
|
||||||
|
and asset_def['end_daily'] != 'N/A' else None
|
||||||
|
|
||||||
|
params['end_minute'] = asset_def['end_minute'] \
|
||||||
|
if 'end_minute' in asset_def \
|
||||||
|
and asset_def['end_minute'] != 'N/A' else None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
params['trading_state'] = 1
|
params['symbol'] = get_catalyst_symbol(market)
|
||||||
|
# TODO: add as an optional column
|
||||||
|
params['leverage'] = 1.0
|
||||||
|
|
||||||
if 'lot' in market:
|
return TradingPair(**params)
|
||||||
params['min_trade_size'] = market['lot']
|
|
||||||
params['lot'] = market['lot']
|
|
||||||
|
|
||||||
if self.name == 'bitfinex':
|
def load_assets(self):
|
||||||
params['maker'] = 0.001
|
log.debug('loading assets for {}'.format(self.name))
|
||||||
params['taker'] = 0.002
|
self.assets = []
|
||||||
|
|
||||||
elif 'maker' in market and 'taker' in market \
|
for market in self.markets:
|
||||||
and market['maker'] is not None \
|
if 'id' not in market:
|
||||||
and market['taker'] is not None:
|
log.warn('invalid market: {}'.format(market))
|
||||||
params['maker'] = market['maker']
|
continue
|
||||||
params['taker'] = market['taker']
|
|
||||||
|
|
||||||
else:
|
asset_defs = self.get_asset_defs(market)
|
||||||
# TODO: default commission, make configurable
|
|
||||||
params['maker'] = 0.0015
|
|
||||||
params['taker'] = 0.0025
|
|
||||||
|
|
||||||
info = market['info'] if 'info' in market else None
|
asset = None
|
||||||
if info:
|
for asset_def in asset_defs:
|
||||||
if 'minimum_order_size' in info:
|
if asset_def[0] is not None or not asset_defs[1]:
|
||||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
try:
|
||||||
|
asset = self.create_trading_pair(
|
||||||
|
market=market,
|
||||||
|
asset_def=asset_def[0],
|
||||||
|
is_local=asset_def[1]
|
||||||
|
)
|
||||||
|
self.assets.append(asset)
|
||||||
|
|
||||||
if 'lot' not in params:
|
except TypeError as e:
|
||||||
params['lot'] = params['min_trade_size']
|
log.warn('unable to add asset: {}'.format(e))
|
||||||
|
|
||||||
|
if asset is None:
|
||||||
|
asset = self.create_trading_pair(market=market)
|
||||||
|
self.assets.append(asset)
|
||||||
|
|
||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
try:
|
try:
|
||||||
@@ -644,14 +755,18 @@ class CCXT(Exchange):
|
|||||||
|
|
||||||
side = 'buy' if amount > 0 else 'sell'
|
side = 'buy' if amount > 0 else 'sell'
|
||||||
if hasattr(self.api, 'amount_to_lots'):
|
if hasattr(self.api, 'amount_to_lots'):
|
||||||
adj_amount = self.api.amount_to_lots(
|
# TODO: is this right?
|
||||||
symbol=symbol,
|
if self.api.markets is None:
|
||||||
amount=abs(amount),
|
self.api.load_markets()
|
||||||
)
|
|
||||||
if adj_amount != abs(amount):
|
# https://github.com/ccxt/ccxt/issues/1483
|
||||||
log.info(
|
adj_amount = round(abs(amount), asset.decimals)
|
||||||
'adjusted order amount {} to {} based on lot size'.format(
|
market = self.api.markets[symbol]
|
||||||
abs(amount), adj_amount,
|
if 'lots' in market and market['lots'] > amount:
|
||||||
|
raise CreateOrderError(
|
||||||
|
exchange=self.name,
|
||||||
|
e='order amount lower than the smallest lot: {}'.format(
|
||||||
|
amount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -865,7 +980,8 @@ class CCXT(Exchange):
|
|||||||
)
|
)
|
||||||
raise ExchangeRequestError(error=e)
|
raise ExchangeRequestError(error=e)
|
||||||
|
|
||||||
def cancel_order(self, order_param, asset_or_symbol=None):
|
def cancel_order(self, order_param,
|
||||||
|
asset_or_symbol=None, params={}):
|
||||||
order_id = order_param.id \
|
order_id = order_param.id \
|
||||||
if isinstance(order_param, Order) else order_param
|
if isinstance(order_param, Order) else order_param
|
||||||
|
|
||||||
@@ -877,7 +993,8 @@ class CCXT(Exchange):
|
|||||||
try:
|
try:
|
||||||
symbol = self.get_symbol(asset_or_symbol) \
|
symbol = self.get_symbol(asset_or_symbol) \
|
||||||
if asset_or_symbol is not None else None
|
if asset_or_symbol is not None else None
|
||||||
self.api.cancel_order(id=order_id, symbol=symbol)
|
self.api.cancel_order(id=order_id,
|
||||||
|
symbol=symbol, params= params)
|
||||||
|
|
||||||
except (ExchangeError, NetworkError) as e:
|
except (ExchangeError, NetworkError) as e:
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import pytz
|
||||||
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
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from logbook import Logger
|
|
||||||
|
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.data.data_portal import BASE_FIELDS
|
from catalyst.data.data_portal import BASE_FIELDS
|
||||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||||
@@ -18,8 +17,9 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
|||||||
from catalyst.exchange.utils.datetime_utils import get_delta, \
|
from catalyst.exchange.utils.datetime_utils import get_delta, \
|
||||||
get_periods_range, \
|
get_periods_range, \
|
||||||
get_periods, get_start_dt, get_frequency
|
get_periods, get_start_dt, get_frequency
|
||||||
from catalyst.exchange.utils.exchange_utils import \
|
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols, \
|
||||||
resample_history_df, has_bundle
|
resample_history_df, has_bundle
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('Exchange', level=LOG_LEVEL)
|
log = Logger('Exchange', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -291,6 +291,16 @@ class Exchange:
|
|||||||
log.debug('found asset: {}'.format(asset))
|
log.debug('found asset: {}'.format(asset))
|
||||||
return asset
|
return asset
|
||||||
|
|
||||||
|
def fetch_symbol_map(self, is_local=False):
|
||||||
|
index = 1 if is_local else 0
|
||||||
|
if self._symbol_maps[index] is not None:
|
||||||
|
return self._symbol_maps[index]
|
||||||
|
|
||||||
|
else:
|
||||||
|
symbol_map = get_exchange_symbols(self.name, is_local)
|
||||||
|
self._symbol_maps[index] = symbol_map
|
||||||
|
return symbol_map
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
@@ -302,13 +312,24 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_exchange_config(self):
|
def load_assets(self, is_local=False):
|
||||||
"""
|
"""
|
||||||
Fetch the exchange market data and generate a config object
|
Populate the 'assets' attribute with a dictionary of Assets.
|
||||||
Returns
|
The key of the resulting dictionary is the exchange specific
|
||||||
-------
|
currency pair symbol. The universal symbol is contained in the
|
||||||
|
'symbol' attribute of each asset.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
The sid of each asset is calculated based on a numeric hash of the
|
||||||
|
universal symbol. This simple approach avoids maintaining a mapping
|
||||||
|
of sids.
|
||||||
|
|
||||||
|
This method can be omerridden if an exchange offers equivalent data
|
||||||
|
via its api.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def get_spot_value(self, assets, field, dt=None, data_frequency='minute'):
|
def get_spot_value(self, assets, field, dt=None, data_frequency='minute'):
|
||||||
"""
|
"""
|
||||||
@@ -494,32 +515,37 @@ class Exchange:
|
|||||||
|
|
||||||
series = dict()
|
series = dict()
|
||||||
for asset in candles:
|
for asset in candles:
|
||||||
first_candle = candles[asset][0]
|
if candles[asset]:
|
||||||
asset_series = self.get_series_from_candles(
|
first_candle = candles[asset][0]
|
||||||
candles=candles[asset],
|
asset_series = self.get_series_from_candles(
|
||||||
start_dt=first_candle['last_traded'],
|
candles=candles[asset],
|
||||||
end_dt=end_dt,
|
start_dt=first_candle['last_traded'],
|
||||||
data_frequency=frequency,
|
end_dt=end_dt,
|
||||||
field=field,
|
data_frequency=frequency,
|
||||||
)
|
field=field,
|
||||||
|
|
||||||
delta_candle_size = candle_size * 60 if unit == 'H' else candle_size
|
|
||||||
# Checking to make sure that the dates match
|
|
||||||
delta = get_delta(delta_candle_size, data_frequency)
|
|
||||||
adj_end_dt = end_dt - delta
|
|
||||||
last_traded = asset_series.index[-1]
|
|
||||||
|
|
||||||
if last_traded < adj_end_dt:
|
|
||||||
raise LastCandleTooEarlyError(
|
|
||||||
last_traded=last_traded,
|
|
||||||
end_dt=adj_end_dt,
|
|
||||||
exchange=self.name,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
delta_candle_size = candle_size * 60 if unit == 'H' else candle_size
|
||||||
|
# Checking to make sure that the dates match
|
||||||
|
delta = get_delta(delta_candle_size, data_frequency)
|
||||||
|
adj_end_dt = end_dt - delta
|
||||||
|
last_traded = asset_series.index[-1]
|
||||||
|
|
||||||
|
if last_traded < adj_end_dt:
|
||||||
|
raise LastCandleTooEarlyError(
|
||||||
|
last_traded=last_traded,
|
||||||
|
end_dt=adj_end_dt,
|
||||||
|
exchange=self.name,
|
||||||
|
)
|
||||||
|
else: # empty candle received
|
||||||
|
# because other assets are tz-aware, we need its tz to be set as well
|
||||||
|
asset_series = pd.Series([], index=pd.DatetimeIndex([], tz=pytz.utc))
|
||||||
|
|
||||||
|
|
||||||
series[asset] = asset_series
|
series[asset] = asset_series
|
||||||
|
|
||||||
df = pd.DataFrame(series)
|
df = pd.DataFrame(series)
|
||||||
df.dropna(inplace=True)
|
#df.dropna(inplace=True) # commented out due to issue 236
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@@ -636,20 +662,16 @@ class Exchange:
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def _check_low_balance(self, currency, balances, amount, open_orders=None):
|
def _check_low_balance(self, currency, balances, amount):
|
||||||
free = balances[currency]['free'] if currency in balances else 0.0
|
free = balances[currency]['free'] if currency in balances else 0.0
|
||||||
|
|
||||||
if open_orders:
|
|
||||||
# TODO: make sure that this works
|
|
||||||
free += sum([order.amount for order in open_orders])
|
|
||||||
|
|
||||||
if free < amount:
|
if free < amount:
|
||||||
return free, True
|
return free, True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return free, False
|
return free, False
|
||||||
|
|
||||||
def sync_positions(self, positions, open_orders=None, cash=None,
|
def sync_positions(self, positions, cash=None,
|
||||||
check_balances=False):
|
check_balances=False):
|
||||||
"""
|
"""
|
||||||
Update the portfolio cash and position balances based on the
|
Update the portfolio cash and position balances based on the
|
||||||
@@ -679,7 +701,7 @@ class Exchange:
|
|||||||
balances=balances,
|
balances=balances,
|
||||||
amount=cash,
|
amount=cash,
|
||||||
)
|
)
|
||||||
if is_lower and not open_orders:
|
if is_lower:
|
||||||
raise NotEnoughCashError(
|
raise NotEnoughCashError(
|
||||||
currency=self.base_currency,
|
currency=self.base_currency,
|
||||||
exchange=self.name,
|
exchange=self.name,
|
||||||
@@ -906,7 +928,8 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cancel_order(self, order_param, symbol_or_asset=None):
|
def cancel_order(self, order_param,
|
||||||
|
symbol_or_asset=None, params={}):
|
||||||
"""Cancel an open order.
|
"""Cancel an open order.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -915,6 +938,7 @@ class Exchange:
|
|||||||
The order_id or order object to cancel.
|
The order_id or order object to cancel.
|
||||||
symbol_or_asset: str|TradingPair
|
symbol_or_asset: str|TradingPair
|
||||||
The catalyst symbol, some exchanges need this
|
The catalyst symbol, some exchanges need this
|
||||||
|
params:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ from datetime import timedelta
|
|||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import isfile, join, exists
|
from os.path import isfile, join, exists
|
||||||
|
|
||||||
|
import catalyst.protocol as zp
|
||||||
import logbook
|
import logbook
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from redo import retry
|
|
||||||
|
|
||||||
import catalyst.protocol as zp
|
|
||||||
from catalyst.algorithm import TradingAlgorithm
|
from catalyst.algorithm import TradingAlgorithm
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.exchange.exchange_blotter import ExchangeBlotter
|
from catalyst.exchange.exchange_blotter import ExchangeBlotter
|
||||||
@@ -52,6 +50,7 @@ from catalyst.utils.api_support import api_method
|
|||||||
from catalyst.utils.input_validation import error_keywords, ensure_upper_case
|
from catalyst.utils.input_validation import error_keywords, ensure_upper_case
|
||||||
from catalyst.utils.math_utils import round_nearest
|
from catalyst.utils.math_utils import round_nearest
|
||||||
from catalyst.utils.preprocess import preprocess
|
from catalyst.utils.preprocess import preprocess
|
||||||
|
from redo import retry
|
||||||
|
|
||||||
log = logbook.Logger('exchange_algorithm', level=LOG_LEVEL)
|
log = logbook.Logger('exchange_algorithm', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -376,19 +375,30 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
if error:
|
if error:
|
||||||
log.warning(error)
|
log.warning(error)
|
||||||
|
|
||||||
self.pnl_stats = get_algo_df(self.algo_namespace, 'pnl_stats')
|
# in order to save paper & live files separately
|
||||||
|
self.mode_name = 'paper' if kwargs['simulate_orders'] else 'live'
|
||||||
|
|
||||||
self.custom_signals_stats = \
|
self.pnl_stats = get_algo_df(
|
||||||
get_algo_df(self.algo_namespace, 'custom_signals_stats')
|
self.algo_namespace,
|
||||||
|
'pnl_stats_{}'.format(self.mode_name),
|
||||||
|
)
|
||||||
|
|
||||||
self.exposure_stats = \
|
self.custom_signals_stats = get_algo_df(
|
||||||
get_algo_df(self.algo_namespace, 'exposure_stats')
|
self.algo_namespace,
|
||||||
|
'custom_signals_stats_{}'.format(self.mode_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.exposure_stats = get_algo_df(
|
||||||
|
self.algo_namespace,
|
||||||
|
'exposure_stats_{}'.format(self.mode_name)
|
||||||
|
)
|
||||||
|
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
|
|
||||||
self.stats_minutes = 1
|
self.stats_minutes = 1
|
||||||
|
|
||||||
self._last_orders = []
|
self._last_orders = []
|
||||||
|
self._last_open_orders = []
|
||||||
self.trading_client = None
|
self.trading_client = None
|
||||||
|
|
||||||
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs)
|
||||||
@@ -515,7 +525,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
"""
|
"""
|
||||||
self.state = get_algo_object(
|
self.state = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='context.state',
|
key='context.state_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if self.state is None:
|
if self.state is None:
|
||||||
self.state = {}
|
self.state = {}
|
||||||
@@ -538,7 +548,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
# Unpacking the perf_tracker and positions if available
|
# Unpacking the perf_tracker and positions if available
|
||||||
cum_perf = get_algo_object(
|
cum_perf = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='cumulative_performance',
|
key='cumulative_performance_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if cum_perf is not None:
|
if cum_perf is not None:
|
||||||
tracker.cumulative_performance = cum_perf
|
tracker.cumulative_performance = cum_perf
|
||||||
@@ -549,7 +559,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
todays_perf = get_algo_object(
|
todays_perf = get_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key=today.strftime('%Y-%m-%d'),
|
key=today.strftime('%Y-%m-%d'),
|
||||||
rel_path='daily_performance',
|
rel_path='daily_performance_{}'.format(self.mode_name),
|
||||||
)
|
)
|
||||||
if todays_perf is not None:
|
if todays_perf is not None:
|
||||||
# Ensure single common position tracker
|
# Ensure single common position tracker
|
||||||
@@ -641,7 +651,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
required_cash = self.portfolio.cash if not orders else None
|
required_cash = self.portfolio.cash if not orders else None
|
||||||
cash, positions_value = exchange.sync_positions(
|
cash, positions_value = exchange.sync_positions(
|
||||||
positions=exchange_positions,
|
positions=exchange_positions,
|
||||||
open_orders=orders,
|
|
||||||
check_balances=check_balances,
|
check_balances=check_balances,
|
||||||
cash=required_cash,
|
cash=required_cash,
|
||||||
)
|
)
|
||||||
@@ -687,7 +696,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
self.pnl_stats = pd.concat([self.pnl_stats, df])
|
self.pnl_stats = pd.concat([self.pnl_stats, df])
|
||||||
|
|
||||||
save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats)
|
save_algo_df(
|
||||||
|
self.algo_namespace,
|
||||||
|
'pnl_stats_{}'.format(self.mode_name),
|
||||||
|
self.pnl_stats,
|
||||||
|
)
|
||||||
|
|
||||||
def add_custom_signals_stats(self, period_stats):
|
def add_custom_signals_stats(self, period_stats):
|
||||||
"""
|
"""
|
||||||
@@ -708,8 +721,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
)
|
)
|
||||||
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
|
self.custom_signals_stats = pd.concat([self.custom_signals_stats, df])
|
||||||
|
|
||||||
save_algo_df(self.algo_namespace, 'custom_signals_stats',
|
save_algo_df(
|
||||||
self.custom_signals_stats)
|
self.algo_namespace,
|
||||||
|
'custom_signals_stats_{}'.format(self.mode_name),
|
||||||
|
self.custom_signals_stats,
|
||||||
|
)
|
||||||
|
|
||||||
def add_exposure_stats(self, period_stats):
|
def add_exposure_stats(self, period_stats):
|
||||||
"""
|
"""
|
||||||
@@ -736,7 +752,9 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.exposure_stats = pd.concat([self.exposure_stats, df])
|
self.exposure_stats = pd.concat([self.exposure_stats, df])
|
||||||
|
|
||||||
save_algo_df(
|
save_algo_df(
|
||||||
self.algo_namespace, 'exposure_stats', self.exposure_stats
|
self.algo_namespace,
|
||||||
|
'exposure_stats_{}'.format(self.mode_name),
|
||||||
|
self.exposure_stats
|
||||||
)
|
)
|
||||||
|
|
||||||
def nullify_frame_stats(self, now):
|
def nullify_frame_stats(self, now):
|
||||||
@@ -760,6 +778,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
obj=self.frame_stats,
|
obj=self.frame_stats,
|
||||||
rel_path='frame_stats'
|
rel_path='frame_stats'
|
||||||
)
|
)
|
||||||
|
|
||||||
error = remove_old_files(
|
error = remove_old_files(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
today=now,
|
today=now,
|
||||||
@@ -792,12 +811,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
self.nullify_frame_stats(now=data.current_dt)
|
self.nullify_frame_stats(now=data.current_dt)
|
||||||
|
|
||||||
self.performance_needs_update = False
|
self.performance_needs_update = False
|
||||||
orders = list(self.perf_tracker.todays_performance.orders_by_id.keys())
|
last_orders_list = list(self.blotter.orders.keys())
|
||||||
if orders != self._last_orders:
|
open_orders_list = list(self.blotter.open_orders.keys())
|
||||||
|
|
||||||
|
if last_orders_list != self._last_orders or \
|
||||||
|
open_orders_list != self._last_open_orders:
|
||||||
self.performance_needs_update = True
|
self.performance_needs_update = True
|
||||||
|
|
||||||
# Saving current orders to detect changes in the next frame
|
# Saving current order positions
|
||||||
self._last_orders = copy.deepcopy(orders)
|
# to detect changes in the next frame
|
||||||
|
self._last_orders = copy.deepcopy(last_orders_list)
|
||||||
|
self._last_open_orders = copy.deepcopy(open_orders_list)
|
||||||
|
|
||||||
if self.performance_needs_update:
|
if self.performance_needs_update:
|
||||||
self.perf_tracker.update_performance()
|
self.perf_tracker.update_performance()
|
||||||
@@ -839,7 +863,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
log.debug('saving cumulative performance object')
|
log.debug('saving cumulative performance object')
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='cumulative_performance',
|
key='cumulative_performance_{}'.format(self.mode_name),
|
||||||
obj=self.perf_tracker.cumulative_performance,
|
obj=self.perf_tracker.cumulative_performance,
|
||||||
)
|
)
|
||||||
log.debug('saving todays performance object')
|
log.debug('saving todays performance object')
|
||||||
@@ -847,12 +871,12 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key=today.strftime('%Y-%m-%d'),
|
key=today.strftime('%Y-%m-%d'),
|
||||||
obj=self.perf_tracker.todays_performance,
|
obj=self.perf_tracker.todays_performance,
|
||||||
rel_path='daily_performance'
|
rel_path='daily_performance_{}'.format(self.mode_name)
|
||||||
)
|
)
|
||||||
log.debug('saving context.state object')
|
log.debug('saving context.state object')
|
||||||
save_algo_object(
|
save_algo_object(
|
||||||
algo_name=self.algo_namespace,
|
algo_name=self.algo_namespace,
|
||||||
key='context.state',
|
key='context.state_{}'.format(self.mode_name),
|
||||||
obj=self.state)
|
obj=self.state)
|
||||||
|
|
||||||
def _process_stats(self, data):
|
def _process_stats(self, data):
|
||||||
@@ -908,6 +932,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
csv_bytes = stats_to_algo_folder(
|
csv_bytes = stats_to_algo_folder(
|
||||||
stats=self.frame_stats,
|
stats=self.frame_stats,
|
||||||
algo_namespace=self.algo_namespace,
|
algo_namespace=self.algo_namespace,
|
||||||
|
folder_name='stats_{}'.format(self.mode_name),
|
||||||
recorded_cols=recorded_cols,
|
recorded_cols=recorded_cols,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1012,13 +1037,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
args=(order_id,))
|
args=(order_id,))
|
||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
def cancel_order(self, order_param, exchange_name):
|
def cancel_order(self, order_param, exchange_name,
|
||||||
|
symbol=None, params={}):
|
||||||
"""Cancel an open order.
|
"""Cancel an open order.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
order_param : str or Order
|
order_param : str or Order
|
||||||
The order_id or order object to cancel.
|
The order_id or order object to cancel.
|
||||||
|
|
||||||
|
exchange_name: name of exchange from
|
||||||
|
which you want to cancel the order
|
||||||
|
symbol:
|
||||||
|
params:
|
||||||
"""
|
"""
|
||||||
exchange = self.exchanges[exchange_name]
|
exchange = self.exchanges[exchange_name]
|
||||||
|
|
||||||
@@ -1032,4 +1063,4 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
|||||||
sleeptime=self.attempts['retry_sleeptime'],
|
sleeptime=self.attempts['retry_sleeptime'],
|
||||||
retry_exceptions=(ExchangeRequestError,),
|
retry_exceptions=(ExchangeRequestError,),
|
||||||
cleanup=lambda: log.warn('cancelling order again.'),
|
cleanup=lambda: log.warn('cancelling order again.'),
|
||||||
args=(order_id,))
|
args=(order_id, symbol, params))
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import copy
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@@ -9,12 +8,8 @@ from operator import is_not
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytz
|
import pytz
|
||||||
from catalyst.assets._assets import TradingPair
|
|
||||||
from logbook import Logger
|
|
||||||
from pytz import UTC
|
|
||||||
from six import itervalues
|
|
||||||
|
|
||||||
from catalyst import get_calendar
|
from catalyst import get_calendar
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
from catalyst.constants import DATE_TIME_FORMAT, AUTO_INGEST
|
from catalyst.constants import DATE_TIME_FORMAT, AUTO_INGEST
|
||||||
from catalyst.constants import LOG_LEVEL
|
from catalyst.constants import LOG_LEVEL
|
||||||
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
||||||
@@ -27,11 +22,15 @@ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
|||||||
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
|
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
|
||||||
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
|
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
|
||||||
get_bcolz_chunk, get_df_from_arrays, get_assets
|
get_bcolz_chunk, get_df_from_arrays, get_assets
|
||||||
from catalyst.exchange.utils.datetime_utils import get_start_dt, \
|
from catalyst.exchange.utils.datetime_utils import get_delta, get_start_dt, \
|
||||||
get_period_label, get_month_start_end, get_year_start_end
|
get_period_label, get_month_start_end, get_year_start_end
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
from catalyst.exchange.utils.exchange_utils import get_exchange_folder, \
|
||||||
|
save_exchange_symbols, mixin_market_params, get_catalyst_symbol
|
||||||
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
|
||||||
|
from logbook import Logger
|
||||||
|
from pytz import UTC
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
log = Logger('exchange_bundle', level=LOG_LEVEL)
|
log = Logger('exchange_bundle', level=LOG_LEVEL)
|
||||||
|
|
||||||
@@ -626,13 +625,13 @@ class ExchangeBundle:
|
|||||||
key=lambda chunk: pd.to_datetime(chunk['period'])
|
key=lambda chunk: pd.to_datetime(chunk['period'])
|
||||||
)
|
)
|
||||||
with maybe_show_progress(
|
with maybe_show_progress(
|
||||||
all_chunks,
|
all_chunks,
|
||||||
show_progress,
|
show_progress,
|
||||||
label='Ingesting {frequency} price data on '
|
label='Ingesting {frequency} price data on '
|
||||||
'{exchange}'.format(
|
'{exchange}'.format(
|
||||||
exchange=self.exchange_name,
|
exchange=self.exchange_name,
|
||||||
frequency=data_frequency,
|
frequency=data_frequency,
|
||||||
)) as it:
|
)) as it:
|
||||||
for chunk in it:
|
for chunk in it:
|
||||||
problems += self.ingest_ctable(
|
problems += self.ingest_ctable(
|
||||||
asset=chunk['asset'],
|
asset=chunk['asset'],
|
||||||
@@ -701,36 +700,42 @@ class ExchangeBundle:
|
|||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
start_dt = df.index.get_level_values(1).min()
|
start_dt = df.index.get_level_values(1).min()
|
||||||
end_dt = df.index.get_level_values(1).max()
|
end_dt = df.index.get_level_values(1).max()
|
||||||
|
end_dt_key = 'end_{}'.format(data_frequency)
|
||||||
|
|
||||||
try:
|
market = self.exchange.get_market(symbol)
|
||||||
asset = self.exchange.get_asset(symbol, is_local=True)
|
if market is None:
|
||||||
except:
|
raise ValueError('symbol not available in the exchange.')
|
||||||
asset = copy.deepcopy(self.exchange.get_asset(symbol))
|
|
||||||
|
|
||||||
if asset.data_source == 'local':
|
params = dict(
|
||||||
asset.start_date = asset.start_date \
|
exchange=self.exchange.name,
|
||||||
if asset.start_date < start_dt else start_dt
|
data_source='local',
|
||||||
|
exchange_symbol=market['id'],
|
||||||
|
)
|
||||||
|
mixin_market_params(self.exchange_name, params, market)
|
||||||
|
|
||||||
if data_frequency == 'daily':
|
asset_def = self.exchange.get_asset_def(market, True)
|
||||||
asset.end_date = asset.end_daily = asset.end_daily \
|
if asset_def is not None:
|
||||||
if asset.end_daily > end_dt else end_dt
|
params['symbol'] = asset_def['symbol']
|
||||||
|
|
||||||
else:
|
params['start_date'] = asset_def['start_date'] \
|
||||||
asset.end_date = asset.end_minute = asset.end_minute \
|
if asset_def['start_date'] < start_dt else start_dt
|
||||||
if asset.end_minute > end_dt else end_dt
|
|
||||||
|
params['end_date'] = asset_def[end_dt_key] \
|
||||||
|
if asset_def[end_dt_key] > end_dt else end_dt
|
||||||
|
|
||||||
|
params['end_daily'] = end_dt \
|
||||||
|
if data_frequency == 'daily' else asset_def['end_daily']
|
||||||
|
|
||||||
|
params['end_minute'] = end_dt \
|
||||||
|
if data_frequency == 'minute' else asset_def['end_minute']
|
||||||
|
|
||||||
else:
|
else:
|
||||||
asset.data_source = 'local'
|
params['symbol'] = get_catalyst_symbol(market)
|
||||||
asset.start_date = start_dt
|
|
||||||
asset.end_dt = end_dt
|
|
||||||
|
|
||||||
if data_frequency == 'daily':
|
params['end_daily'] = end_dt \
|
||||||
asset.end_daily = end_dt
|
if data_frequency == 'daily' else 'N/A'
|
||||||
asset.end_minute = None
|
params['end_minute'] = end_dt \
|
||||||
|
if data_frequency == 'minute' else 'N/A'
|
||||||
else:
|
|
||||||
asset.end_daily = None
|
|
||||||
asset.end_minute = end_dt
|
|
||||||
|
|
||||||
if min_start_dt is None or start_dt < min_start_dt:
|
if min_start_dt is None or start_dt < min_start_dt:
|
||||||
min_start_dt = start_dt
|
min_start_dt = start_dt
|
||||||
@@ -738,9 +743,11 @@ class ExchangeBundle:
|
|||||||
if max_end_dt is None or end_dt > max_end_dt:
|
if max_end_dt is None or end_dt > max_end_dt:
|
||||||
max_end_dt = end_dt
|
max_end_dt = end_dt
|
||||||
|
|
||||||
assets[symbol] = asset
|
asset = TradingPair(**params)
|
||||||
|
assets[market['id']] = asset
|
||||||
|
|
||||||
|
save_exchange_symbols(self.exchange_name, assets, True)
|
||||||
|
|
||||||
# TODO: update config.json
|
|
||||||
writer = self.get_writer(
|
writer = self.get_writer(
|
||||||
start_dt=min_start_dt.replace(hour=00, minute=00),
|
start_dt=min_start_dt.replace(hour=00, minute=00),
|
||||||
end_dt=max_end_dt.replace(hour=23, minute=59),
|
end_dt=max_end_dt.replace(hour=23, minute=59),
|
||||||
|
|||||||
@@ -322,17 +322,3 @@ class BalanceTooLowError(ZiplineError):
|
|||||||
'add positions to hold a free amount greater than {amount}, or clean '
|
'add positions to hold a free amount greater than {amount}, or clean '
|
||||||
'the state of this algo and restart.'
|
'the state of this algo and restart.'
|
||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
class MarketsNotFoundError(ZiplineError):
|
|
||||||
msg = (
|
|
||||||
'Exchange {exchange} contains no valid market so it is unusable in '
|
|
||||||
'Catalyst.'
|
|
||||||
).strip()
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidMarketError(ZiplineError):
|
|
||||||
msg = (
|
|
||||||
'Exchange {exchange} contains at least one incorrectly structured '
|
|
||||||
'market: {market}, so it is unusable in Catalyst.'
|
|
||||||
).strip()
|
|
||||||
|
|||||||
@@ -1,307 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import pandas as pd
|
|
||||||
from six.moves.urllib import request
|
|
||||||
|
|
||||||
from catalyst.assets._assets import TradingPair
|
|
||||||
from ccxt import NetworkError
|
|
||||||
from catalyst.constants import LOG_LEVEL, EXCHANGE_CONFIG_URL
|
|
||||||
from catalyst.exchange.exchange_errors import MarketsNotFoundError, \
|
|
||||||
InvalidMarketError
|
|
||||||
from catalyst.exchange.utils.exchange_utils import get_catalyst_symbol, \
|
|
||||||
get_exchange_folder, get_exchange_auth
|
|
||||||
from catalyst.exchange.utils.serialization_utils import ExchangeJSONDecoder, \
|
|
||||||
ExchangeJSONEncoder
|
|
||||||
from logbook import Logger
|
|
||||||
from redo import retry
|
|
||||||
from ccxt.base.exchange import Exchange
|
|
||||||
from catalyst.utils.paths import last_modified_time, data_root, \
|
|
||||||
ensure_directory
|
|
||||||
import ccxt
|
|
||||||
|
|
||||||
log = Logger('ccxt_utils', level=LOG_LEVEL)
|
|
||||||
|
|
||||||
|
|
||||||
def scan_exchange_configs(features=None, history=None, is_authenticated=False,
|
|
||||||
path=None):
|
|
||||||
"""
|
|
||||||
Finding exchanges from their config files
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
features
|
|
||||||
is_authenticated
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
for exchange_name in ccxt.exchanges:
|
|
||||||
config = get_exchange_config(exchange_name, path)
|
|
||||||
if not config or 'error' in config:
|
|
||||||
log.info(
|
|
||||||
'skipping invalid exchange {}'.format(exchange_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if the exchange has an auth.json file
|
|
||||||
if is_authenticated:
|
|
||||||
exchange_auth = get_exchange_auth(exchange_name)
|
|
||||||
has_auth = (exchange_auth['key'] != ''
|
|
||||||
and exchange_auth['secret'] != '')
|
|
||||||
|
|
||||||
if not has_auth:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if features is None:
|
|
||||||
has_features = True
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
supported_features = [
|
|
||||||
feature for feature in features if
|
|
||||||
feature in config['features']
|
|
||||||
]
|
|
||||||
has_features = len(supported_features) > 0
|
|
||||||
except Exception:
|
|
||||||
has_features = False
|
|
||||||
|
|
||||||
# TODO: filter by history
|
|
||||||
if has_features:
|
|
||||||
yield config
|
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_config(exchange_name, path=None, environ=None,
|
|
||||||
expiry='2H'):
|
|
||||||
"""
|
|
||||||
The de-serialized content of the exchange's config.json.
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
exchange_name: str
|
|
||||||
The exchange name
|
|
||||||
filename: str
|
|
||||||
The target file
|
|
||||||
environ:
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
config: dict[srt, Object]
|
|
||||||
The config dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if path is None:
|
|
||||||
root = data_root(environ)
|
|
||||||
path = os.path.join(root, 'exchanges')
|
|
||||||
|
|
||||||
folder = os.path.join(path, exchange_name)
|
|
||||||
ensure_directory(folder)
|
|
||||||
|
|
||||||
filename = os.path.join(folder, 'config.json')
|
|
||||||
url = EXCHANGE_CONFIG_URL.format(exchange=exchange_name)
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
# If the file exists, only update periodically to avoid
|
|
||||||
# unnecessary calls
|
|
||||||
now = pd.Timestamp.utcnow()
|
|
||||||
limit = pd.Timedelta(expiry)
|
|
||||||
if pd.Timedelta(now - last_modified_time(filename)) > limit:
|
|
||||||
try:
|
|
||||||
request.urlretrieve(url=url, filename=filename)
|
|
||||||
except Exception as e:
|
|
||||||
log.warn(
|
|
||||||
'unable to update config {} => {}: {}'.format(
|
|
||||||
url, filename, e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
request.urlretrieve(url=url, filename=filename)
|
|
||||||
|
|
||||||
with open(filename) as data_file:
|
|
||||||
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
|
||||||
return data
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log.warn(
|
|
||||||
'unable to download {} config: {}'.format(
|
|
||||||
exchange_name, e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return dict(error=e)
|
|
||||||
|
|
||||||
|
|
||||||
def save_exchange_config(config, filename=None, environ=None):
|
|
||||||
"""
|
|
||||||
Save assets into an exchange_config file.
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
exchange_name: str
|
|
||||||
config
|
|
||||||
environ
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
"""
|
|
||||||
if filename is None:
|
|
||||||
name = 'config.json'
|
|
||||||
exchange_folder = get_exchange_folder(config['id'], environ)
|
|
||||||
filename = os.path.join(exchange_folder, name)
|
|
||||||
|
|
||||||
with open(filename, 'w+') as handle:
|
|
||||||
json.dump(config, handle, indent=4, cls=ExchangeJSONEncoder)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_markets(ccxt_exchange):
|
|
||||||
"""
|
|
||||||
Fetches CCXT market objects.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
ccxt_exchange: Exchange
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
markets_symbols = ccxt_exchange.load_markets()
|
|
||||||
log.debug(
|
|
||||||
'fetching {} markets:\n{}'.format(
|
|
||||||
ccxt_exchange.name, markets_symbols
|
|
||||||
)
|
|
||||||
)
|
|
||||||
markets = ccxt_exchange.fetch_markets()
|
|
||||||
|
|
||||||
if not markets:
|
|
||||||
raise MarketsNotFoundError(
|
|
||||||
exchange=ccxt_exchange.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
for market in markets:
|
|
||||||
if 'id' not in market:
|
|
||||||
raise InvalidMarketError(
|
|
||||||
exchange=ccxt_exchange.name,
|
|
||||||
market=market,
|
|
||||||
)
|
|
||||||
return markets
|
|
||||||
|
|
||||||
|
|
||||||
def create_exchange_config(ccxt_exchange):
|
|
||||||
"""
|
|
||||||
Creates an exchange config structure.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
ccxt_exchange: Exchange
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
exchange_name = ccxt_exchange.__class__.__name__
|
|
||||||
config = dict(
|
|
||||||
id=exchange_name,
|
|
||||||
name=ccxt_exchange.name,
|
|
||||||
features=[
|
|
||||||
feature for feature in ccxt_exchange.has if
|
|
||||||
ccxt_exchange.has[feature]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
markets = retry(
|
|
||||||
action=fetch_markets,
|
|
||||||
attempts=5,
|
|
||||||
sleeptime=5,
|
|
||||||
retry_exceptions=(NetworkError,),
|
|
||||||
cleanup=lambda: log.warn(
|
|
||||||
'fetching markets again for {}'.format(exchange_name)
|
|
||||||
),
|
|
||||||
args=(ccxt_exchange,)
|
|
||||||
)
|
|
||||||
|
|
||||||
config['assets'] = []
|
|
||||||
for market in markets:
|
|
||||||
asset = create_trading_pair(exchange_name, market)
|
|
||||||
config['assets'].append(asset)
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def create_trading_pair(exchange_name, market, start_dt=None, end_dt=None,
|
|
||||||
leverage=1, end_daily=None, end_minute=None):
|
|
||||||
"""
|
|
||||||
Creating a TradingPair from market and asset data.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
market: dict[str, Object]
|
|
||||||
start_dt
|
|
||||||
end_dt
|
|
||||||
leverage
|
|
||||||
end_daily
|
|
||||||
end_minute
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
params = dict(
|
|
||||||
exchange=exchange_name,
|
|
||||||
data_source='catalyst',
|
|
||||||
exchange_symbol=market['id'],
|
|
||||||
symbol=get_catalyst_symbol(market),
|
|
||||||
start_date=start_dt,
|
|
||||||
end_date=end_dt,
|
|
||||||
leverage=leverage,
|
|
||||||
asset_name=market['symbol'],
|
|
||||||
end_daily=end_daily,
|
|
||||||
end_minute=end_minute,
|
|
||||||
)
|
|
||||||
apply_conditional_market_params(exchange_name, params, market)
|
|
||||||
|
|
||||||
return TradingPair(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_conditional_market_params(exchange_name, params, market):
|
|
||||||
"""
|
|
||||||
Applies a CCXT market dict to parameters of TradingPair init.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
params: dict[Object]
|
|
||||||
market: dict[Object]
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
|
|
||||||
"""
|
|
||||||
# TODO: make this more externalized / configurable
|
|
||||||
# Consider representing in some type of JSON structure
|
|
||||||
if 'active' in market:
|
|
||||||
params['trading_state'] = 1 if market['active'] else 0
|
|
||||||
|
|
||||||
else:
|
|
||||||
params['trading_state'] = 1
|
|
||||||
|
|
||||||
if 'lot' in market:
|
|
||||||
params['min_trade_size'] = market['lot']
|
|
||||||
params['lot'] = market['lot']
|
|
||||||
|
|
||||||
if exchange_name == 'bitfinex':
|
|
||||||
params['maker'] = 0.001
|
|
||||||
params['taker'] = 0.002
|
|
||||||
|
|
||||||
elif 'maker' in market and 'taker' in market \
|
|
||||||
and market['maker'] is not None \
|
|
||||||
and market['taker'] is not None:
|
|
||||||
params['maker'] = market['maker']
|
|
||||||
params['taker'] = market['taker']
|
|
||||||
|
|
||||||
else:
|
|
||||||
# TODO: default commission, make configurable
|
|
||||||
params['maker'] = 0.0015
|
|
||||||
params['taker'] = 0.0025
|
|
||||||
|
|
||||||
info = market['info'] if 'info' in market else None
|
|
||||||
if info:
|
|
||||||
if 'minimum_order_size' in info:
|
|
||||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
|
||||||
|
|
||||||
if 'lot' not in params:
|
|
||||||
params['lot'] = params['min_trade_size']
|
|
||||||
@@ -11,14 +11,11 @@ from six import string_types
|
|||||||
from six.moves.urllib import request
|
from six.moves.urllib import request
|
||||||
|
|
||||||
from catalyst.constants import DATE_FORMAT, SYMBOLS_URL
|
from catalyst.constants import DATE_FORMAT, SYMBOLS_URL
|
||||||
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \
|
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound
|
||||||
InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias
|
|
||||||
from catalyst.exchange.utils.serialization_utils import ExchangeJSONEncoder, \
|
from catalyst.exchange.utils.serialization_utils import ExchangeJSONEncoder, \
|
||||||
ExchangeJSONDecoder, ConfigJSONEncoder
|
ExchangeJSONDecoder
|
||||||
from catalyst.utils.paths import data_root, ensure_directory, \
|
from catalyst.utils.paths import data_root, ensure_directory, \
|
||||||
last_modified_time
|
last_modified_time
|
||||||
from six import string_types
|
|
||||||
from six.moves.urllib import request
|
|
||||||
|
|
||||||
|
|
||||||
def get_sid(symbol):
|
def get_sid(symbol):
|
||||||
@@ -72,7 +69,7 @@ def is_blacklist(exchange_name, environ=None):
|
|||||||
return os.path.exists(filename)
|
return os.path.exists(filename)
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_config_filename(exchange_name, environ=None):
|
def get_exchange_symbols_filename(exchange_name, is_local=False, environ=None):
|
||||||
"""
|
"""
|
||||||
The absolute path of the exchange's symbol.json file.
|
The absolute path of the exchange's symbol.json file.
|
||||||
|
|
||||||
@@ -86,12 +83,12 @@ def get_exchange_config_filename(exchange_name, environ=None):
|
|||||||
str
|
str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
name = 'config.json'
|
name = 'symbols.json' if not is_local else 'symbols_local.json'
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||||
return os.path.join(exchange_folder, name)
|
return os.path.join(exchange_folder, name)
|
||||||
|
|
||||||
|
|
||||||
def download_exchange_config(exchange_name, filename, environ=None):
|
def download_exchange_symbols(exchange_name, environ=None):
|
||||||
"""
|
"""
|
||||||
Downloads the exchange's symbols.json from the repository.
|
Downloads the exchange's symbols.json from the repository.
|
||||||
|
|
||||||
@@ -105,13 +102,15 @@ def download_exchange_config(exchange_name, filename, environ=None):
|
|||||||
str
|
str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
url = EXCHANGE_CONFIG_URL.format(exchange=exchange_name)
|
filename = get_exchange_symbols_filename(exchange_name)
|
||||||
request.urlretrieve(url=url, filename=filename)
|
url = SYMBOLS_URL.format(exchange=exchange_name)
|
||||||
|
response = request.urlretrieve(url=url, filename=filename)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_config(exchange_name, filename=None, environ=None):
|
def get_exchange_symbols(exchange_name, is_local=False, environ=None):
|
||||||
"""
|
"""
|
||||||
The de-serialized content of the exchange's config.json.
|
The de-serialized content of the exchange's symbols.json.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -124,47 +123,55 @@ def get_exchange_config(exchange_name, filename=None, environ=None):
|
|||||||
Object
|
Object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if filename is None:
|
filename = get_exchange_symbols_filename(exchange_name, is_local)
|
||||||
filename = get_exchange_config_filename(exchange_name)
|
|
||||||
|
if not is_local and (not os.path.isfile(filename) or pd.Timedelta(
|
||||||
|
pd.Timestamp('now', tz='UTC') - last_modified_time(
|
||||||
|
filename)).days > 1):
|
||||||
|
try:
|
||||||
|
download_exchange_symbols(exchange_name, environ)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
now = pd.Timestamp.utcnow()
|
with open(filename) as data_file:
|
||||||
limit = pd.Timedelta('2H')
|
try:
|
||||||
if pd.Timedelta(now - last_modified_time(filename)) > limit:
|
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
||||||
download_exchange_config(exchange_name, filename, environ)
|
return data
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return dict()
|
||||||
else:
|
else:
|
||||||
download_exchange_config(exchange_name, filename, environ)
|
raise ExchangeSymbolsNotFound(
|
||||||
|
exchange=exchange_name,
|
||||||
|
filename=filename
|
||||||
|
)
|
||||||
|
|
||||||
with open(filename) as data_file:
|
|
||||||
try:
|
|
||||||
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
|
||||||
return data
|
|
||||||
|
|
||||||
except ValueError:
|
def save_exchange_symbols(exchange_name, assets, is_local=False, environ=None):
|
||||||
return dict()
|
|
||||||
|
|
||||||
def save_exchange_config(exchange_name, config, filename=None, environ=None):
|
|
||||||
"""
|
"""
|
||||||
Save assets into an exchange_config file.
|
Save assets into an exchange_symbols file.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
exchange_name: str
|
exchange_name: str
|
||||||
config
|
assets: list[dict[str, object]]
|
||||||
|
is_local: bool
|
||||||
environ
|
environ
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if filename is None:
|
asset_dicts = dict()
|
||||||
name = 'config.json'
|
for symbol in assets:
|
||||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
asset_dicts[symbol] = assets[symbol].to_dict()
|
||||||
filename = os.path.join(exchange_folder, name)
|
|
||||||
|
|
||||||
with open(filename, 'w+') as handle:
|
filename = get_exchange_symbols_filename(
|
||||||
json.dump(config, handle, indent=4, cls=ConfigJSONEncoder)
|
exchange_name, is_local, environ
|
||||||
|
)
|
||||||
|
with open(filename, 'wt') as handle:
|
||||||
|
json.dump(asset_dicts, handle, indent=4, default=symbols_serial)
|
||||||
|
|
||||||
|
|
||||||
def get_symbols_string(assets):
|
def get_symbols_string(assets):
|
||||||
@@ -413,7 +420,7 @@ def clear_frame_stats_directory(algo_name):
|
|||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
def remove_old_files(algo_name, today, rel_path):
|
def remove_old_files(algo_name, today, rel_path, environ=None):
|
||||||
"""
|
"""
|
||||||
remove old files from a directory
|
remove old files from a directory
|
||||||
to avoid overloading the disk
|
to avoid overloading the disk
|
||||||
@@ -423,27 +430,31 @@ def remove_old_files(algo_name, today, rel_path):
|
|||||||
algo_name: str
|
algo_name: str
|
||||||
today: Timestamp
|
today: Timestamp
|
||||||
rel_path: str
|
rel_path: str
|
||||||
|
environ:
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
error: str
|
error: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
error = None
|
error = None
|
||||||
algo_folder = get_algo_folder(algo_name)
|
algo_folder = get_algo_folder(algo_name, environ)
|
||||||
folder = os.path.join(algo_folder, rel_path)
|
folder = os.path.join(algo_folder, rel_path)
|
||||||
|
ensure_directory(folder)
|
||||||
|
|
||||||
# run on all files in the folder
|
# run on all files in the folder
|
||||||
for f in os.listdir(folder):
|
for f in os.listdir(folder):
|
||||||
creation_unix = os.path.getctime(f)
|
try:
|
||||||
creation_time = pd.to_datetime(creation_unix, unit='s', )
|
file_path = os.path.join(folder, f)
|
||||||
|
creation_unix = os.path.getctime(file_path)
|
||||||
|
creation_time = pd.to_datetime(creation_unix, unit='s', utc=True)
|
||||||
|
|
||||||
# if the file is older than 30 days erase it
|
# if the file is older than 30 days erase it
|
||||||
if today - pd.DateOffset(30) > creation_time:
|
if today - pd.DateOffset(30) > creation_time:
|
||||||
try:
|
os.unlink(file_path)
|
||||||
os.unlink(f)
|
except OSError:
|
||||||
except OSError:
|
error = 'unable to erase files in {}'.format(folder)
|
||||||
error = 'unable to erase files in {}'.format(folder)
|
|
||||||
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
@@ -501,6 +512,25 @@ def has_bundle(exchange_name, data_frequency, environ=None):
|
|||||||
return os.path.isdir(folder)
|
return os.path.isdir(folder)
|
||||||
|
|
||||||
|
|
||||||
|
def symbols_serial(obj):
|
||||||
|
"""
|
||||||
|
JSON serializer for objects not serializable by default json code
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj: Object
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (datetime, date)):
|
||||||
|
return obj.floor('1D').strftime(DATE_FORMAT)
|
||||||
|
|
||||||
|
raise TypeError("Type %s not serializable" % type(obj))
|
||||||
|
|
||||||
|
|
||||||
def perf_serial(obj):
|
def perf_serial(obj):
|
||||||
"""
|
"""
|
||||||
JSON serializer for objects not serializable by default json code
|
JSON serializer for objects not serializable by default json code
|
||||||
@@ -590,12 +620,46 @@ def resample_history_df(df, freq, field, start_dt=None):
|
|||||||
return resampled_df
|
return resampled_df
|
||||||
|
|
||||||
|
|
||||||
def from_ms_timestamp(ms):
|
def mixin_market_params(exchange_name, params, market):
|
||||||
return pd.to_datetime(ms, unit='ms', utc=True)
|
"""
|
||||||
|
Applies a CCXT market dict to parameters of TradingPair init.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
params: dict[Object]
|
||||||
|
market: dict[Object]
|
||||||
|
|
||||||
def get_epoch():
|
Returns
|
||||||
return pd.to_datetime('1970-1-1', utc=True)
|
-------
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: make this more externalized / configurable
|
||||||
|
if 'lot' in market:
|
||||||
|
params['min_trade_size'] = market['lot']
|
||||||
|
params['lot'] = market['lot']
|
||||||
|
|
||||||
|
if exchange_name == 'bitfinex':
|
||||||
|
params['maker'] = 0.001
|
||||||
|
params['taker'] = 0.002
|
||||||
|
|
||||||
|
elif 'maker' in market and 'taker' in market and \
|
||||||
|
market['maker'] is not None and market['taker'] is not None:
|
||||||
|
|
||||||
|
params['maker'] = market['maker']
|
||||||
|
params['taker'] = market['taker']
|
||||||
|
|
||||||
|
else:
|
||||||
|
# TODO: default commission, make configurable
|
||||||
|
params['maker'] = 0.0015
|
||||||
|
params['taker'] = 0.0025
|
||||||
|
|
||||||
|
info = market['info'] if 'info' in market else None
|
||||||
|
if info:
|
||||||
|
if 'minimum_order_size' in info:
|
||||||
|
params['min_trade_size'] = float(info['minimum_order_size'])
|
||||||
|
|
||||||
|
if 'lot' not in params:
|
||||||
|
params['lot'] = params['min_trade_size']
|
||||||
|
|
||||||
|
|
||||||
def group_assets_by_exchange(assets):
|
def group_assets_by_exchange(assets):
|
||||||
@@ -662,12 +726,14 @@ def get_candles_df(candles, field, freq, bar_count, end_dt,
|
|||||||
values = [candle[field] for candle in candles[asset]]
|
values = [candle[field] for candle in candles[asset]]
|
||||||
series = pd.Series(values, index=dates)
|
series = pd.Series(values, index=dates)
|
||||||
|
|
||||||
|
"""
|
||||||
series = series.reindex(
|
series = series.reindex(
|
||||||
periods,
|
periods,
|
||||||
method='ffill',
|
method='ffill',
|
||||||
fill_value=previous_value,
|
fill_value=previous_value,
|
||||||
)
|
)
|
||||||
series.sort_index(inplace=True)
|
series.sort_index(inplace=True)
|
||||||
|
"""
|
||||||
all_series[asset] = series
|
all_series[asset] = series
|
||||||
|
|
||||||
df = pd.DataFrame(all_series)
|
df = pd.DataFrame(all_series)
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ from catalyst.constants import LOG_LEVEL
|
|||||||
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
||||||
from catalyst.exchange.exchange import Exchange
|
from catalyst.exchange.exchange import Exchange
|
||||||
from catalyst.exchange.exchange_errors import ExchangeAuthEmpty
|
from catalyst.exchange.exchange_errors import ExchangeAuthEmpty
|
||||||
from catalyst.exchange.utils.ccxt_utils import scan_exchange_configs
|
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_auth, \
|
from catalyst.exchange.utils.exchange_utils import get_exchange_auth, \
|
||||||
get_exchange_folder
|
get_exchange_folder, is_blacklist
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
log = Logger('factory', level=LOG_LEVEL)
|
log = Logger('factory', level=LOG_LEVEL)
|
||||||
@@ -14,7 +13,7 @@ exchange_cache = dict()
|
|||||||
|
|
||||||
|
|
||||||
def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
||||||
skip_init=False, auth_alias=None, config=None):
|
skip_init=False, auth_alias=None):
|
||||||
key = (exchange_name, base_currency)
|
key = (exchange_name, base_currency)
|
||||||
if key in exchange_cache:
|
if key in exchange_cache:
|
||||||
return exchange_cache[key]
|
return exchange_cache[key]
|
||||||
@@ -37,7 +36,6 @@ def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
|||||||
password=exchange_auth['password'] if 'password'
|
password=exchange_auth['password'] if 'password'
|
||||||
in exchange_auth.keys() else '',
|
in exchange_auth.keys() else '',
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
config=config,
|
|
||||||
)
|
)
|
||||||
exchange_cache[key] = exchange
|
exchange_cache[key] = exchange
|
||||||
|
|
||||||
@@ -55,8 +53,8 @@ def get_exchanges(exchange_names):
|
|||||||
return exchanges
|
return exchanges
|
||||||
|
|
||||||
|
|
||||||
def find_exchanges(features=None, history=None, skip_blacklist=True, path=None,
|
def find_exchanges(features=None, skip_blacklist=True, is_authenticated=False,
|
||||||
is_authenticated=False, base_currency=None):
|
base_currency=None):
|
||||||
"""
|
"""
|
||||||
Find exchanges filtered by a list of feature.
|
Find exchanges filtered by a list of feature.
|
||||||
|
|
||||||
@@ -74,33 +72,28 @@ def find_exchanges(features=None, history=None, skip_blacklist=True, path=None,
|
|||||||
list[Exchange]
|
list[Exchange]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
exchange_names = CCXT.find_exchanges(features, is_authenticated)
|
||||||
|
|
||||||
return list(
|
exchanges = []
|
||||||
scan_exchanges(
|
for exchange_name in exchange_names:
|
||||||
features,
|
if skip_blacklist and is_blacklist(exchange_name):
|
||||||
history,
|
|
||||||
skip_blacklist,
|
|
||||||
path,
|
|
||||||
is_authenticated,
|
|
||||||
base_currency
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def scan_exchanges(features=None, history=None, skip_blacklist=True, path=None,
|
|
||||||
is_authenticated=False, base_currency=None):
|
|
||||||
for config in scan_exchange_configs(
|
|
||||||
features=features,
|
|
||||||
history=history,
|
|
||||||
is_authenticated=is_authenticated,
|
|
||||||
path=path,
|
|
||||||
):
|
|
||||||
if skip_blacklist and (config is None or 'error' in config):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield get_exchange(
|
exchange = get_exchange(
|
||||||
exchange_name=config['id'],
|
exchange_name=exchange_name,
|
||||||
skip_init=True,
|
skip_init=True,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
config=config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if features is not None:
|
||||||
|
if 'dailyBundle' in features \
|
||||||
|
and not exchange.has_bundle('daily'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif 'minuteBundle' in features \
|
||||||
|
and not exchange.has_bundle('minute'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
exchanges.append(exchange)
|
||||||
|
|
||||||
|
return exchanges
|
||||||
|
|||||||
@@ -3,42 +3,15 @@ import re
|
|||||||
from json import JSONEncoder
|
from json import JSONEncoder
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.constants import DATE_TIME_FORMAT
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
from datetime import date, datetime
|
|
||||||
from catalyst.constants import DATE_TIME_FORMAT, DATE_FORMAT
|
|
||||||
from catalyst.assets._assets import TradingPair
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigJSONEncoder(json.JSONEncoder):
|
|
||||||
def default(self, obj):
|
|
||||||
"""
|
|
||||||
JSON serializer for objects not serializable by default json code
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
obj: Object
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
str
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(obj, (datetime, date)):
|
|
||||||
return obj.floor('1D').strftime(DATE_FORMAT)
|
|
||||||
|
|
||||||
elif isinstance(obj, TradingPair):
|
|
||||||
return obj.to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class ExchangeJSONEncoder(json.JSONEncoder):
|
class ExchangeJSONEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
if isinstance(obj, pd.Timestamp):
|
if isinstance(obj, pd.Timestamp):
|
||||||
return obj.strftime(DATE_TIME_FORMAT)
|
return obj.strftime(DATE_TIME_FORMAT)
|
||||||
|
|
||||||
elif isinstance(obj, TradingPair):
|
|
||||||
return obj.to_dict()
|
|
||||||
|
|
||||||
# Let the base class default method raise the TypeError
|
# Let the base class default method raise the TypeError
|
||||||
return JSONEncoder.default(self, obj)
|
return JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|||||||
@@ -396,7 +396,8 @@ def email_error(algo_name, dt, e, environ=None):
|
|||||||
)})
|
)})
|
||||||
|
|
||||||
|
|
||||||
def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
def stats_to_algo_folder(stats, algo_namespace,
|
||||||
|
folder_name, recorded_cols=None):
|
||||||
"""
|
"""
|
||||||
Saves the performance stats to the algo local folder.
|
Saves the performance stats to the algo local folder.
|
||||||
|
|
||||||
@@ -404,6 +405,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
|||||||
----------
|
----------
|
||||||
stats: list[Object]
|
stats: list[Object]
|
||||||
algo_namespace: str
|
algo_namespace: str
|
||||||
|
folder_name: str
|
||||||
recorded_cols: list[str]
|
recorded_cols: list[str]
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -416,7 +418,7 @@ def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None):
|
|||||||
timestr = time.strftime('%Y%m%d')
|
timestr = time.strftime('%Y%m%d')
|
||||||
folder = get_algo_folder(algo_namespace)
|
folder = get_algo_folder(algo_namespace)
|
||||||
|
|
||||||
stats_folder = os.path.join(folder, 'stats')
|
stats_folder = os.path.join(folder, folder_name)
|
||||||
ensure_directory(stats_folder)
|
ensure_directory(stats_folder)
|
||||||
|
|
||||||
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
|
filename = os.path.join(stats_folder, '{}.csv'.format(timestr))
|
||||||
|
|||||||
@@ -580,162 +580,8 @@ which you can skim through for now. A copy of this algorithm is available in
|
|||||||
the ``examples`` directory:
|
the ``examples`` directory:
|
||||||
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
|
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
|
||||||
|
|
||||||
.. code-block:: python
|
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
|
||||||
|
:language: python
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
from logbook import Logger
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import (order, record, symbol, order_target_percent,
|
|
||||||
get_open_orders)
|
|
||||||
from catalyst.exchange.utils.stats_utils import extract_transactions
|
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
|
||||||
log = Logger(NAMESPACE)
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.i = 0
|
|
||||||
context.asset = symbol('ltc_usd')
|
|
||||||
context.base_price = None
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
# define the windows for the moving averages
|
|
||||||
short_window = 50
|
|
||||||
long_window = 200
|
|
||||||
|
|
||||||
# Skip as many bars as long_window to properly compute the average
|
|
||||||
context.i += 1
|
|
||||||
if context.i < long_window:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Compute moving averages calling data.history() for each
|
|
||||||
# moving average with the appropriate parameters. We choose to use
|
|
||||||
# minute bars for this simulation -> freq="1m"
|
|
||||||
# Returns a pandas dataframe.
|
|
||||||
short_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=short_window, frequency="1m").mean()
|
|
||||||
long_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=long_window, frequency="1m").mean()
|
|
||||||
|
|
||||||
# Let's keep the price of our asset in a more handy variable
|
|
||||||
price = data.current(context.asset, 'price')
|
|
||||||
|
|
||||||
# If base_price is not set, we use the current value. This is the
|
|
||||||
# price at the first bar which we reference to calculate price_change.
|
|
||||||
if context.base_price is None:
|
|
||||||
context.base_price = price
|
|
||||||
price_change = (price - context.base_price) / context.base_price
|
|
||||||
|
|
||||||
# Save values for later inspection
|
|
||||||
record(price=price,
|
|
||||||
cash=context.portfolio.cash,
|
|
||||||
price_change=price_change,
|
|
||||||
short_mavg=short_mavg,
|
|
||||||
long_mavg=long_mavg)
|
|
||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
|
||||||
# we wait until all orders are executed before considering more trades.
|
|
||||||
orders = get_open_orders(context.asset)
|
|
||||||
if len(orders) > 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Exit if we cannot trade
|
|
||||||
if not data.can_trade(context.asset):
|
|
||||||
return
|
|
||||||
|
|
||||||
# We check what's our position on our portfolio and trade accordingly
|
|
||||||
pos_amount = context.portfolio.positions[context.asset].amount
|
|
||||||
|
|
||||||
# Trading logic
|
|
||||||
if short_mavg > long_mavg and pos_amount == 0:
|
|
||||||
# we buy 100% of our portfolio for this asset
|
|
||||||
order_target_percent(context.asset, 1)
|
|
||||||
elif short_mavg < long_mavg and pos_amount > 0:
|
|
||||||
# we sell all our positions for this asset
|
|
||||||
order_target_percent(context.asset, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context, perf):
|
|
||||||
|
|
||||||
# Get the base_currency that was passed as a parameter to the simulation
|
|
||||||
exchange = list(context.exchanges.values())[0]
|
|
||||||
base_currency = exchange.base_currency.upper()
|
|
||||||
|
|
||||||
# First chart: Plot portfolio value using base_currency
|
|
||||||
ax1 = plt.subplot(411)
|
|
||||||
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
|
|
||||||
ax1.legend_.remove()
|
|
||||||
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
|
|
||||||
start, end = ax1.get_ylim()
|
|
||||||
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Second chart: Plot asset price, moving averages and buys/sells
|
|
||||||
ax2 = plt.subplot(412, sharex=ax1)
|
|
||||||
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
|
||||||
ax2.legend_.remove()
|
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
|
||||||
asset = context.asset.symbol,
|
|
||||||
base = base_currency
|
|
||||||
))
|
|
||||||
start, end = ax2.get_ylim()
|
|
||||||
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
transaction_df = extract_transactions(perf)
|
|
||||||
if not transaction_df.empty:
|
|
||||||
buy_df = transaction_df[transaction_df['amount'] > 0]
|
|
||||||
sell_df = transaction_df[transaction_df['amount'] < 0]
|
|
||||||
ax2.scatter(
|
|
||||||
buy_df.index.to_pydatetime(),
|
|
||||||
perf.loc[buy_df.index, 'price'],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='green',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
ax2.scatter(
|
|
||||||
sell_df.index.to_pydatetime(),
|
|
||||||
perf.loc[sell_df.index, 'price'],
|
|
||||||
marker='v',
|
|
||||||
s=100,
|
|
||||||
c='red',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
|
|
||||||
# Third chart: Compare percentage change between our portfolio
|
|
||||||
# and the price of the asset
|
|
||||||
ax3 = plt.subplot(413, sharex=ax1)
|
|
||||||
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
|
|
||||||
ax3.legend_.remove()
|
|
||||||
ax3.set_ylabel('Percent Change')
|
|
||||||
start, end = ax3.get_ylim()
|
|
||||||
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Fourth chart: Plot our cash
|
|
||||||
ax4 = plt.subplot(414, sharex=ax1)
|
|
||||||
perf.cash.plot(ax=ax4)
|
|
||||||
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
|
||||||
start, end = ax4.get_ylim()
|
|
||||||
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
|
|
||||||
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=1000,
|
|
||||||
data_frequency='minute',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace=NAMESPACE,
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2017-9-22', utc=True),
|
|
||||||
end=pd.to_datetime('2017-9-23', utc=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
In order to run the code above, you have to ingest the needed data first:
|
In order to run the code above, you have to ingest the needed data first:
|
||||||
|
|
||||||
|
|||||||
+17
-881
@@ -52,35 +52,8 @@ Buy BTC Simple Algorithm
|
|||||||
|
|
||||||
Source code: `examples/buy_btc_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_btc_simple.py>`_
|
Source code: `examples/buy_btc_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_btc_simple.py>`_
|
||||||
|
|
||||||
.. code-block:: python
|
.. literalinclude:: ../../catalyst/examples/buy_btc_simple.py
|
||||||
|
:language: python
|
||||||
'''
|
|
||||||
Run this example, by executing the following from your terminal:
|
|
||||||
catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt
|
|
||||||
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
|
||||||
|
|
||||||
If you want to run this code using another exchange, make sure that
|
|
||||||
the asset is available on that exchange. For example, if you were to run
|
|
||||||
it for exchange Poloniex, you would need to edit the following line:
|
|
||||||
|
|
||||||
context.asset = symbol('btc_usdt') # note 'usdt' instead of 'usd'
|
|
||||||
|
|
||||||
and specify exchange poloniex as follows:
|
|
||||||
catalyst ingest-exchange -x poloniex -f daily -i btc_usdt
|
|
||||||
catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle
|
|
||||||
|
|
||||||
To see which assets are available on each exchange, visit:
|
|
||||||
https://www.enigma.co/catalyst/status
|
|
||||||
'''
|
|
||||||
|
|
||||||
from catalyst.api import order, record, symbol
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.asset = symbol('btc_usd')
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
order(context.asset, 1)
|
|
||||||
record(btc = data.current(context.asset, 'price'))
|
|
||||||
|
|
||||||
This simple algorithm does not produce any output nor displays any chart.
|
This simple algorithm does not produce any output nor displays any chart.
|
||||||
|
|
||||||
@@ -90,8 +63,6 @@ This simple algorithm does not produce any output nor displays any chart.
|
|||||||
Buy and Hodl Algorithm
|
Buy and Hodl Algorithm
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Source code: `examples/buy_and_hodl.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_and_hodl.py>`_
|
|
||||||
|
|
||||||
First ingest the historical pricing data needed to run this algorithm:
|
First ingest the historical pricing data needed to run this algorithm:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
@@ -119,157 +90,10 @@ that 2015-3-1 is the earliest date that Catalyst supports (if you choose an
|
|||||||
earlier date, you'll get an error), and the most recent date you can choose is
|
earlier date, you'll get an error), and the most recent date you can choose is
|
||||||
one day prior to the current date.
|
one day prior to the current date.
|
||||||
|
|
||||||
|
Source code: `examples/buy_and_hodl.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_and_hodl.py>`_
|
||||||
|
|
||||||
.. code-block:: python
|
.. literalinclude:: ../../catalyst/examples/buy_and_hodl.py
|
||||||
|
:language: python
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright 2017 Enigma MPC, Inc.
|
|
||||||
# Copyright 2015 Quantopian, Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
import pandas as pd
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import (order_target_value, symbol, record,
|
|
||||||
cancel_order, get_open_orders, )
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.ASSET_NAME = 'btc_usd'
|
|
||||||
context.TARGET_HODL_RATIO = 0.8
|
|
||||||
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
|
|
||||||
|
|
||||||
context.is_buying = True
|
|
||||||
context.asset = symbol(context.ASSET_NAME)
|
|
||||||
|
|
||||||
context.i = 0
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
context.i += 1
|
|
||||||
|
|
||||||
starting_cash = context.portfolio.starting_cash
|
|
||||||
target_hodl_value = context.TARGET_HODL_RATIO * starting_cash
|
|
||||||
reserve_value = context.RESERVE_RATIO * starting_cash
|
|
||||||
|
|
||||||
# Cancel any outstanding orders
|
|
||||||
orders = get_open_orders(context.asset) or []
|
|
||||||
for order in orders:
|
|
||||||
cancel_order(order)
|
|
||||||
|
|
||||||
# Stop buying after passing the reserve threshold
|
|
||||||
cash = context.portfolio.cash
|
|
||||||
if cash <= reserve_value:
|
|
||||||
context.is_buying = False
|
|
||||||
|
|
||||||
# Retrieve current asset price from pricing data
|
|
||||||
price = data.current(context.asset, 'price')
|
|
||||||
|
|
||||||
# Check if still buying and could (approximately) afford another purchase
|
|
||||||
if context.is_buying and cash > price:
|
|
||||||
print('buying')
|
|
||||||
# Place order to make position in asset equal to target_hodl_value
|
|
||||||
order_target_value(
|
|
||||||
context.asset,
|
|
||||||
target_hodl_value,
|
|
||||||
limit_price=price * 1.1,
|
|
||||||
)
|
|
||||||
|
|
||||||
record(
|
|
||||||
price=price,
|
|
||||||
volume=data.current(context.asset, 'volume'),
|
|
||||||
cash=cash,
|
|
||||||
starting_cash=context.portfolio.starting_cash,
|
|
||||||
leverage=context.account.leverage,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
|
||||||
|
|
||||||
# Plot the portfolio and asset data.
|
|
||||||
ax1 = plt.subplot(611)
|
|
||||||
results[['portfolio_value']].plot(ax=ax1)
|
|
||||||
ax1.set_ylabel('Portfolio Value (USD)')
|
|
||||||
|
|
||||||
ax2 = plt.subplot(612, sharex=ax1)
|
|
||||||
ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME))
|
|
||||||
results[['price']].plot(ax=ax2)
|
|
||||||
|
|
||||||
trans = results.ix[[t != [] for t in results.transactions]]
|
|
||||||
buys = trans.ix[
|
|
||||||
[t[0]['amount'] > 0 for t in trans.transactions]
|
|
||||||
]
|
|
||||||
ax2.scatter(
|
|
||||||
buys.index.to_pydatetime(),
|
|
||||||
results.price[buys.index],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='g',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
|
|
||||||
ax3 = plt.subplot(613, sharex=ax1)
|
|
||||||
results[['leverage', 'alpha', 'beta']].plot(ax=ax3)
|
|
||||||
ax3.set_ylabel('Leverage ')
|
|
||||||
|
|
||||||
ax4 = plt.subplot(614, sharex=ax1)
|
|
||||||
results[['starting_cash', 'cash']].plot(ax=ax4)
|
|
||||||
ax4.set_ylabel('Cash (USD)')
|
|
||||||
|
|
||||||
results[[
|
|
||||||
'treasury',
|
|
||||||
'algorithm',
|
|
||||||
'benchmark',
|
|
||||||
]] = results[[
|
|
||||||
'treasury_period_return',
|
|
||||||
'algorithm_period_return',
|
|
||||||
'benchmark_period_return',
|
|
||||||
]]
|
|
||||||
|
|
||||||
ax5 = plt.subplot(615, sharex=ax1)
|
|
||||||
results[[
|
|
||||||
'treasury',
|
|
||||||
'algorithm',
|
|
||||||
'benchmark',
|
|
||||||
]].plot(ax=ax5)
|
|
||||||
ax5.set_ylabel('Percent Change')
|
|
||||||
|
|
||||||
ax6 = plt.subplot(616, sharex=ax1)
|
|
||||||
results[['volume']].plot(ax=ax6)
|
|
||||||
ax6.set_ylabel('Volume (mCoins/5min)')
|
|
||||||
|
|
||||||
plt.legend(loc=3)
|
|
||||||
|
|
||||||
# Show the plot.
|
|
||||||
plt.gcf().set_size_inches(18, 8)
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=10000,
|
|
||||||
data_frequency='daily',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace='buy_and_hodl',
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2015-03-01', utc=True),
|
|
||||||
end=pd.to_datetime('2017-10-31', utc=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_buy_and_hodl.png
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_buy_and_hodl.png
|
||||||
|
|
||||||
@@ -278,166 +102,13 @@ one day prior to the current date.
|
|||||||
Dual Moving Average Crossover
|
Dual Moving Average Crossover
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Source Code: `examples/dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_
|
|
||||||
|
|
||||||
This strategy is covered in detail in the last part of
|
This strategy is covered in detail in the last part of
|
||||||
`this tutorial <beginner-tutorial.html#history>`_.
|
`this tutorial <beginner-tutorial.html#history>`_.
|
||||||
|
|
||||||
.. code-block:: python
|
Source Code: `examples/dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_
|
||||||
|
|
||||||
import numpy as np
|
.. literalinclude:: ../../catalyst/examples/dual_moving_average.py
|
||||||
import pandas as pd
|
:language: python
|
||||||
from logbook import Logger
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import (order, record, symbol, order_target_percent,
|
|
||||||
get_open_orders)
|
|
||||||
from catalyst.exchange.stats_utils import extract_transactions
|
|
||||||
|
|
||||||
NAMESPACE = 'dual_moving_average'
|
|
||||||
log = Logger(NAMESPACE)
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.i = 0
|
|
||||||
context.asset = symbol('ltc_usd')
|
|
||||||
context.base_price = None
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
# define the windows for the moving averages
|
|
||||||
short_window = 50
|
|
||||||
long_window = 200
|
|
||||||
|
|
||||||
# Skip as many bars as long_window to properly compute the average
|
|
||||||
context.i += 1
|
|
||||||
if context.i < long_window:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Compute moving averages calling data.history() for each
|
|
||||||
# moving average with the appropriate parameters. We choose to use
|
|
||||||
# minute bars for this simulation -> freq="1m"
|
|
||||||
# Returns a pandas dataframe.
|
|
||||||
short_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=short_window, frequency="1m").mean()
|
|
||||||
long_mavg = data.history(context.asset, 'price',
|
|
||||||
bar_count=long_window, frequency="1m").mean()
|
|
||||||
|
|
||||||
# Let's keep the price of our asset in a more handy variable
|
|
||||||
price = data.current(context.asset, 'price')
|
|
||||||
|
|
||||||
# If base_price is not set, we use the current value. This is the
|
|
||||||
# price at the first bar which we reference to calculate price_change.
|
|
||||||
if context.base_price is None:
|
|
||||||
context.base_price = price
|
|
||||||
price_change = (price - context.base_price) / context.base_price
|
|
||||||
|
|
||||||
# Save values for later inspection
|
|
||||||
record(price=price,
|
|
||||||
cash=context.portfolio.cash,
|
|
||||||
price_change=price_change,
|
|
||||||
short_mavg=short_mavg,
|
|
||||||
long_mavg=long_mavg)
|
|
||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
|
||||||
# we wait until all orders are executed before considering more trades.
|
|
||||||
orders = get_open_orders(context.asset)
|
|
||||||
if len(orders) > 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Exit if we cannot trade
|
|
||||||
if not data.can_trade(context.asset):
|
|
||||||
return
|
|
||||||
|
|
||||||
# We check what's our position on our portfolio and trade accordingly
|
|
||||||
pos_amount = context.portfolio.positions[context.asset].amount
|
|
||||||
|
|
||||||
# Trading logic
|
|
||||||
if short_mavg > long_mavg and pos_amount == 0:
|
|
||||||
# we buy 100% of our portfolio for this asset
|
|
||||||
order_target_percent(context.asset, 1)
|
|
||||||
elif short_mavg < long_mavg and pos_amount > 0:
|
|
||||||
# we sell all our positions for this asset
|
|
||||||
order_target_percent(context.asset, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context, perf):
|
|
||||||
|
|
||||||
# Get the base_currency that was passed as a parameter to the simulation
|
|
||||||
base_currency = context.exchanges.values()[0].base_currency.upper()
|
|
||||||
|
|
||||||
# First chart: Plot portfolio value using base_currency
|
|
||||||
ax1 = plt.subplot(411)
|
|
||||||
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
|
|
||||||
ax1.legend_.remove()
|
|
||||||
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
|
|
||||||
start, end = ax1.get_ylim()
|
|
||||||
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Second chart: Plot asset price, moving averages and buys/sells
|
|
||||||
ax2 = plt.subplot(412, sharex=ax1)
|
|
||||||
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
|
||||||
ax2.legend_.remove()
|
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
|
||||||
asset = context.asset.symbol,
|
|
||||||
base = base_currency
|
|
||||||
))
|
|
||||||
start, end = ax2.get_ylim()
|
|
||||||
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
transaction_df = extract_transactions(perf)
|
|
||||||
if not transaction_df.empty:
|
|
||||||
buy_df = transaction_df[transaction_df['amount'] > 0]
|
|
||||||
sell_df = transaction_df[transaction_df['amount'] < 0]
|
|
||||||
ax2.scatter(
|
|
||||||
buy_df.index.to_pydatetime(),
|
|
||||||
perf.loc[buy_df.index, 'price'],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='green',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
ax2.scatter(
|
|
||||||
sell_df.index.to_pydatetime(),
|
|
||||||
perf.loc[sell_df.index, 'price'],
|
|
||||||
marker='v',
|
|
||||||
s=100,
|
|
||||||
c='red',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
|
|
||||||
# Third chart: Compare percentage change between our portfolio
|
|
||||||
# and the price of the asset
|
|
||||||
ax3 = plt.subplot(413, sharex=ax1)
|
|
||||||
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
|
|
||||||
ax3.legend_.remove()
|
|
||||||
ax3.set_ylabel('Percent Change')
|
|
||||||
start, end = ax3.get_ylim()
|
|
||||||
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
|
||||||
|
|
||||||
# Fourth chart: Plot our cash
|
|
||||||
ax4 = plt.subplot(414, sharex=ax1)
|
|
||||||
perf.cash.plot(ax=ax4)
|
|
||||||
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
|
||||||
start, end = ax4.get_ylim()
|
|
||||||
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
|
|
||||||
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=1000,
|
|
||||||
data_frequency='minute',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace=NAMESPACE,
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2017-9-22', utc=True),
|
|
||||||
end=pd.to_datetime('2017-9-23', utc=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/tutorial_dual_moving_average.png
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/tutorial_dual_moving_average.png
|
||||||
|
|
||||||
@@ -447,8 +118,6 @@ This strategy is covered in detail in the last part of
|
|||||||
Mean Reversion Algorithm
|
Mean Reversion Algorithm
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Source code: `examples/mean_reversion_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/mean_reversion_simple.py>`_
|
|
||||||
|
|
||||||
This algorithm is based on a simple momentum strategy. When the cryptoasset goes
|
This algorithm is based on a simple momentum strategy. When the cryptoasset goes
|
||||||
up quickly, we're going to buy; when it goes down quickly, we're going to sell.
|
up quickly, we're going to buy; when it goes down quickly, we're going to sell.
|
||||||
Hopefully, we'll ride the waves.
|
Hopefully, we'll ride the waves.
|
||||||
@@ -469,284 +138,10 @@ lines 218-245, so in order to run the algorithm we just type:
|
|||||||
|
|
||||||
python mean_reversion_simple.py
|
python mean_reversion_simple.py
|
||||||
|
|
||||||
.. code-block:: python
|
Source code: `examples/mean_reversion_simple.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/mean_reversion_simple.py>`_
|
||||||
|
|
||||||
import os
|
.. literalinclude:: ../../catalyst/examples/mean_reversion_simple.py
|
||||||
import tempfile
|
:language: python
|
||||||
import time
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
import talib
|
|
||||||
from logbook import Logger
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.api import symbol, record, order_target_percent, get_open_orders
|
|
||||||
from catalyst.exchange.stats_utils import extract_transactions
|
|
||||||
# We give a name to the algorithm which Catalyst will use to persist its state.
|
|
||||||
# In this example, Catalyst will create the `.catalyst/data/live_algos`
|
|
||||||
# directory. If we stop and start the algorithm, Catalyst will resume its
|
|
||||||
# state using the files included in the folder.
|
|
||||||
from catalyst.utils.paths import ensure_directory
|
|
||||||
|
|
||||||
NAMESPACE = 'mean_reversion_simple'
|
|
||||||
log = Logger(NAMESPACE)
|
|
||||||
|
|
||||||
|
|
||||||
# To run an algorithm in Catalyst, you need two functions: initialize and
|
|
||||||
# handle_data.
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
# This initialize function sets any data or variables that you'll use in
|
|
||||||
# your algorithm. For instance, you'll want to define the trading pair (or
|
|
||||||
# trading pairs) you want to backtest. You'll also want to define any
|
|
||||||
# parameters or values you're going to use.
|
|
||||||
|
|
||||||
# In our example, we're looking at Neo in USD.
|
|
||||||
context.neo_eth = symbol('neo_usd')
|
|
||||||
context.base_price = None
|
|
||||||
context.current_day = None
|
|
||||||
|
|
||||||
context.RSI_OVERSOLD = 30
|
|
||||||
context.RSI_OVERBOUGHT = 80
|
|
||||||
context.CANDLE_SIZE = '15T'
|
|
||||||
|
|
||||||
context.start_time = time.time()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
# This handle_data function is where the real work is done. Our data is
|
|
||||||
# minute-level tick data, and each minute is called a frame. This function
|
|
||||||
# runs on each frame of the data.
|
|
||||||
|
|
||||||
# We flag the first period of each day.
|
|
||||||
# Since cryptocurrencies trade 24/7 the `before_trading_starts` handle
|
|
||||||
# would only execute once. This method works with minute and daily
|
|
||||||
# frequencies.
|
|
||||||
today = data.current_dt.floor('1D')
|
|
||||||
if today != context.current_day:
|
|
||||||
context.traded_today = False
|
|
||||||
context.current_day = today
|
|
||||||
|
|
||||||
# We're computing the volume-weighted-average-price of the security
|
|
||||||
# defined above, in the context.neo_eth variable. For this example, we're
|
|
||||||
# using three bars on the 15 min bars.
|
|
||||||
|
|
||||||
# The frequency attribute determine the bar size. We use this convention
|
|
||||||
# for the frequency alias:
|
|
||||||
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
|
|
||||||
prices = data.history(
|
|
||||||
context.neo_eth,
|
|
||||||
fields='close',
|
|
||||||
bar_count=50,
|
|
||||||
frequency=context.CANDLE_SIZE
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ta-lib calculates various technical indicator based on price and
|
|
||||||
# volume arrays.
|
|
||||||
|
|
||||||
# In this example, we are comp
|
|
||||||
rsi = talib.RSI(prices.values, timeperiod=14)
|
|
||||||
|
|
||||||
# We need a variable for the current price of the security to compare to
|
|
||||||
# the average. Since we are requesting two fields, data.current()
|
|
||||||
# returns a DataFrame with
|
|
||||||
current = data.current(context.neo_eth, fields=['close', 'volume'])
|
|
||||||
price = current['close']
|
|
||||||
|
|
||||||
# If base_price is not set, we use the current value. This is the
|
|
||||||
# price at the first bar which we reference to calculate price_change.
|
|
||||||
if context.base_price is None:
|
|
||||||
context.base_price = price
|
|
||||||
|
|
||||||
price_change = (price - context.base_price) / context.base_price
|
|
||||||
cash = context.portfolio.cash
|
|
||||||
|
|
||||||
# Now that we've collected all current data for this frame, we use
|
|
||||||
# the record() method to save it. This data will be available as
|
|
||||||
# a parameter of the analyze() function for further analysis.
|
|
||||||
record(
|
|
||||||
price=price,
|
|
||||||
volume=current['volume'],
|
|
||||||
price_change=price_change,
|
|
||||||
rsi=rsi[-1],
|
|
||||||
cash=cash
|
|
||||||
)
|
|
||||||
|
|
||||||
# We are trying to avoid over-trading by limiting our trades to
|
|
||||||
# one per day.
|
|
||||||
if context.traded_today:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Since we are using limit orders, some orders may not execute immediately
|
|
||||||
# we wait until all orders are executed before considering more trades.
|
|
||||||
orders = get_open_orders(context.neo_eth)
|
|
||||||
if len(orders) > 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Exit if we cannot trade
|
|
||||||
if not data.can_trade(context.neo_eth):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Another powerful built-in feature of the Catalyst backtester is the
|
|
||||||
# portfolio object. The portfolio object tracks your positions, cash,
|
|
||||||
# cost basis of specific holdings, and more. In this line, we calculate
|
|
||||||
# how long or short our position is at this minute.
|
|
||||||
pos_amount = context.portfolio.positions[context.neo_eth].amount
|
|
||||||
|
|
||||||
if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0:
|
|
||||||
log.info(
|
|
||||||
'{}: buying - price: {}, rsi: {}'.format(
|
|
||||||
data.current_dt, price, rsi[-1]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# Set a style for limit orders,
|
|
||||||
limit_price = price * 1.005
|
|
||||||
order_target_percent(
|
|
||||||
context.neo_eth, 1, limit_price=limit_price
|
|
||||||
)
|
|
||||||
context.traded_today = True
|
|
||||||
|
|
||||||
elif rsi[-1] >= context.RSI_OVERBOUGHT and pos_amount > 0:
|
|
||||||
log.info(
|
|
||||||
'{}: selling - price: {}, rsi: {}'.format(
|
|
||||||
data.current_dt, price, rsi[-1]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
limit_price = price * 0.995
|
|
||||||
order_target_percent(
|
|
||||||
context.neo_eth, 0, limit_price=limit_price
|
|
||||||
)
|
|
||||||
context.traded_today = True
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, perf=None):
|
|
||||||
end = time.time()
|
|
||||||
log.info('elapsed time: {}'.format(end - context.start_time))
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
# The base currency of the algo exchange
|
|
||||||
base_currency = context.exchanges.values()[0].base_currency.upper()
|
|
||||||
|
|
||||||
# Plot the portfolio value over time.
|
|
||||||
ax1 = plt.subplot(611)
|
|
||||||
perf.loc[:, 'portfolio_value'].plot(ax=ax1)
|
|
||||||
ax1.set_ylabel('Portfolio\nValue\n({})'.format(base_currency))
|
|
||||||
|
|
||||||
# Plot the price increase or decrease over time.
|
|
||||||
ax2 = plt.subplot(612, sharex=ax1)
|
|
||||||
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
|
|
||||||
|
|
||||||
ax2.set_ylabel('{asset}\n({base})'.format(
|
|
||||||
asset=context.neo_eth.symbol, base=base_currency
|
|
||||||
))
|
|
||||||
|
|
||||||
transaction_df = extract_transactions(perf)
|
|
||||||
if not transaction_df.empty:
|
|
||||||
buy_df = transaction_df[transaction_df['amount'] > 0]
|
|
||||||
sell_df = transaction_df[transaction_df['amount'] < 0]
|
|
||||||
ax2.scatter(
|
|
||||||
buy_df.index.to_pydatetime(),
|
|
||||||
perf.loc[buy_df.index.floor('1 min'), 'price'],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='green',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
ax2.scatter(
|
|
||||||
sell_df.index.to_pydatetime(),
|
|
||||||
perf.loc[sell_df.index.floor('1 min'), 'price'],
|
|
||||||
marker='v',
|
|
||||||
s=100,
|
|
||||||
c='red',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
|
|
||||||
ax4 = plt.subplot(613, sharex=ax1)
|
|
||||||
perf.loc[:, 'cash'].plot(
|
|
||||||
ax=ax4, label='Base Currency ({})'.format(base_currency)
|
|
||||||
)
|
|
||||||
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
|
||||||
|
|
||||||
perf['algorithm'] = perf.loc[:, 'algorithm_period_return']
|
|
||||||
|
|
||||||
ax5 = plt.subplot(614, sharex=ax1)
|
|
||||||
perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
|
|
||||||
ax5.set_ylabel('Percent\nChange')
|
|
||||||
|
|
||||||
ax6 = plt.subplot(615, sharex=ax1)
|
|
||||||
perf.loc[:, 'rsi'].plot(ax=ax6, label='RSI')
|
|
||||||
ax6.set_ylabel('RSI')
|
|
||||||
ax6.axhline(context.RSI_OVERBOUGHT, color='darkgoldenrod')
|
|
||||||
ax6.axhline(context.RSI_OVERSOLD, color='darkgoldenrod')
|
|
||||||
|
|
||||||
if not transaction_df.empty:
|
|
||||||
ax6.scatter(
|
|
||||||
buy_df.index.to_pydatetime(),
|
|
||||||
perf.loc[buy_df.index.floor('1 min'), 'rsi'],
|
|
||||||
marker='^',
|
|
||||||
s=100,
|
|
||||||
c='green',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
ax6.scatter(
|
|
||||||
sell_df.index.to_pydatetime(),
|
|
||||||
perf.loc[sell_df.index.floor('1 min'), 'rsi'],
|
|
||||||
marker='v',
|
|
||||||
s=100,
|
|
||||||
c='red',
|
|
||||||
label=''
|
|
||||||
)
|
|
||||||
plt.legend(loc=3)
|
|
||||||
start, end = ax6.get_ylim()
|
|
||||||
ax6.yaxis.set_ticks(np.arange(0, end, end/5))
|
|
||||||
|
|
||||||
# Show the plot.
|
|
||||||
plt.gcf().set_size_inches(18, 8)
|
|
||||||
plt.show()
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# The execution mode: backtest or live
|
|
||||||
MODE = 'backtest'
|
|
||||||
|
|
||||||
if MODE == 'backtest':
|
|
||||||
folder = os.path.join(
|
|
||||||
tempfile.gettempdir(), 'catalyst', NAMESPACE
|
|
||||||
)
|
|
||||||
ensure_directory(folder)
|
|
||||||
|
|
||||||
timestr = time.strftime('%Y%m%d-%H%M%S')
|
|
||||||
out = os.path.join(folder, '{}.p'.format(timestr))
|
|
||||||
# catalyst run -f catalyst/examples/mean_reversion_simple.py -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=10000,
|
|
||||||
data_frequency='minute',
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
algo_namespace=NAMESPACE,
|
|
||||||
base_currency='usd',
|
|
||||||
start=pd.to_datetime('2017-10-01', utc=True),
|
|
||||||
end=pd.to_datetime('2017-11-10', utc=True),
|
|
||||||
output=out
|
|
||||||
)
|
|
||||||
log.info('saved perf stats: {}'.format(out))
|
|
||||||
|
|
||||||
elif MODE == 'live':
|
|
||||||
run_algorithm(
|
|
||||||
capital_base=0.5,
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bittrex',
|
|
||||||
live=True,
|
|
||||||
algo_namespace=NAMESPACE,
|
|
||||||
base_currency='usd',
|
|
||||||
live_graph=False
|
|
||||||
)
|
|
||||||
|
|
||||||
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_mean_reversion_simple.png
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/example_mean_reversion_simple.png
|
||||||
|
|
||||||
@@ -763,8 +158,6 @@ strategy.
|
|||||||
Simple Universe
|
Simple Universe
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/simple_universe.py>`_
|
|
||||||
|
|
||||||
This example aims to provide an easy way for users to learn how to
|
This example aims to provide an easy way for users to learn how to
|
||||||
collect data from any given exchange and select a subset of the available
|
collect data from any given exchange and select a subset of the available
|
||||||
currency pairs for trading. You simply need to specify the exchange and
|
currency pairs for trading. You simply need to specify the exchange and
|
||||||
@@ -791,142 +184,10 @@ of the file:
|
|||||||
|
|
||||||
catalyst ingest-exchange -x bitfinex -f minute
|
catalyst ingest-exchange -x bitfinex -f minute
|
||||||
|
|
||||||
.. code-block:: bash
|
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/simple_universe.py>`_
|
||||||
|
|
||||||
python simple_universe.py
|
|
||||||
|
|
||||||
Credits: This code was originally submitted by `Abner Ayala-Acevedo
|
|
||||||
<https://github.com/abnera>`_. Thank you!
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from catalyst import run_algorithm
|
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols
|
|
||||||
from catalyst.api import (symbols, )
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
context.i = -1 # minute counter
|
|
||||||
context.exchange = context.exchanges.values()[0].name.lower()
|
|
||||||
context.base_currency = context.exchanges.values()[0].base_currency.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
context.i += 1
|
|
||||||
lookback_days = 7 # 7 days
|
|
||||||
|
|
||||||
# current date & time in each iteration formatted into a string
|
|
||||||
now = data.current_dt
|
|
||||||
date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ')
|
|
||||||
lookback_date = now - timedelta(days=lookback_days)
|
|
||||||
# keep only the date as a string, discard the time
|
|
||||||
lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0]
|
|
||||||
|
|
||||||
one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute'
|
|
||||||
# update universe everyday at midnight
|
|
||||||
if not context.i % one_day_in_minutes:
|
|
||||||
context.universe = universe(context, lookback_date, date)
|
|
||||||
|
|
||||||
# get data every 30 minutes
|
|
||||||
minutes = 30
|
|
||||||
# get lookback_days of history data: that is 'lookback' number of bins
|
|
||||||
lookback = one_day_in_minutes / minutes * lookback_days
|
|
||||||
if not context.i % minutes and context.universe:
|
|
||||||
# we iterate for every pair in the current universe
|
|
||||||
for coin in context.coins:
|
|
||||||
pair = str(coin.symbol)
|
|
||||||
|
|
||||||
# Get 30 minute interval OHLCV data. This is the standard data
|
|
||||||
# required for candlestick or indicators/signals. Return Pandas
|
|
||||||
# DataFrames. 30T means 30-minute re-sampling of one minute data.
|
|
||||||
# Adjust it to your desired time interval as needed.
|
|
||||||
opened = fill(data.history(coin, 'open',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
high = fill(data.history(coin, 'high',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
low = fill(data.history(coin, 'low',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
close = fill(data.history(coin, 'price',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
volume = fill(data.history(coin, 'volume',
|
|
||||||
bar_count=lookback, frequency='30T')).values
|
|
||||||
|
|
||||||
# close[-1] is the last value in the set, which is the equivalent
|
|
||||||
# to current price (as in the most recent value)
|
|
||||||
# displays the minute price for each pair every 30 minutes
|
|
||||||
print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},\tV:{v}'.format(
|
|
||||||
now=now,
|
|
||||||
pair=pair,
|
|
||||||
o=opened[-1],
|
|
||||||
h=high[-1],
|
|
||||||
l=low[-1],
|
|
||||||
c=close[-1],
|
|
||||||
v=volume[-1],
|
|
||||||
))
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
# --------------- Insert Your Strategy Here -------------------
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Get the universe for a given exchange and a given base_currency market
|
|
||||||
# Example: Poloniex BTC Market
|
|
||||||
def universe(context, lookback_date, current_date):
|
|
||||||
# get all the pairs for the given exchange
|
|
||||||
json_symbols = get_exchange_symbols(context.exchange)
|
|
||||||
# convert into a DataFrame for easier processing
|
|
||||||
df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str)
|
|
||||||
df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],axis=1)
|
|
||||||
df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],axis=1)
|
|
||||||
|
|
||||||
# Filter all the pairs to get only the ones for a given base_currency
|
|
||||||
df = df[df['base_currency'] == context.base_currency]
|
|
||||||
|
|
||||||
# Filter all the pairs to ensure that pair existed in the current date range
|
|
||||||
df = df[df.start_date < lookback_date]
|
|
||||||
df = df[df.end_daily >= current_date]
|
|
||||||
context.coins = symbols(*df.symbol) # convert all the pairs to symbols
|
|
||||||
|
|
||||||
return df.symbol.tolist()
|
|
||||||
|
|
||||||
|
|
||||||
# Replace all NA, NAN or infinite values with its nearest value
|
|
||||||
def fill(series):
|
|
||||||
if isinstance(series, pd.Series):
|
|
||||||
return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
|
|
||||||
elif isinstance(series, np.ndarray):
|
|
||||||
return pd.Series(series).replace(
|
|
||||||
[np.inf, -np.inf], np.nan
|
|
||||||
).ffill().bfill().values
|
|
||||||
else:
|
|
||||||
return series
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
start_date = pd.to_datetime('2017-11-10', utc=True)
|
|
||||||
end_date = pd.to_datetime('2017-11-13', utc=True)
|
|
||||||
|
|
||||||
performance = run_algorithm(start=start_date, end=end_date,
|
|
||||||
capital_base=100.0, # amount of base_currency
|
|
||||||
initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
exchange_name='bitfinex',
|
|
||||||
data_frequency='minute',
|
|
||||||
base_currency='btc',
|
|
||||||
live=False,
|
|
||||||
live_graph=False,
|
|
||||||
algo_namespace='simple_universe')
|
|
||||||
|
|
||||||
|
.. literalinclude:: ../../catalyst/examples/simple_universe.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
|
||||||
.. _portfolio_optimization:
|
.. _portfolio_optimization:
|
||||||
@@ -940,135 +201,10 @@ use 180 days of historical data and rebalance every 30 days. This code was used
|
|||||||
in writting the following article:
|
in writting the following article:
|
||||||
`Markowitz Portfolio Optimization for Cryptocurrencies <https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556>`_.
|
`Markowitz Portfolio Optimization for Cryptocurrencies <https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556>`_.
|
||||||
|
|
||||||
.. code-block:: python
|
Source code: `examples/simple_universe.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/portfolio_optimization.py>`_
|
||||||
|
|
||||||
'''
|
.. literalinclude:: ../../catalyst/examples/portfolio_optimization.py
|
||||||
You can run this code using the Python interpreter:
|
:language: python
|
||||||
|
|
||||||
$ python portfolio_optimization.py
|
|
||||||
'''
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
import os
|
|
||||||
import pytz
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
from scipy.optimize import minimize
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from catalyst.api import record, symbol, symbols, order_target_percent
|
|
||||||
from catalyst.utils.run_algo import run_algorithm
|
|
||||||
|
|
||||||
np.set_printoptions(threshold='nan', suppress=True)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
|
||||||
# Portfolio assets list
|
|
||||||
context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt',
|
|
||||||
'xmr_usdt')
|
|
||||||
context.nassets = len(context.assets)
|
|
||||||
# Set the time window that will be used to compute expected return
|
|
||||||
# and asset correlations
|
|
||||||
context.window = 180
|
|
||||||
# Set the number of days between each portfolio rebalancing
|
|
||||||
context.rebalance_period = 30
|
|
||||||
context.i = 0
|
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
|
||||||
# Only rebalance at the beggining of the algorithm execution and
|
|
||||||
# every multiple of the rebalance period
|
|
||||||
if context.i == 0 or context.i%context.rebalance_period == 0:
|
|
||||||
n = context.window
|
|
||||||
prices = data.history(context.assets, fields='price',
|
|
||||||
bar_count=n+1, frequency='1d')
|
|
||||||
pr = np.asmatrix(prices)
|
|
||||||
t_prices = prices.iloc[1:n+1]
|
|
||||||
t_val = t_prices.values
|
|
||||||
tminus_prices = prices.iloc[0:n]
|
|
||||||
tminus_val = tminus_prices.values
|
|
||||||
# Compute daily returns (r)
|
|
||||||
r = np.asmatrix(t_val/tminus_val-1)
|
|
||||||
# Compute the expected returns of each asset with the average
|
|
||||||
# daily return for the selected time window
|
|
||||||
m = np.asmatrix(np.mean(r, axis=0))
|
|
||||||
# ###
|
|
||||||
stds = np.std(r, axis=0)
|
|
||||||
# Compute excess returns matrix (xr)
|
|
||||||
xr = r - m
|
|
||||||
# Matrix algebra to get variance-covariance matrix
|
|
||||||
cov_m = np.dot(np.transpose(xr),xr)/n
|
|
||||||
# Compute asset correlation matrix (informative only)
|
|
||||||
corr_m = cov_m/np.dot(np.transpose(stds),stds)
|
|
||||||
|
|
||||||
# Define portfolio optimization parameters
|
|
||||||
n_portfolios = 50000
|
|
||||||
results_array = np.zeros((3+context.nassets,n_portfolios))
|
|
||||||
for p in xrange(n_portfolios):
|
|
||||||
weights = np.random.random(context.nassets)
|
|
||||||
weights /= np.sum(weights)
|
|
||||||
w = np.asmatrix(weights)
|
|
||||||
p_r = np.sum(np.dot(w,np.transpose(m)))*365
|
|
||||||
p_std = np.sqrt(np.dot(np.dot(w,cov_m),np.transpose(w)))*np.sqrt(365)
|
|
||||||
|
|
||||||
#store results in results array
|
|
||||||
results_array[0,p] = p_r
|
|
||||||
results_array[1,p] = p_std
|
|
||||||
#store Sharpe Ratio (return / volatility) - risk free rate element
|
|
||||||
#excluded for simplicity
|
|
||||||
results_array[2,p] = results_array[0,p] / results_array[1,p]
|
|
||||||
i = 0
|
|
||||||
for iw in weights:
|
|
||||||
results_array[3+i,p] = weights[i]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
#convert results array to Pandas DataFrame
|
|
||||||
results_frame = pd.DataFrame(np.transpose(results_array),
|
|
||||||
columns=['r','stdev','sharpe']+context.assets)
|
|
||||||
#locate position of portfolio with highest Sharpe Ratio
|
|
||||||
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
|
|
||||||
#locate positon of portfolio with minimum standard deviation
|
|
||||||
min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
|
|
||||||
|
|
||||||
#order optimal weights for each asset
|
|
||||||
for asset in context.assets:
|
|
||||||
if data.can_trade(asset):
|
|
||||||
order_target_percent(asset, max_sharpe_port[asset])
|
|
||||||
|
|
||||||
#create scatter plot coloured by Sharpe Ratio
|
|
||||||
plt.scatter(results_frame.stdev,results_frame.r,c=results_frame.sharpe,cmap='RdYlGn')
|
|
||||||
plt.xlabel('Volatility')
|
|
||||||
plt.ylabel('Returns')
|
|
||||||
plt.colorbar()
|
|
||||||
#plot red star to highlight position of portfolio with highest Sharpe Ratio
|
|
||||||
plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker='o',color='b',s=200)
|
|
||||||
#plot green star to highlight position of minimum variance portfolio
|
|
||||||
plt.show()
|
|
||||||
print(max_sharpe_port)
|
|
||||||
record(pr=pr,r=r, m=m, stds=stds ,max_sharpe_port=max_sharpe_port, corr_m=corr_m)
|
|
||||||
context.i += 1
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(context=None, results=None):
|
|
||||||
# Form DataFrame with selected data
|
|
||||||
data = results[['pr','r','m','stds','max_sharpe_port','corr_m','portfolio_value']]
|
|
||||||
|
|
||||||
# Save results in CSV file
|
|
||||||
filename = os.path.splitext(os.path.basename(__file__))[0]
|
|
||||||
data.to_csv(filename + '.csv')
|
|
||||||
|
|
||||||
|
|
||||||
# Bitcoin data is available from 2015-3-2. Dates vary for other tokens.
|
|
||||||
start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc)
|
|
||||||
end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc)
|
|
||||||
results = run_algorithm(initialize=initialize,
|
|
||||||
handle_data=handle_data,
|
|
||||||
analyze=analyze,
|
|
||||||
start=start,
|
|
||||||
end=end,
|
|
||||||
exchange_name='poloniex',
|
|
||||||
capital_base=100000, )
|
|
||||||
|
|
||||||
.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ.
|
.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ.
|
||||||
:align: center
|
:align: center
|
||||||
|
|||||||
+10
-1
@@ -89,7 +89,7 @@ Once either Conda or MiniConda has been set up you can install Catalyst:
|
|||||||
|
|
||||||
.. 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
|
4. Activate the environment (which you need to do every time you start a new
|
||||||
session to run Catalyst):
|
session to run Catalyst):
|
||||||
@@ -133,10 +133,19 @@ with the following steps:
|
|||||||
|
|
||||||
2. Create the environment:
|
2. Create the environment:
|
||||||
|
|
||||||
|
for python 2.7:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
conda create --name catalyst python=2.7 scipy zlib
|
conda create --name catalyst python=2.7 scipy zlib
|
||||||
|
|
||||||
|
or for python 3.6:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
conda create --name catalyst python=3.6 scipy zlib
|
||||||
|
|
||||||
|
|
||||||
3. Activate the environment:
|
3. Activate the environment:
|
||||||
|
|
||||||
**Linux or MacOS:**
|
**Linux or MacOS:**
|
||||||
|
|||||||
@@ -184,5 +184,14 @@ Here is the breakdown of the new arguments:
|
|||||||
essentially sleep and when the predefined time comes, it would start executing.
|
essentially sleep and when the predefined time comes, it would start executing.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The `catalyst live` command offers additional parameters.
|
||||||
|
You can learn more by running the following from the command line:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
catalyst live --help
|
||||||
|
|
||||||
|
|
||||||
Here is a complete algorithm for reference:
|
Here is a complete algorithm for reference:
|
||||||
`Buy Low and Sell High <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_low_sell_high_live.py>`_
|
`Buy Low and Sell High <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_low_sell_high_live.py>`_
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
name: catalyst
|
name: catalyst
|
||||||
channels:
|
channels:
|
||||||
- defaults
|
- defaults
|
||||||
|
- conda-forge
|
||||||
dependencies:
|
dependencies:
|
||||||
- certifi=2016.2.28=py27_0
|
- certifi=2016.2.28=py27_0
|
||||||
- mkl=2017.0.3
|
- mkl=2017.0.3
|
||||||
|
- matplotlib=2.1.2=py36_0
|
||||||
- numpy=1.13.1=py27_0
|
- numpy=1.13.1=py27_0
|
||||||
- openssl=1.0.2l
|
- openssl=1.0.2l
|
||||||
- pip=9.0.1=py27_1
|
- pip=9.0.1=py27_1
|
||||||
|
|||||||
@@ -1,29 +1,24 @@
|
|||||||
name: catalyst
|
name: catalyst
|
||||||
channels:
|
channels:
|
||||||
- defaults
|
- defaults
|
||||||
|
- conda-forge
|
||||||
dependencies:
|
dependencies:
|
||||||
- ca-certificates=2017.08.26=ha1e5d58_0
|
- ca-certificates=2017.08.26
|
||||||
- certifi=2018.1.18=py36_0
|
- certifi=2018.1.18
|
||||||
- intel-openmp=2018.0.0=h8158457_8
|
- intel-openmp=2018.0.0
|
||||||
- libcxx=4.0.1=h579ed51_0
|
- mkl=2018.0.1
|
||||||
- libcxxabi=4.0.1=hebd6815_0
|
- numpy=1.14.0
|
||||||
- libedit=3.1=hb4e282d_0
|
- openssl=1.0.2n
|
||||||
- libffi=3.2.1=h475c297_4
|
- matplotlib=2.1.2=py36_0
|
||||||
- libgfortran=3.0.1=h93005f0_2
|
- pip=9.0.1
|
||||||
- mkl=2018.0.1=hfbd8650_4
|
- python=3.6.4
|
||||||
- ncurses=6.0=hd04f020_2
|
- scipy=1.0.0
|
||||||
- numpy=1.14.0=py36h8a80b8c_1
|
|
||||||
- openssl=1.0.2n=hdbc3d79_0
|
|
||||||
- pip=9.0.1=py36h1555ced_4
|
|
||||||
- python=3.6.4=hc167b69_1
|
|
||||||
- readline=7.0=hc1231fa_4
|
|
||||||
- scipy=1.0.0=py36h1de22e9_0
|
|
||||||
- setuptools=38.4.0=py36_0
|
- setuptools=38.4.0=py36_0
|
||||||
- sqlite=3.22.0=h3efe00b_0
|
- sqlite=3.22.0
|
||||||
- tk=8.6.7=h35a86e2_3
|
- tk=8.6.7
|
||||||
- wheel=0.30.0=py36h5eb2c71_1
|
- wheel=0.30.0
|
||||||
- xz=5.2.3=h0278029_2
|
- xz=5.2.3
|
||||||
- zlib=1.2.11=hf3cbc9b_2
|
- zlib=1.2.11
|
||||||
- pip:
|
- pip:
|
||||||
- aiodns==1.1.1
|
- aiodns==1.1.1
|
||||||
- aiohttp==3.0.1
|
- aiohttp==3.0.1
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
from catalyst.exchange.utils.factory import get_exchange
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfig:
|
|
||||||
def test_create_config(self):
|
|
||||||
exchange = get_exchange('binance', skip_init=True)
|
|
||||||
config = exchange.create_exchange_config()
|
|
||||||
pass
|
|
||||||
@@ -197,7 +197,7 @@ class TestSuiteBundle:
|
|||||||
# population=exchange_population,
|
# population=exchange_population,
|
||||||
# features=[bundle],
|
# features=[bundle],
|
||||||
# ) # Type: list[Exchange]
|
# ) # Type: list[Exchange]
|
||||||
exchanges = [get_exchange('binance', skip_init=True)]
|
exchanges = [get_exchange('poloniex', skip_init=True)]
|
||||||
|
|
||||||
data_portal = TestSuiteBundle.get_data_portal(exchanges)
|
data_portal = TestSuiteBundle.get_data_portal(exchanges)
|
||||||
for exchange in exchanges:
|
for exchange in exchanges:
|
||||||
|
|||||||
@@ -5,28 +5,63 @@ from logging import Logger, WARNING
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from catalyst.assets._assets import TradingPair
|
||||||
from logbook import TestHandler
|
from logbook import TestHandler
|
||||||
|
|
||||||
from catalyst.assets._assets import TradingPair
|
from catalyst.exchange.exchange_errors import ExchangeRequestError
|
||||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
||||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
||||||
from catalyst.exchange.utils.factory import get_exchanges, get_exchange
|
|
||||||
from catalyst.exchange.utils.test_utils import select_random_exchanges, \
|
from catalyst.exchange.utils.test_utils import select_random_exchanges, \
|
||||||
select_random_assets
|
handle_exchange_error, select_random_assets
|
||||||
from catalyst.testing import ZiplineTestCase
|
from catalyst.testing import ZiplineTestCase
|
||||||
from catalyst.testing.fixtures import WithLogger
|
from catalyst.testing.fixtures import WithLogger
|
||||||
|
from catalyst.exchange.utils.factory import get_exchanges, get_exchange
|
||||||
|
|
||||||
log = Logger('TestSuiteExchange')
|
log = Logger('TestSuiteExchange')
|
||||||
|
|
||||||
|
|
||||||
class TestSuiteExchange(WithLogger, ZiplineTestCase):
|
class TestSuiteExchange(WithLogger, ZiplineTestCase):
|
||||||
|
def _test_markets_exchange(self, exchange, attempts=0):
|
||||||
|
assets = None
|
||||||
|
try:
|
||||||
|
exchange.init()
|
||||||
|
|
||||||
|
# Verify that the assets and markets are populated
|
||||||
|
if not exchange.markets:
|
||||||
|
raise ValueError(
|
||||||
|
'no markets found'
|
||||||
|
)
|
||||||
|
if not exchange.assets:
|
||||||
|
raise ValueError(
|
||||||
|
'no assets derived from markets'
|
||||||
|
)
|
||||||
|
assets = exchange.assets
|
||||||
|
|
||||||
|
except ExchangeRequestError as e:
|
||||||
|
sleep(5)
|
||||||
|
|
||||||
|
if attempts > 5:
|
||||||
|
handle_exchange_error(exchange, e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
're-trying an exchange request {} {}'.format(
|
||||||
|
exchange.name, attempts
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._test_markets_exchange(exchange, attempts + 1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
handle_exchange_error(exchange, e)
|
||||||
|
|
||||||
|
return assets
|
||||||
|
|
||||||
def test_markets(self):
|
def test_markets(self):
|
||||||
population = 3
|
population = 3
|
||||||
results = dict()
|
results = dict()
|
||||||
|
|
||||||
exchanges = select_random_exchanges(population) # Type: list[Exchange]
|
exchanges = select_random_exchanges(population) # Type: list[Exchange]
|
||||||
for exchange in exchanges:
|
for exchange in exchanges:
|
||||||
exchange.init()
|
|
||||||
assets = self._test_markets_exchange(exchange)
|
assets = self._test_markets_exchange(exchange)
|
||||||
|
|
||||||
if assets is not None:
|
if assets is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user