mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-27 19:14:36 +08:00
BUG: fixed issues with data frequencies in data.history() which was particularly noticeable in live mode and minor adjustments around the commission model
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import talib
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from catalyst import run_algorithm
|
||||
from catalyst.api import symbol
|
||||
|
||||
|
||||
def initialize(context):
|
||||
print('initializing')
|
||||
context.asset = symbol('burst_btc')
|
||||
context.asset = symbol('eth_btc')
|
||||
|
||||
|
||||
def handle_data(context, data):
|
||||
@@ -16,22 +16,22 @@ def handle_data(context, data):
|
||||
price = data.current(context.asset, 'close')
|
||||
print('got price {price}'.format(price=price))
|
||||
|
||||
prices = data.history(
|
||||
context.asset,
|
||||
fields='price',
|
||||
bar_count=15,
|
||||
frequency='1d'
|
||||
)
|
||||
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
||||
print('got rsi: {}'.format(rsi))
|
||||
# prices = data.history(
|
||||
# context.asset,
|
||||
# fields='price',
|
||||
# bar_count=20,
|
||||
# frequency='1T'
|
||||
# )
|
||||
# rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
||||
# print('got rsi: {}'.format(rsi))
|
||||
pass
|
||||
|
||||
|
||||
run_algorithm(
|
||||
capital_base=250,
|
||||
start=pd.to_datetime('2017-08-01', utc=True),
|
||||
end=pd.to_datetime('2017-9-30', utc=True),
|
||||
data_frequency='minute',
|
||||
start=pd.to_datetime('2017-1-1', utc=True),
|
||||
end=pd.to_datetime('2017-10-22', utc=True),
|
||||
data_frequency='daily',
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=None,
|
||||
@@ -43,7 +43,7 @@ run_algorithm(
|
||||
# initialize=initialize,
|
||||
# handle_data=handle_data,
|
||||
# analyze=None,
|
||||
# exchange_name='bitfinex',
|
||||
# exchange_name='bittrex',
|
||||
# live=True,
|
||||
# algo_namespace='simple_loop',
|
||||
# base_currency='eth',
|
||||
|
||||
@@ -240,7 +240,7 @@ class Bitfinex(Exchange):
|
||||
# TODO: fetch account data and keep in cache
|
||||
return None
|
||||
|
||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
||||
def get_candles(self, freq, assets, bar_count=None,
|
||||
start_dt=None, end_dt=None):
|
||||
"""
|
||||
Retrieve OHLVC candles from Bitfinex
|
||||
@@ -259,39 +259,36 @@ class Bitfinex(Exchange):
|
||||
'retrieving {bars} {freq} candles on {exchange} from '
|
||||
'{end_dt} for markets {symbols}, '.format(
|
||||
bars=bar_count,
|
||||
freq=data_frequency,
|
||||
freq=freq,
|
||||
exchange=self.name,
|
||||
end_dt=end_dt,
|
||||
symbols=get_symbols_string(assets)
|
||||
)
|
||||
)
|
||||
|
||||
freq_match = re.match(r'([0-9].*)(m|h|d)', data_frequency, re.M | re.I)
|
||||
allowed_frequencies = ['1T', '5T', '15T', '30T', '60T', '180T',
|
||||
'360T', '720T', '1D', '7D', '14D', '30D']
|
||||
if freq not in allowed_frequencies:
|
||||
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||
|
||||
freq_match = re.match(r'([0-9].*)(T|H|D)', freq, re.M | re.I)
|
||||
if freq_match:
|
||||
number = int(freq_match.group(1))
|
||||
unit = freq_match.group(2)
|
||||
|
||||
if unit == 'd':
|
||||
converted_unit = 'D'
|
||||
if unit == 'T':
|
||||
if number in [60, 180, 360, 720]:
|
||||
number = number / 60
|
||||
converted_unit = 'h'
|
||||
else:
|
||||
converted_unit = 'm'
|
||||
else:
|
||||
converted_unit = unit
|
||||
|
||||
frequency = '{}{}'.format(number, converted_unit)
|
||||
allowed_frequencies = ['1m', '5m', '15m', '30m', '1h', '3h', '6h',
|
||||
'12h', '1D', '7D', '14D', '1M']
|
||||
|
||||
if frequency not in allowed_frequencies:
|
||||
raise InvalidHistoryFrequencyError(
|
||||
frequency=data_frequency
|
||||
)
|
||||
elif data_frequency == 'minute':
|
||||
frequency = '1m'
|
||||
elif data_frequency == 'daily':
|
||||
frequency = '1D'
|
||||
else:
|
||||
raise InvalidHistoryFrequencyError(
|
||||
frequency=data_frequency
|
||||
)
|
||||
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||
|
||||
# Making sure that assets are iterable
|
||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||
|
||||
@@ -210,14 +210,14 @@ class Bittrex(Exchange):
|
||||
error=status['message']
|
||||
)
|
||||
|
||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
||||
def get_candles(self, freq, assets, bar_count=None,
|
||||
start_dt=None, end_dt=None):
|
||||
"""
|
||||
Supported Intervals
|
||||
-------------------
|
||||
day, oneMin, fiveMin, thirtyMin, hour
|
||||
|
||||
:param data_frequency:
|
||||
:param freq:
|
||||
:param assets:
|
||||
:param bar_count:
|
||||
:param start_dt
|
||||
@@ -233,28 +233,25 @@ class Bittrex(Exchange):
|
||||
'retrieving {bars} {freq} candles on {exchange} from '
|
||||
'{end_dt} for markets {symbols}, '.format(
|
||||
bars=bar_count,
|
||||
freq=data_frequency,
|
||||
freq=freq,
|
||||
exchange=self.name,
|
||||
end_dt=end_dt,
|
||||
symbols=get_symbols_string(assets)
|
||||
)
|
||||
)
|
||||
|
||||
data_frequency = data_frequency.lower()
|
||||
if data_frequency == 'minute' or data_frequency == '1m':
|
||||
if freq == '1T':
|
||||
frequency = 'oneMin'
|
||||
elif data_frequency == '5m':
|
||||
elif freq == '5T':
|
||||
frequency = 'fiveMin'
|
||||
elif data_frequency == '30m':
|
||||
elif freq == '30T':
|
||||
frequency = 'thirtyMin'
|
||||
elif data_frequency == '1h':
|
||||
elif freq == '60T':
|
||||
frequency = 'hour'
|
||||
elif data_frequency == 'daily' or data_frequency == '1d':
|
||||
elif freq == '1D':
|
||||
frequency = 'day'
|
||||
else:
|
||||
raise InvalidHistoryFrequencyError(
|
||||
frequency=data_frequency
|
||||
)
|
||||
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||
|
||||
# Making sure that assets are iterable
|
||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||
@@ -297,6 +294,7 @@ class Bittrex(Exchange):
|
||||
if bar_count is None:
|
||||
ohlc_map[asset] = ohlc_from_candle(ordered_candles[0])
|
||||
else:
|
||||
# TODO: optimize
|
||||
ohlc_bars = []
|
||||
for candle in ordered_candles[:bar_count]:
|
||||
ohlc = ohlc_from_candle(candle)
|
||||
|
||||
@@ -71,25 +71,18 @@ def get_delta(periods, data_frequency):
|
||||
if data_frequency == 'minute' else timedelta(days=periods)
|
||||
|
||||
|
||||
def get_periods_range(start_dt, end_dt, data_frequency):
|
||||
freq = 'T' if data_frequency == 'minute' else 'D'
|
||||
def get_periods_range(start_dt, end_dt, freq):
|
||||
if freq == 'minute':
|
||||
freq = 'T'
|
||||
|
||||
elif freq == 'daily':
|
||||
freq = 'D'
|
||||
|
||||
return pd.date_range(start_dt, end_dt, freq=freq)
|
||||
|
||||
|
||||
def get_periods(start_dt, end_dt, data_frequency):
|
||||
delta = end_dt - start_dt
|
||||
|
||||
if data_frequency == 'minute':
|
||||
delta_periods = delta.total_seconds() / 60
|
||||
|
||||
elif data_frequency == 'daily':
|
||||
delta_periods = delta.total_seconds() / 60 / 60 / 24
|
||||
|
||||
else:
|
||||
raise ValueError('frequency not supported')
|
||||
|
||||
return int(delta_periods)
|
||||
def get_periods(start_dt, end_dt, freq):
|
||||
return len(get_periods_range(start_dt, end_dt, freq))
|
||||
|
||||
|
||||
def get_start_dt(end_dt, bar_count, data_frequency):
|
||||
|
||||
+104
-18
@@ -12,11 +12,12 @@ from logbook import Logger
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.data.data_portal import BASE_FIELDS
|
||||
from catalyst.exchange.bundle_utils import get_start_dt, \
|
||||
get_delta, get_periods
|
||||
get_delta, get_periods, get_periods_range
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||
InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||
InvalidHistoryFrequencyError, PricingDataNotLoadedError
|
||||
InvalidHistoryFrequencyError, PricingDataNotLoadedError, \
|
||||
NoDataAvailableOnExchange
|
||||
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
||||
ExchangeLimitOrder, ExchangeStopOrder
|
||||
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
|
||||
@@ -24,6 +25,7 @@ from catalyst.exchange.exchange_utils import get_exchange_symbols, \
|
||||
get_frequency, resample_history_df
|
||||
from catalyst.finance.order import ORDER_STATUS
|
||||
from catalyst.finance.transaction import Transaction
|
||||
from catalyst.utils.deprecate import deprecated
|
||||
|
||||
log = Logger('Exchange', level=LOG_LEVEL)
|
||||
|
||||
@@ -71,6 +73,15 @@ class Exchange:
|
||||
def time_skew(self):
|
||||
pass
|
||||
|
||||
def is_open(self, dt):
|
||||
"""
|
||||
Is the exchange open?
|
||||
:param dt:
|
||||
:return:
|
||||
"""
|
||||
# TODO: implement for each exchange.
|
||||
return True
|
||||
|
||||
def ask_request(self):
|
||||
"""
|
||||
Asks permission to issue a request to the exchange.
|
||||
@@ -364,7 +375,8 @@ class Exchange:
|
||||
)
|
||||
)
|
||||
|
||||
ohlc = self.get_candles(data_frequency, asset)
|
||||
freq = '1T' if data_frequency == 'minute' else '1D'
|
||||
ohlc = self.get_candles(freq, asset)
|
||||
if field not in ohlc:
|
||||
raise KeyError('Invalid column: %s' % field)
|
||||
|
||||
@@ -388,17 +400,84 @@ class Exchange:
|
||||
|
||||
dates = [candle['last_traded'] for candle in candles]
|
||||
values = [candle[field] for candle in candles]
|
||||
|
||||
periods = self.bundle.get_calendar_periods_range(
|
||||
start_dt, end_dt, data_frequency
|
||||
)
|
||||
series = pd.Series(values, index=dates)
|
||||
|
||||
periods = get_periods_range(
|
||||
start_dt, end_dt, data_frequency
|
||||
)
|
||||
# TODO: ensure that this working as expected, if not use fillna
|
||||
series.reindex(periods, method='ffill', fill_value=previous_value)
|
||||
series = series.reindex(
|
||||
periods,
|
||||
method='ffill',
|
||||
fill_value=previous_value,
|
||||
)
|
||||
|
||||
return series
|
||||
|
||||
@deprecated
|
||||
def get_history_window_direct(self,
|
||||
assets,
|
||||
end_dt,
|
||||
bar_count,
|
||||
frequency,
|
||||
field,
|
||||
data_frequency=None,
|
||||
ffill=True):
|
||||
|
||||
"""
|
||||
Public API method that returns a dataframe containing the requested
|
||||
history window. Data is fully adjusted.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
assets : list of catalyst.data.Asset objects
|
||||
The assets whose data is desired.
|
||||
|
||||
end_dt: not applicable to cryptocurrencies
|
||||
|
||||
bar_count: int
|
||||
The number of bars desired.
|
||||
|
||||
frequency: string
|
||||
"1d" or "1m"
|
||||
|
||||
field: string
|
||||
The desired field of the asset.
|
||||
|
||||
data_frequency: string
|
||||
The frequency of the data to query; i.e. whether the data is
|
||||
'daily' or 'minute' bars.
|
||||
|
||||
# TODO: fill how?
|
||||
ffill: boolean
|
||||
Forward-fill missing values. Only has effect if field
|
||||
is 'price'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A dataframe containing the requested data.
|
||||
"""
|
||||
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||
|
||||
# The get_history method supports multiple asset
|
||||
candles = self.get_candles(
|
||||
data_frequency=frequency,
|
||||
assets=assets,
|
||||
bar_count=bar_count,
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt
|
||||
)
|
||||
candle_series = self.get_series_from_candles(
|
||||
candles=candles,
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt,
|
||||
data_frequency=frequency,
|
||||
field=field,
|
||||
)
|
||||
|
||||
df = pd.DataFrame(candle_series)
|
||||
return df
|
||||
|
||||
def get_history_window(self,
|
||||
assets,
|
||||
end_dt,
|
||||
@@ -442,7 +521,7 @@ class Exchange:
|
||||
A dataframe containing the requested data.
|
||||
"""
|
||||
|
||||
candle_size, unit, data_frequency = get_frequency(
|
||||
freq, candle_size, unit, data_frequency = get_frequency(
|
||||
frequency, data_frequency
|
||||
)
|
||||
adj_bar_count = candle_size * bar_count
|
||||
@@ -454,7 +533,7 @@ class Exchange:
|
||||
field=field,
|
||||
data_frequency=data_frequency
|
||||
)
|
||||
except PricingDataNotLoadedError:
|
||||
except (PricingDataNotLoadedError, NoDataAvailableOnExchange):
|
||||
series = dict()
|
||||
|
||||
for asset in assets:
|
||||
@@ -467,12 +546,14 @@ class Exchange:
|
||||
series[asset].index[-1] + get_delta(1, data_frequency) \
|
||||
if asset in series else start_dt
|
||||
|
||||
trailing_bar_count = \
|
||||
get_periods(trailing_dt, end_dt, data_frequency)
|
||||
|
||||
# The get_history method supports multiple asset
|
||||
# Use the original frequency to let each api optimize
|
||||
# the size of result sets
|
||||
trailing_bar_count = get_periods(
|
||||
trailing_dt, end_dt, freq
|
||||
)
|
||||
candles = self.get_candles(
|
||||
data_frequency=data_frequency,
|
||||
freq=freq,
|
||||
assets=asset,
|
||||
bar_count=trailing_bar_count,
|
||||
start_dt=start_dt,
|
||||
@@ -482,6 +563,8 @@ class Exchange:
|
||||
last_value = series[asset].iloc(0) if asset in series \
|
||||
else np.nan
|
||||
|
||||
# Create a series with the common data_frequency, ffill
|
||||
# missing values
|
||||
candle_series = self.get_series_from_candles(
|
||||
candles=candles,
|
||||
start_dt=trailing_dt,
|
||||
@@ -497,7 +580,9 @@ class Exchange:
|
||||
else:
|
||||
series[asset] = candle_series
|
||||
|
||||
df = resample_history_df(pd.DataFrame(series), candle_size, field)
|
||||
df = resample_history_df(pd.DataFrame(series), freq, field)
|
||||
# TODO: consider this more carefully
|
||||
df.dropna(inplace=True)
|
||||
|
||||
return df
|
||||
|
||||
@@ -708,13 +793,14 @@ class Exchange:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
||||
def get_candles(self, freq, assets, bar_count=None,
|
||||
start_dt=None, end_dt=None):
|
||||
"""
|
||||
Retrieve OHLCV candles for the given assets
|
||||
|
||||
:param data_frequency:
|
||||
The candle frequency: minute or daily
|
||||
:param freq:
|
||||
The frequency alias per convention:
|
||||
http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
|
||||
:param assets: list[TradingPair]
|
||||
The targeted assets.
|
||||
:param bar_count:
|
||||
|
||||
@@ -303,7 +303,7 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
|
||||
"""
|
||||
bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle
|
||||
|
||||
candle_size, unit, data_frequency = get_frequency(
|
||||
freq, candle_size, unit, data_frequency = get_frequency(
|
||||
frequency, data_frequency
|
||||
)
|
||||
adj_bar_count = candle_size * bar_count
|
||||
@@ -317,7 +317,7 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase):
|
||||
algo_end_dt=self._last_available_session,
|
||||
)
|
||||
|
||||
df = resample_history_df(pd.DataFrame(series), candle_size, field)
|
||||
df = resample_history_df(pd.DataFrame(series), freq, field)
|
||||
return df
|
||||
|
||||
def get_exchange_spot_value(self,
|
||||
|
||||
@@ -86,6 +86,14 @@ class AlgoPickleNotFound(ZiplineError):
|
||||
).strip()
|
||||
|
||||
|
||||
class InvalidHistoryFrequencyAlias(ZiplineError):
|
||||
msg = (
|
||||
'Invalid frequency alias {freq}. Valid suffixes are M (minute) '
|
||||
'and D (day). For example, these aliases would be valid '
|
||||
'1M, 5M, 1D.'
|
||||
).strip()
|
||||
|
||||
|
||||
class InvalidHistoryFrequencyError(ZiplineError):
|
||||
msg = (
|
||||
'Frequency {frequency} not supported by the exchange.'
|
||||
|
||||
@@ -10,7 +10,7 @@ from datetime import date, datetime
|
||||
import pandas as pd
|
||||
|
||||
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \
|
||||
InvalidHistoryFrequencyError
|
||||
InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias
|
||||
from catalyst.utils.paths import data_root, ensure_directory, \
|
||||
last_modified_time
|
||||
|
||||
@@ -313,50 +313,61 @@ def get_common_assets(exchanges):
|
||||
|
||||
|
||||
def get_frequency(freq, data_frequency):
|
||||
if freq == 'daily':
|
||||
freq = '1d'
|
||||
elif freq == 'minute':
|
||||
freq = '1m'
|
||||
if freq == 'minute':
|
||||
unit = 'T'
|
||||
candle_size = 1
|
||||
|
||||
freq_match = re.match(r'([0-9].*)(m|M|d|D)', freq, re.M | re.I)
|
||||
if freq_match:
|
||||
candle_size = int(freq_match.group(1))
|
||||
unit = freq_match.group(2)
|
||||
elif freq == 'daily':
|
||||
unit = 'D'
|
||||
candle_size = 1
|
||||
|
||||
else:
|
||||
raise InvalidHistoryFrequencyError(freq)
|
||||
freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I)
|
||||
if freq_match:
|
||||
candle_size = int(freq_match.group(1)) if freq_match.group(1) \
|
||||
else 1
|
||||
unit = freq_match.group(2)
|
||||
|
||||
else:
|
||||
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||
|
||||
if unit.lower() == 'd':
|
||||
alias = '{}D'.format(candle_size)
|
||||
|
||||
if data_frequency == 'minute':
|
||||
data_frequency = 'daily'
|
||||
|
||||
elif unit.lower() == 'm':
|
||||
elif unit.lower() == 'm' or unit == 'T':
|
||||
alias = '{}T'.format(candle_size)
|
||||
|
||||
if data_frequency == 'daily':
|
||||
data_frequency = 'minute'
|
||||
|
||||
else:
|
||||
raise InvalidHistoryFrequencyError(freq)
|
||||
|
||||
return candle_size, unit, data_frequency
|
||||
|
||||
|
||||
def resample_history_df(df, candle_size, field):
|
||||
if candle_size > 1:
|
||||
if field == 'open':
|
||||
agg = 'first'
|
||||
elif field == 'high':
|
||||
agg = 'max'
|
||||
elif field == 'low':
|
||||
agg = 'min'
|
||||
elif field == 'close':
|
||||
agg = 'last'
|
||||
elif field == 'volume':
|
||||
agg = 'sum'
|
||||
else:
|
||||
raise ValueError('Invalid field.')
|
||||
|
||||
# TODO: pad with nan?
|
||||
return df.resample('{}T'.format(candle_size)).agg(agg)
|
||||
# elif unit.lower() == 'h':
|
||||
# candle_size = candle_size * 60
|
||||
#
|
||||
# alias = '{}T'.format(candle_size)
|
||||
# if data_frequency == 'daily':
|
||||
# data_frequency = 'minute'
|
||||
|
||||
else:
|
||||
return df
|
||||
raise InvalidHistoryFrequencyAlias(freq=freq)
|
||||
|
||||
return alias, candle_size, unit, data_frequency
|
||||
|
||||
|
||||
def resample_history_df(df, freq, field):
|
||||
if field == 'open':
|
||||
agg = 'first'
|
||||
elif field == 'high':
|
||||
agg = 'max'
|
||||
elif field == 'low':
|
||||
agg = 'min'
|
||||
elif field == 'close':
|
||||
agg = 'last'
|
||||
elif field == 'volume':
|
||||
agg = 'sum'
|
||||
else:
|
||||
raise ValueError('Invalid field.')
|
||||
|
||||
return df.resample(freq).agg(agg)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import calendar
|
||||
import json
|
||||
import json
|
||||
import time
|
||||
@@ -171,12 +172,12 @@ class Poloniex(Exchange):
|
||||
# TODO: fetch account data and keep in cache
|
||||
return None
|
||||
|
||||
def get_candles(self, data_frequency, assets, bar_count=None,
|
||||
def get_candles(self, freq, assets, bar_count=None,
|
||||
start_dt=None, end_dt=None):
|
||||
"""
|
||||
Retrieve OHLVC candles from Poloniex
|
||||
|
||||
:param data_frequency:
|
||||
:param freq:
|
||||
:param assets:
|
||||
:param bar_count:
|
||||
:return:
|
||||
@@ -193,31 +194,33 @@ class Poloniex(Exchange):
|
||||
'retrieving {bars} {freq} candles on {exchange} from '
|
||||
'{end_dt} for markets {symbols}, '.format(
|
||||
bars=bar_count,
|
||||
freq=data_frequency,
|
||||
freq=freq,
|
||||
exchange=self.name,
|
||||
end_dt=end_dt,
|
||||
symbols=get_symbols_string(assets)
|
||||
)
|
||||
)
|
||||
|
||||
if data_frequency == '5m':
|
||||
if freq == '1T' and (bar_count == 1 or bar_count is None):
|
||||
# TODO: use the order book instead
|
||||
# We use the 5m to fetch the last bar
|
||||
frequency = 300
|
||||
elif data_frequency == '15m':
|
||||
elif freq == '5T':
|
||||
frequency = 300
|
||||
elif freq == '15T':
|
||||
frequency = 900
|
||||
elif data_frequency == '30m':
|
||||
elif freq == '30T':
|
||||
frequency = 1800
|
||||
elif data_frequency == '2h':
|
||||
elif freq == '120T':
|
||||
frequency = 7200
|
||||
elif data_frequency == '4h':
|
||||
elif freq == '240T':
|
||||
frequency = 14400
|
||||
elif data_frequency == '1D' or data_frequency == 'daily':
|
||||
elif freq == '1D':
|
||||
frequency = 86400
|
||||
else:
|
||||
# Poloniex does not offer 1m data candles
|
||||
# It is likely to error out there frequently
|
||||
raise InvalidHistoryFrequencyError(
|
||||
frequency=data_frequency
|
||||
)
|
||||
raise InvalidHistoryFrequencyError(frequency=freq)
|
||||
|
||||
# Making sure that assets are iterable
|
||||
asset_list = [assets] if isinstance(assets, TradingPair) else assets
|
||||
@@ -225,15 +228,18 @@ class Poloniex(Exchange):
|
||||
|
||||
for asset in asset_list:
|
||||
|
||||
end = int(time.mktime(end_dt.timetuple()))
|
||||
# TODO: what's wrong with this?
|
||||
# end = int(time.mktime(end_dt.timetuple()))
|
||||
end = int(time.time())
|
||||
if bar_count is None:
|
||||
start = end - 2 * frequency
|
||||
else:
|
||||
start = end - bar_count * frequency
|
||||
|
||||
try:
|
||||
response = self.api.returnchartdata(self.get_symbol(asset),
|
||||
frequency, start, end)
|
||||
response = self.api.returnchartdata(
|
||||
self.get_symbol(asset), frequency, start, end
|
||||
)
|
||||
except Exception as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import pandas as pd
|
||||
from logbook import Logger, DEBUG
|
||||
|
||||
from catalyst import run_algorithm
|
||||
from catalyst.api import (schedule_function, order_target_percent, symbol,
|
||||
date_rules, get_open_orders, cancel_order, record,
|
||||
set_commission, set_slippage)
|
||||
|
||||
log = Logger('rodrigo_1', level=DEBUG)
|
||||
"""
|
||||
The initialize function sets any data or variables that
|
||||
you'll use in your algorithm.
|
||||
It's only called once at the beginning of your algorithm.
|
||||
"""
|
||||
|
||||
|
||||
def initialize(context):
|
||||
# Select asset of interest
|
||||
context.asset = symbol('BTC_USD')
|
||||
|
||||
# set_commission(TradingPairFeeSchedule(maker_fee=0.5, taker_fee=0.5))
|
||||
# set_slippage(TradingPairFixedSlippage(spread=0.5))
|
||||
# Set up a rebalance method to run every day
|
||||
schedule_function(rebalance, date_rule=date_rules.every_day())
|
||||
|
||||
|
||||
"""
|
||||
Rebalance function scheduled to run once per day.
|
||||
"""
|
||||
|
||||
|
||||
def rebalance(context, data):
|
||||
# To make market decisions, we're calculating the token's
|
||||
# moving average for the last 5 days.
|
||||
|
||||
# We get the price history for the last 5 days.
|
||||
price_history = data.history(context.asset, fields='price', bar_count=5,
|
||||
frequency='1d')
|
||||
|
||||
# Then we take an average of those 5 days.
|
||||
average_price = price_history.mean()
|
||||
|
||||
# We also get the coin's current price.
|
||||
price = data.current(context.asset, 'price')
|
||||
|
||||
# Cancel any outstanding orders
|
||||
orders = get_open_orders(context.asset) or []
|
||||
for order in orders:
|
||||
cancel_order(order)
|
||||
|
||||
# If our coin is currently listed on a major exchange
|
||||
if data.can_trade(context.asset):
|
||||
# If the current price is 1% above the 5-day average price,
|
||||
# we open a long position. If the current price is below the
|
||||
# average price, then we want to close our position to 0 shares.
|
||||
if price > (1.01 * average_price):
|
||||
# Place the buy order (positive means buy, negative means sell)
|
||||
order_target_percent(context.asset, .99)
|
||||
log.info("Buying %s" % (context.asset.symbol))
|
||||
elif price < average_price:
|
||||
# Sell all of our shares by setting the target position to zero
|
||||
order_target_percent(context.asset, 0)
|
||||
log.info("Selling %s" % (context.asset.symbol))
|
||||
|
||||
# Use the record() method to track up to five custom signals.
|
||||
# Record Apple's current price and the average price over the last
|
||||
# five days.
|
||||
cash = context.portfolio.cash
|
||||
leverage = context.account.leverage
|
||||
|
||||
record(price=price, average_price=average_price, cash=cash,
|
||||
leverage=leverage)
|
||||
|
||||
|
||||
def analyze(context=None, results=None):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Plot the portfolio and asset data.
|
||||
ax1 = plt.subplot(511)
|
||||
results[['portfolio_value']].plot(ax=ax1)
|
||||
ax1.set_ylabel('Portfolio Value (USD)')
|
||||
|
||||
ax2 = plt.subplot(512, sharex=ax1)
|
||||
ax2.set_ylabel('{asset} (USD)'.format(asset=context.asset))
|
||||
(results[[
|
||||
'price',
|
||||
]]).plot(ax=ax2)
|
||||
|
||||
trans = results.ix[[t != [] for t in results.transactions]]
|
||||
buys = trans.ix[
|
||||
[t[0]['amount'] > 0 for t in trans.transactions]
|
||||
]
|
||||
sells = trans.ix[
|
||||
[t[0]['amount'] < 0 for t in trans.transactions]
|
||||
]
|
||||
|
||||
ax2.plot(
|
||||
buys.index,
|
||||
results.price[buys.index],
|
||||
'^',
|
||||
markersize=10,
|
||||
color='g',
|
||||
)
|
||||
ax2.plot(
|
||||
sells.index,
|
||||
results.price[sells.index],
|
||||
'v',
|
||||
markersize=10,
|
||||
color='r',
|
||||
)
|
||||
|
||||
ax3 = plt.subplot(513, sharex=ax1)
|
||||
results[['leverage']].plot(ax=ax3)
|
||||
ax3.set_ylabel('Leverage ')
|
||||
|
||||
ax4 = plt.subplot(514, sharex=ax1)
|
||||
results[['cash']].plot(ax=ax4)
|
||||
ax4.set_ylabel('Cash (USD)')
|
||||
|
||||
results[[
|
||||
'algorithm',
|
||||
'benchmark',
|
||||
]] = results[[
|
||||
'algorithm_period_return',
|
||||
'benchmark_period_return',
|
||||
]]
|
||||
|
||||
ax5 = plt.subplot(515, sharex=ax1)
|
||||
results[[
|
||||
'algorithm',
|
||||
'benchmark',
|
||||
]].plot(ax=ax5)
|
||||
ax5.set_ylabel('Percent Change')
|
||||
|
||||
plt.legend(loc=3)
|
||||
|
||||
# Show the plot.
|
||||
plt.gcf().set_size_inches(18, 8)
|
||||
plt.show()
|
||||
|
||||
|
||||
run_algorithm(
|
||||
capital_base=100000,
|
||||
start=pd.to_datetime('2017-1-1', utc=True),
|
||||
end=pd.to_datetime('2017-10-22', utc=True),
|
||||
data_frequency='minute',
|
||||
initialize=initialize,
|
||||
handle_data=None,
|
||||
analyze=analyze,
|
||||
exchange_name='bitfinex',
|
||||
algo_namespace='rodrigo_1',
|
||||
base_currency='usd'
|
||||
)
|
||||
@@ -12,6 +12,7 @@ Bug Fixes
|
||||
- Fixed issue with auto-ingestion of minute data
|
||||
- Fixed issue with sell orders in backtesting
|
||||
- Fixed data frequency issues with data.history() in backtesting
|
||||
- Fixed an issue with can_trade()
|
||||
|
||||
|
||||
Build
|
||||
|
||||
@@ -8,7 +8,7 @@ from catalyst.finance.execution import (LimitOrder)
|
||||
log = Logger('test_bitfinex')
|
||||
|
||||
|
||||
class TestBitfinexTestCase(BaseExchangeTestCase):
|
||||
class TestBitfinex(BaseExchangeTestCase):
|
||||
@classmethod
|
||||
def setup(self):
|
||||
log.info('creating bitfinex object')
|
||||
@@ -48,7 +48,7 @@ class TestBitfinexTestCase(BaseExchangeTestCase):
|
||||
def test_get_candles(self):
|
||||
log.info('retrieving candles')
|
||||
ohlcv_neo = self.exchange.get_candles(
|
||||
data_frequency='1m',
|
||||
freq='1T',
|
||||
assets=self.exchange.get_asset('neo_btc')
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -52,13 +52,13 @@ class TestBittrex(BaseExchangeTestCase):
|
||||
def test_get_candles(self):
|
||||
log.info('retrieving candles')
|
||||
ohlcv_neo = self.exchange.get_candles(
|
||||
data_frequency='5m',
|
||||
freq='5T',
|
||||
assets=self.exchange.get_asset('neo_btc'),
|
||||
bar_count=20,
|
||||
end_dt=pd.to_datetime('2017-10-20', utc=True)
|
||||
)
|
||||
ohlcv_neo_ubq = self.exchange.get_candles(
|
||||
data_frequency='1d',
|
||||
freq='1D',
|
||||
assets=[
|
||||
self.exchange.get_asset('neo_btc'),
|
||||
self.exchange.get_asset('ubq_btc')
|
||||
|
||||
@@ -126,9 +126,9 @@ class TestExchangeBundle:
|
||||
# data_frequency = 'daily'
|
||||
# include_symbols = 'neo_btc,bch_btc,eth_btc'
|
||||
|
||||
exchange_name = 'bittrex'
|
||||
exchange_name = 'poloniex'
|
||||
data_frequency = 'daily'
|
||||
include_symbols = 'wings_eth'
|
||||
include_symbols = 'eth_btc'
|
||||
|
||||
start = pd.to_datetime('2017-1-1', utc=True)
|
||||
end = pd.to_datetime('2017-10-16', utc=True)
|
||||
@@ -140,10 +140,10 @@ class TestExchangeBundle:
|
||||
log.info('ingesting exchange bundle {}'.format(exchange_name))
|
||||
exchange_bundle.ingest(
|
||||
data_frequency=data_frequency,
|
||||
include_symbols=None,
|
||||
include_symbols=include_symbols,
|
||||
exclude_symbols=None,
|
||||
start=None,
|
||||
end=None,
|
||||
start=start,
|
||||
end=end,
|
||||
show_progress=True
|
||||
)
|
||||
|
||||
@@ -342,7 +342,7 @@ class TestExchangeBundle:
|
||||
assets=assets,
|
||||
end_dt=end_dt,
|
||||
bar_count=bar_count,
|
||||
data_frequency='minute'
|
||||
freq='1T'
|
||||
)
|
||||
start_dt = get_start_dt(end_dt, bar_count, data_frequency)
|
||||
|
||||
@@ -392,7 +392,7 @@ class TestExchangeBundle:
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt,
|
||||
bar_count=bar_count,
|
||||
data_frequency=data_frequency
|
||||
freq='1T'
|
||||
)
|
||||
|
||||
writer = bundle.get_writer(start_dt, end_dt, data_frequency)
|
||||
@@ -437,7 +437,7 @@ class TestExchangeBundle:
|
||||
|
||||
exchange = get_exchange(exchange_name)
|
||||
bundle = ExchangeBundle(exchange)
|
||||
asset = exchange.get_asset('xmr_btc')
|
||||
asset = exchange.get_asset('eth_btc')
|
||||
|
||||
path = get_bcolz_chunk(
|
||||
exchange_name=exchange.name,
|
||||
|
||||
@@ -8,7 +8,7 @@ from catalyst.exchange.exchange_utils import get_exchange_auth
|
||||
log = Logger('test_poloniex')
|
||||
|
||||
|
||||
class TestPoloniexTestCase(BaseExchangeTestCase):
|
||||
class TestPoloniex(BaseExchangeTestCase):
|
||||
@classmethod
|
||||
def setup(self):
|
||||
print ('creating poloniex object')
|
||||
@@ -52,11 +52,11 @@ class TestPoloniexTestCase(BaseExchangeTestCase):
|
||||
def test_get_candles(self):
|
||||
log.info('retrieving candles')
|
||||
ohlcv_neo = self.exchange.get_candles(
|
||||
data_frequency='5m',
|
||||
assets=self.exchange.get_asset('neos_btc')
|
||||
freq='5T',
|
||||
assets=self.exchange.get_asset('eth_btc')
|
||||
)
|
||||
ohlcv_neo_ubq = self.exchange.get_candles(
|
||||
data_frequency='5m',
|
||||
freq='5T',
|
||||
assets=[
|
||||
self.exchange.get_asset('neos_btc'),
|
||||
self.exchange.get_asset('via_btc')
|
||||
|
||||
Reference in New Issue
Block a user