mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-01 03:40:51 +08:00
BUG: trying to mitigate a date adjustment issue which occurs sometimes sometimes in live trading especially with Bitrrex at certain frequencies.
This commit is contained in:
@@ -245,7 +245,7 @@ def analyze(context=None, perf=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
# The execution mode: backtest or live
|
||||
MODE = 'backtest'
|
||||
MODE = 'live'
|
||||
|
||||
if MODE == 'backtest':
|
||||
folder = os.path.join(
|
||||
|
||||
@@ -9,7 +9,7 @@ from catalyst.exchange.stats_utils import get_pretty_stats, \
|
||||
|
||||
def initialize(context):
|
||||
print('initializing')
|
||||
context.asset = symbol('neo_eth')
|
||||
context.asset = symbol('eth_btc')
|
||||
context.base_price = None
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ def handle_data(context, data):
|
||||
context.asset,
|
||||
fields='price',
|
||||
bar_count=20,
|
||||
frequency='15T'
|
||||
frequency='5T'
|
||||
)
|
||||
last_traded = prices.index[-1]
|
||||
print('last candle date: {}'.format(last_traded))
|
||||
|
||||
rsi = talib.RSI(prices.values, timeperiod=14)[-1]
|
||||
print('got rsi: {}'.format(rsi))
|
||||
|
||||
@@ -107,25 +110,27 @@ def analyze(context, perf):
|
||||
pass
|
||||
|
||||
|
||||
run_algorithm(
|
||||
capital_base=250,
|
||||
start=pd.to_datetime('2017-11-9 0:00', utc=True),
|
||||
end=pd.to_datetime('2017-11-10 23:59', utc=True),
|
||||
data_frequency='minute',
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=analyze,
|
||||
exchange_name='bitfinex',
|
||||
algo_namespace='simple_loop',
|
||||
base_currency='usd'
|
||||
)
|
||||
# run_algorithm(
|
||||
# capital_base=250,
|
||||
# start=pd.to_datetime('2017-11-9 0:00', utc=True),
|
||||
# end=pd.to_datetime('2017-11-10 23:59', utc=True),
|
||||
# data_frequency='minute',
|
||||
# initialize=initialize,
|
||||
# handle_data=handle_data,
|
||||
# analyze=None,
|
||||
# exchange_name='binance',
|
||||
# live=True,
|
||||
# analyze=analyze,
|
||||
# exchange_name='bitfinex',
|
||||
# algo_namespace='simple_loop',
|
||||
# base_currency='eth',
|
||||
# live_graph=False,
|
||||
# base_currency='usd'
|
||||
# )
|
||||
run_algorithm(
|
||||
capital_base=1,
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=None,
|
||||
exchange_name='binance',
|
||||
live=True,
|
||||
algo_namespace='simple_loop',
|
||||
base_currency='eth',
|
||||
live_graph=False,
|
||||
simulate_orders=True
|
||||
)
|
||||
|
||||
@@ -163,7 +163,7 @@ if __name__ == '__main__':
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=analyze,
|
||||
exchange_name='bitfinex',
|
||||
exchange_name='poloniex',
|
||||
data_frequency='minute',
|
||||
base_currency='btc',
|
||||
live=False,
|
||||
|
||||
@@ -4,12 +4,12 @@ from collections import defaultdict
|
||||
import ccxt
|
||||
import pandas as pd
|
||||
import six
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from ccxt import ExchangeNotAvailable, InvalidOrder
|
||||
from logbook import Logger
|
||||
from six import string_types
|
||||
|
||||
from catalyst.algorithm import MarketOrder
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.exchange.exchange import Exchange
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
@@ -58,8 +58,12 @@ class CCXT(Exchange):
|
||||
|
||||
self._symbol_maps = [None, None]
|
||||
|
||||
markets_symbols = self.api.load_markets()
|
||||
log.debug('the markets:\n{}'.format(markets_symbols))
|
||||
try:
|
||||
markets_symbols = self.api.load_markets()
|
||||
log.debug('the markets:\n{}'.format(markets_symbols))
|
||||
|
||||
except ExchangeNotAvailable as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
self.name = exchange_name
|
||||
|
||||
@@ -185,10 +189,12 @@ class CCXT(Exchange):
|
||||
assets = [assets]
|
||||
|
||||
symbols = self.get_symbols(assets)
|
||||
|
||||
timeframe = self.get_timeframe(freq)
|
||||
delta = start_dt - get_epoch()
|
||||
ms = int(delta.total_seconds()) * 1000
|
||||
|
||||
ms = None
|
||||
if start_dt is not None:
|
||||
delta = start_dt - get_epoch()
|
||||
ms = int(delta.total_seconds()) * 1000
|
||||
|
||||
candles = dict()
|
||||
for asset in assets:
|
||||
|
||||
@@ -15,7 +15,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||
BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||
PricingDataNotLoadedError, \
|
||||
NoDataAvailableOnExchange, NoValueForField
|
||||
NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError
|
||||
from catalyst.exchange.exchange_utils import get_exchange_symbols, \
|
||||
get_frequency, resample_history_df
|
||||
|
||||
@@ -176,10 +176,18 @@ class Exchange:
|
||||
|
||||
assets = []
|
||||
for symbol in symbols:
|
||||
asset = self.get_asset(
|
||||
symbol, data_frequency, is_exchange_symbol, is_local
|
||||
)
|
||||
assets.append(asset)
|
||||
try:
|
||||
asset = self.get_asset(
|
||||
symbol, data_frequency, is_exchange_symbol, is_local
|
||||
)
|
||||
assets.append(asset)
|
||||
|
||||
except SymbolNotFoundOnExchange:
|
||||
log.debug(
|
||||
'skipping non-existent market {} {}'.format(
|
||||
self.name, symbol
|
||||
)
|
||||
)
|
||||
return assets
|
||||
|
||||
def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False,
|
||||
@@ -227,8 +235,10 @@ class Exchange:
|
||||
|
||||
elif data_frequency is not None:
|
||||
applies = (
|
||||
(data_frequency == 'minute' and a.end_minute is not None)
|
||||
or (data_frequency == 'daily' and a.end_daily is not None)
|
||||
(
|
||||
data_frequency == 'minute' and a.end_minute is not None)
|
||||
or (
|
||||
data_frequency == 'daily' and a.end_daily is not None)
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -441,6 +451,12 @@ class Exchange:
|
||||
Forward-fill missing values. Only has effect if field
|
||||
is 'price'.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Catalysts requires an end data with bar count both CCXT wants a
|
||||
start data with bar count. Since we have to make calculations here,
|
||||
we ensure that the last candle match the end_dt parameter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DataFrame
|
||||
@@ -451,6 +467,7 @@ class Exchange:
|
||||
frequency, data_frequency
|
||||
)
|
||||
adj_bar_count = candle_size * bar_count
|
||||
|
||||
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
||||
|
||||
# The get_history method supports multiple asset
|
||||
@@ -459,11 +476,23 @@ class Exchange:
|
||||
assets=assets,
|
||||
bar_count=bar_count,
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt
|
||||
end_dt=end_dt,
|
||||
)
|
||||
|
||||
series = dict()
|
||||
for asset in candles:
|
||||
if end_dt is not None and candles[asset]:
|
||||
delta = get_delta(candle_size, data_frequency)
|
||||
adj_end_dt = end_dt - delta
|
||||
last_candle = candles[asset][-1]
|
||||
|
||||
if last_candle['last_traded'] < adj_end_dt:
|
||||
raise LastCandleTooEarlyError(
|
||||
last_traded=last_candle['last_traded'],
|
||||
end_dt=adj_end_dt,
|
||||
exchange=self.name,
|
||||
)
|
||||
|
||||
asset_series = self.get_series_from_candles(
|
||||
candles=candles[asset],
|
||||
start_dt=start_dt,
|
||||
@@ -528,6 +557,7 @@ class Exchange:
|
||||
frequency, data_frequency
|
||||
)
|
||||
adj_bar_count = candle_size * bar_count
|
||||
|
||||
try:
|
||||
series = self.bundle.get_history_window_series_and_load(
|
||||
assets=assets,
|
||||
@@ -537,6 +567,7 @@ class Exchange:
|
||||
data_frequency=data_frequency,
|
||||
force_auto_ingest=force_auto_ingest
|
||||
)
|
||||
|
||||
except (PricingDataNotLoadedError, NoDataAvailableOnExchange):
|
||||
series = dict()
|
||||
|
||||
@@ -548,7 +579,7 @@ class Exchange:
|
||||
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
||||
trailing_dt = \
|
||||
series[asset].index[-1] + get_delta(1, data_frequency) \
|
||||
if asset in series else start_dt
|
||||
if asset in series else start_dt
|
||||
|
||||
# The get_history method supports multiple asset
|
||||
# Use the original frequency to let each api optimize
|
||||
@@ -590,24 +621,27 @@ class Exchange:
|
||||
|
||||
return df
|
||||
|
||||
def calculate_totals(self, positions=None):
|
||||
def calculate_totals(self, check_cash=False, positions=None):
|
||||
"""
|
||||
Update the portfolio cash and position balances based on the
|
||||
latest ticker prices.
|
||||
|
||||
"""
|
||||
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
||||
balances = self.get_balances()
|
||||
|
||||
cash = balances[self.base_currency]['free'] \
|
||||
if self.base_currency in balances else None
|
||||
cash = None
|
||||
if check_cash:
|
||||
balances = self.get_balances()
|
||||
|
||||
if cash is None:
|
||||
raise BaseCurrencyNotFoundError(
|
||||
base_currency=self.base_currency,
|
||||
exchange=self.name
|
||||
)
|
||||
log.debug('found base currency balance: {}'.format(cash))
|
||||
cash = balances[self.base_currency]['free'] \
|
||||
if self.base_currency in balances else None
|
||||
|
||||
if cash is None:
|
||||
raise BaseCurrencyNotFoundError(
|
||||
base_currency=self.base_currency,
|
||||
exchange=self.name
|
||||
)
|
||||
log.debug('found base currency balance: {}'.format(cash))
|
||||
|
||||
positions_value = 0.0
|
||||
if positions:
|
||||
|
||||
@@ -498,13 +498,18 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
||||
exchange_positions = \
|
||||
[positions[asset] for asset in assets]
|
||||
|
||||
exchange = self.exchanges[exchange_name] # Type: Exchange
|
||||
cash, positions_value = \
|
||||
exchange.calculate_totals(exchange_positions)
|
||||
check_cash = (not self.simulate_orders)
|
||||
|
||||
total_cash += cash
|
||||
exchange = self.exchanges[exchange_name] # Type: Exchange
|
||||
cash, positions_value = exchange.calculate_totals(
|
||||
positions=exchange_positions,
|
||||
check_cash=check_cash,
|
||||
)
|
||||
total_positions_value += positions_value
|
||||
|
||||
if cash is not None:
|
||||
total_cash += cash
|
||||
|
||||
for position in exchange_positions:
|
||||
tracker.update_position(
|
||||
asset=position.asset,
|
||||
@@ -512,7 +517,10 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
||||
last_sale_price=position.last_sale_price
|
||||
)
|
||||
|
||||
if total_cash < self.portfolio.cash:
|
||||
if cash is None:
|
||||
total_cash = self.portfolio.cash
|
||||
|
||||
elif total_cash < self.portfolio.cash:
|
||||
raise ValueError('Cash on exchanges is lower than the algo.')
|
||||
|
||||
return total_cash, total_positions_value
|
||||
|
||||
@@ -237,6 +237,7 @@ class DataPortalExchangeLive(DataPortalExchangeBase):
|
||||
|
||||
"""
|
||||
exchange = self.exchanges[exchange_name]
|
||||
|
||||
df = exchange.get_history_window(
|
||||
assets,
|
||||
end_dt,
|
||||
|
||||
@@ -263,3 +263,9 @@ class NotEnoughCapitalError(ZiplineError):
|
||||
'exchange should contain at least as much {base_currency} '
|
||||
'as the specified `capital_base`. The current balance {balance} is '
|
||||
'lower than the `capital_base`: {capital_base}').strip()
|
||||
|
||||
class LastCandleTooEarlyError(ZiplineError):
|
||||
msg = (
|
||||
'The trade date of the last candle {last_traded} is before the '
|
||||
'specified end date minus one candle {end_dt}. Please verify how '
|
||||
'{exchange} calculates the start date of OHLCV candles.').strip()
|
||||
|
||||
@@ -155,7 +155,7 @@ def _run(handle_data,
|
||||
exchanges[exchange_name] = get_exchange(
|
||||
exchange_name=exchange_name,
|
||||
base_currency=base_currency,
|
||||
must_authenticate=live,
|
||||
must_authenticate=(live and not simulate_orders),
|
||||
)
|
||||
|
||||
open_calendar = get_calendar('OPEN')
|
||||
|
||||
Reference in New Issue
Block a user