diff --git a/tensortrade/exchanges/live/ccxt_exchange.py b/tensortrade/exchanges/live/ccxt_exchange.py index a8307044a..5a5d40175 100644 --- a/tensortrade/exchanges/live/ccxt_exchange.py +++ b/tensortrade/exchanges/live/ccxt_exchange.py @@ -30,23 +30,45 @@ class CCXTExchange(Exchange): def __init__(self, exchange: Union[Exchange, str] = 'coinbase', **kwargs): super(CCXTExchange, self).__init__( - dtype=self.default('dtype', np.float16, kwargs), - feature_pipeline=self.default('feature_pipeline', None, kwargs) + dtype=self.default('dtype', np.float32, kwargs), + feature_pipeline=self.default('feature_pipeline', None, kwargs), + **kwargs ) + exchange = self.default('exchange', exchange) self._exchange = getattr(ccxt, exchange)() if \ isinstance(exchange, str) else exchange self._credentials = self.default('credentials', None, kwargs) - self._exchange.enableRateLimit = self.default('enable_rate_limit', True, kwargs) - self._markets = self._exchange.load_markets() + self._timeframe = self.default('timeframe', '10m', kwargs) self._observation_type = self.default('observation_type', 'trades', kwargs) self._observation_symbol = self.default('observation_symbol', 'ETH/BTC', kwargs) - self._timeframe = self.default('timeframe', '10m', kwargs) - self._window_size = self.default('window_size', 1, kwargs) self._async_timeout_in_ms = self.default('async_timeout_in_ms', 15, kwargs) self._max_trade_wait_in_sec = self.default('max_trade_wait_in_sec', 15, kwargs) + self._exchange.enableRateLimit = self.default('enable_rate_limit', True, kwargs) + + self._markets = self._exchange.load_markets() + + self._min_trade_amount = float( + self._markets[self._observation_symbol]['limits']['amount']['min']) + self._max_trade_amount = float( + self._markets[self._observation_symbol]['limits']['amount']['max']) + self._min_trade_price = float( + self._markets[self._observation_symbol]['limits']['price']['min']) + self._max_trade_price = float( + self._markets[self._observation_symbol]['limits']['price']['max']) + + @property + def data_frame(self) -> pd.DataFrame: + return self._data_frame + + @data_frame.setter + def data_frame(self, data_frame: pd.DataFrame): + self._data_frame = data_frame + + if len(self._data_frame) >= self._window_size: + self._data_frame = self._data_frame.iloc[-(self._window_size - 1):] @property def base_precision(self) -> float: @@ -91,25 +113,6 @@ def trades(self) -> List[Trade]: def performance(self) -> pd.DataFrame: return self._performance - @property - def generated_space(self) -> Space: - low_price = float(self._markets[self._observation_symbol]['limits']['price']['min']) - high_price = float(self._markets[self._observation_symbol]['limits']['price']['max']) - low_volume = float(self._markets[self._observation_symbol]['limits']['amount']['min']) - high_volume = float(self._markets[self._observation_symbol]['limits']['amount']['max']) - - if self._observation_type == 'ohlcv': - low = np.array([low_price, low_price, low_price, low_price, low_volume]) - high = np.array([high_price, high_price, high_price, high_price, high_volume]) - else: - low = np.array([0, low_price, low_price, low_price * low_volume]) - high = np.array([1, high_price, high_volume, high_price * high_volume]) - - low = np.asarray([max(np.finfo(self._dtype).min, x) for x in low], dtype=self._dtype) - high = np.asarray([min(np.finfo(self._dtype).max, x) for x in high], dtype=self._dtype) - - return Box(low=low, high=high, dtype=self._dtype) - @property def generated_columns(self) -> List[str]: if self._observation_type == 'ohlcv': @@ -117,6 +120,13 @@ def generated_columns(self) -> List[str]: return list(['side', 'price', 'amount', 'cost']) + @property + def observation_columns(self) -> List[str]: + if self.has_next_observation: + self._next_observation().columns + + return None + @property def has_next_observation(self) -> bool: if self._observation_type == 'ohlcv': @@ -139,17 +149,15 @@ def _next_observation(self) -> pd.DataFrame: obs = pd.DataFrame(obs, columns=self.generated_columns) obs = pd.concat([self._data_frame, obs], ignore_index=True) - self._data_frame = obs + self.data_frame = obs - if len(self._data_frame) >= self._window_size: - self._data_frame = self._data_frame.iloc[-(self._window_size - 1):] + if self._feature_pipeline is not None: + obs = self._feature_pipeline.transform(obs) if len(obs) < self._window_size: - padding = np.zeros((len(self.generated_columns), self._window_size - len(obs))) - obs = pd.concat([pd.DataFrame(padding), obs], ignore_index=True) - - if self._feature_pipeline is not None: - obs = self._feature_pipeline.transform(obs, self.generated_space) + padding = np.zeros((self._window_size - len(obs), len(self.observation_columns))) + padding = pd.DataFrame(padding, columns=self.observation_columns) + obs = pd.concat([padding, obs], ignore_index=True) return obs diff --git a/tensortrade/exchanges/simulated/simulated_exchange.py b/tensortrade/exchanges/simulated/simulated_exchange.py index 343ca80cb..bafd5ae78 100644 --- a/tensortrade/exchanges/simulated/simulated_exchange.py +++ b/tensortrade/exchanges/simulated/simulated_exchange.py @@ -34,28 +34,17 @@ class SimulatedExchange(Exchange): def __init__(self, data_frame: pd.DataFrame = None, **kwargs): super().__init__( - dtype=self.default('dtype', np.float16), - feature_pipeline=self.default('feature_pipeline', None) + dtype=self.default('dtype', np.float32), + feature_pipeline=self.default('feature_pipeline', None), + **kwargs ) + self._commission_percent = self.default('commission_percent', 0.3, kwargs) self._base_precision = self.default('base_precision', 2, kwargs) self._instrument_precision = self.default('instrument_precision', 8, kwargs) - self._min_trade_price = self.default('min_trade_price', 1e-6, kwargs) - self._max_trade_price = self.default('max_trade_price', 1e6, kwargs) - self._min_trade_amount = self.default('min_trade_amount', 1e-3, kwargs) - self._max_trade_amount = self.default('max_trade_amount', 1e6, kwargs) - self._min_order_amount = self.default('min_order_amount', 1e-3, kwargs) - self._initial_balance = self.default('initial_balance', 1e4, kwargs) - self._observation_columns = self.default( - 'observation_columns', - ['open', 'high', 'low', 'close', 'volume'], - kwargs - ) self._price_column = self.default('price_column', 'close', kwargs) - self._window_size = self.default('window_size', 1, kwargs) self._pretransform = self.default('pretransform', True, kwargs) - self._price_history = None self.data_frame = self.default('data_frame', data_frame) @@ -71,10 +60,12 @@ def data_frame(self) -> pd.DataFrame: def data_frame(self, data_frame: pd.DataFrame): if not isinstance(data_frame, pd.DataFrame): self._data_frame = data_frame + self._price_history = None return - self._data_frame = data_frame[self._observation_columns] + self._data_frame = data_frame self._price_history = data_frame[self._price_column] + self._pre_transformed_columns = data_frame.columns if self._pretransform: self.transform_data_frame() @@ -113,20 +104,16 @@ def performance(self) -> pd.DataFrame: return self._performance @property - def generated_space(self) -> Space: - low = np.array([self._min_trade_price, ] * - (len(self._observation_columns)-1) + [self._min_trade_amount, ]) - high = np.array([self._max_trade_price, ] * - (len(self._observation_columns) - 1) + [self._max_trade_amount, ]) + def observation_columns(self) -> List[str]: + if self._data_frame is None: + return None - low = np.asarray([max(np.finfo(self._dtype).min, x) for x in low], dtype=self._dtype) - high = np.asarray([min(np.finfo(self._dtype).max, x) for x in high], dtype=self._dtype) + data_frame = self._data_frame.iloc[0:10] - return Box(low=low, high=high, dtype=self._dtype) + if self._feature_pipeline is not None: + data_frame = self._feature_pipeline.transform(data_frame) - @property - def generated_columns(self) -> List[str]: - return list(self._observation_columns) + return data_frame.select_dtypes(include=[np.float, np.number]).columns @property def has_next_observation(self) -> bool: @@ -138,12 +125,15 @@ def _next_observation(self) -> pd.DataFrame: obs = self._data_frame.iloc[lower_range:upper_range] + if not self._pretransform and self._feature_pipeline is not None: + obs = self._feature_pipeline.transform(obs) + if len(obs) < self._window_size: - padding = np.zeros((len(self.generated_columns), self._window_size - len(obs))) - obs = pd.concat([pd.DataFrame(padding), obs], ignore_index=True) + padding = np.zeros((self._window_size - len(obs), len(self.observation_columns))) + padding = pd.DataFrame(padding, columns=self.observation_columns) + obs = pd.concat([padding, obs], ignore_index=True) - if not self._pretransform and self._feature_pipeline is not None: - obs = self._feature_pipeline.transform(obs, self.generated_space) + obs = obs.select_dtypes(include='number') self._current_step += 1 @@ -151,8 +141,7 @@ def _next_observation(self) -> pd.DataFrame: def transform_data_frame(self) -> bool: if self._feature_pipeline is not None: - self._data_frame = self._feature_pipeline.transform(self._data_frame, - self.generated_space) + self._data_frame = self._feature_pipeline.transform(self._data_frame) def current_price(self, symbol: str) -> float: if self._price_history is not None: @@ -161,12 +150,12 @@ def current_price(self, symbol: str) -> float: return 0 def _is_valid_trade(self, trade: Trade) -> bool: - if trade.trade_type is TradeType.MARKET_BUY or trade.trade_type is TradeType.LIMIT_BUY: - return trade.amount >= self._min_order_amount and self._balance >= trade.amount * trade.price - elif trade.trade_type is TradeType.MARKET_SELL or trade.trade_type is TradeType.LIMIT_SELL: - return trade.amount >= self._min_order_amount and self._portfolio.get(trade.symbol, 0) >= trade.amount + if trade.is_buy and self._balance < trade.amount * trade.price: + return False + elif trade.is_sell and self._portfolio.get(trade.symbol, 0) < trade.amount: + return False - return True + return trade.amount >= self._min_trade_amount and trade.amount <= self._max_trade_amount def _update_account(self, trade: Trade): if self._is_valid_trade(trade) and not trade.is_hold: