diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index fd326ad6..a09d86a1 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -3,7 +3,6 @@ from abc import ABCMeta, abstractmethod, abstractproperty from datetime import timedelta from time import sleep -import ccxt import numpy as np import pandas as pd from logbook import Logger @@ -14,13 +13,12 @@ from catalyst.exchange.bundle_utils import get_start_dt, \ get_delta, get_periods, get_periods_range from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ - BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ + SymbolNotFoundOnExchange, \ PricingDataNotLoadedError, \ NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError, \ - TickerNotFoundError, BalanceNotFoundError, BalanceTooLowError + TickerNotFoundError, BalanceNotFoundError, NotEnoughCashError from catalyst.exchange.exchange_utils import get_exchange_symbols, \ get_frequency, resample_history_df, has_bundle -from catalyst.utils.deprecate import deprecated log = Logger('Exchange', level=LOG_LEVEL) @@ -666,27 +664,12 @@ class Exchange: ) if free < amount: - limit = amount * (1 - self.low_balance_threshold) - if free < limit: - raise BalanceTooLowError( - currency=currency, - exchange=self.name, - free=free, - amount=amount, - ) - - log.debug( - 'detected lower balance for {} on {}: {} < {}, ' - 'updating position amount'.format( - currency, self.name, free, amount - ) - ) return free, True else: return free, False - def sync_positions(self, positions, check_balances=False): + def sync_positions(self, positions, cash=None, check_balances=False): """ Update the portfolio cash and position balances based on the latest ticker prices. @@ -702,20 +685,23 @@ class Exchange: """ log.debug('synchronizing portfolio with exchange {}'.format(self.name)) - cash = None + free_cash = 0.0 if check_balances: balances = self.get_balances() - 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, + if cash is not None: + free_cash, is_lower = self._check_low_balance( + currency=self.base_currency, balances=balances, + amount=cash, ) - log.debug('found base currency balance: {}'.format(cash)) + if is_lower: + raise NotEnoughCashError( + currency=self.base_currency, + exchange=self.name, + free=free_cash, + cash=cash, + ) positions_value = 0.0 if positions is not None: @@ -750,7 +736,7 @@ class Exchange: ) if is_lower: - log.debug( + log.warn( 'detected lower balance for {} on {}: {} < {}, ' 'updating position amount'.format( asset.symbol, self.name, free, position.amount @@ -758,7 +744,7 @@ class Exchange: ) position.amount = free - return cash, positions_value + return free_cash, positions_value def order(self, asset, amount, style): """Place an order. diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 82da231b..a9045a9d 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -10,6 +10,7 @@ # 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 copy import pickle import signal import sys @@ -18,7 +19,6 @@ from os import listdir from os.path import isfile, join from time import sleep -import copy import logbook import pandas as pd @@ -29,7 +29,7 @@ from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangePortfolioDataError, - OrderTypeNotSupported, CashTooLowError) + OrderTypeNotSupported) from catalyst.exchange.exchange_execution import ExchangeLimitOrder from catalyst.exchange.exchange_utils import ( save_algo_object, @@ -523,10 +523,9 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): cash, positions_value = exchange.sync_positions( positions=exchange_positions, check_balances=check_balances, + cash=self.portfolio.cash, ) - if cash is not None: - total_cash += cash - + total_cash += cash total_positions_value += positions_value # Applying modifications to the original positions @@ -541,13 +540,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): if not check_balances: total_cash = self.portfolio.cash - elif total_cash < self.portfolio.cash: - raise CashTooLowError( - currency=self.exchanges[0].base_currency, - free=total_cash, - cash=self.portfolio.cash, - ) - return total_cash, total_positions_value except ExchangeRequestError as e: diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 9a855c05..0e22868f 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -279,6 +279,15 @@ class NotEnoughCapitalError(ZiplineError): ).strip() +class NotEnoughCashError(ZiplineError): + msg = ( + 'Total {currency} amount on {exchange} is lower than the cash ' + 'reserved for this algo: {free} < {cash}. While trades can be made on ' + 'the exchange accounts outside of the algo, exchange must have enough ' + 'free {currency} to cover the algo cash.' + ).strip() + + class LastCandleTooEarlyError(ZiplineError): msg = ( 'The trade date of the last candle {last_traded} is before the ' @@ -306,12 +315,3 @@ class BalanceTooLowError(ZiplineError): 'add positions to hold a free amount greater than {amount}, or clean ' 'the state of this algo and restart.' ).strip() - - -class CashTooLowError(ZiplineError): - msg = ( - 'Total {currency} amount on exchanges is lower than the cash reserved ' - 'for this algo: {free} < {cash}. While trades can be made on the ' - 'exchange accounts outside of the algo, they must not compromise ' - 'the required amount of free {currency}.' - ).strip()