class PBR(TensorTradeRewardScheme): registered_name = "pbr" def __init__(self, price: 'Stream'): super().__init__() self.position = -1 r = Stream.sensor(price, lambda p: p.value, dtype="float").diff() position = Stream.sensor(self, lambda rs: rs.position, dtype="float") reward = (r * position).fillna(0).rename("reward") self.feed = DataFeed([reward]) self.feed.compile() def on_action(self, action: int): self.position = -1 if action == 0 else 1 def get_reward(self, portfolio: 'Portfolio'): return self.feed.next()["reward"] def reset(self): self.position = -1 self.feed.reset()
def test_price_ds(): btc_price = Stream.source([7000, 7500, 8300], dtype="float").rename("USD-BTC") eth_price = Stream.source([200, 212, 400], dtype="float").rename("USD-ETH") feed = DataFeed([btc_price, eth_price]) assert feed.next() == {"USD-BTC": 7000, "USD-ETH": 200}
def test_exchange_feed(): btc_price = Stream.source([7000, 7500, 8300], dtype="float").rename("USD-BTC") eth_price = Stream.source([200, 212, 400], dtype="float").rename("USD-ETH") exchange = Exchange("bitfinex", service=execute_order)(btc_price, eth_price) feed = DataFeed(exchange.streams()) assert feed.next() == {"bitfinex:/USD-BTC": 7000, "bitfinex:/USD-ETH": 200}
def assert_op(streams, expected): feed = DataFeed(streams) feed.compile() actual = [] while feed.has_next(): d = feed.next() v = None for k in d.keys(): if v is None: v = d[k] else: assert d[k] == v actual += [v] np.testing.assert_allclose(actual, expected)
class PBR(TensorTradeRewardScheme): """A reward scheme for position-based returns. * Let :math:`p_t` denote the price at time t. * Let :math:`x_t` denote the position at time t. * Let :math:`R_t` denote the reward at time t. Then the reward is defined as, :math:`R_{t} = (p_{t} - p_{t-1}) \cdot x_{t}`. Parameters ---------- price : `Stream` The price stream to use for computing rewards. """ registered_name = "pbr" def __init__(self, price: 'Stream') -> None: super().__init__() self.position = -1 ## PBR works when commissions are negligible. ## Need to modify the following line to make it work for cases where commissions are substantial: ###NOTE: the agent learns to hold its position when abs(r) < commission #r = Stream.sensor(price, lambda p: p.value, dtype="float").diff() r = Stream.sensor(price, lambda p: p.value, dtype="float").pct_change() position = Stream.sensor(self, lambda rs: rs.position, dtype="float") reward = ((position * r).fillna(0)).rename("reward") self.feed = DataFeed([reward]) self.feed.compile() def on_action(self, action: int, hasOrder: bool, current_step: int) -> None: self.position = -1 if action == 0 else 1 def get_reward(self, portfolio: 'Portfolio') -> float: return self.feed.next()["reward"] def reset(self) -> None: """Resets the `position` and `feed` of the reward scheme.""" self.position = -1 self.feed.reset()
class SinglePositionProfit(TensorTradeRewardScheme): """A reward scheme for single position return. Parameters ---------- price : `Stream` The price stream to use for computing rewards. """ registered_name = "spp" def __init__(self, price: 'Stream') -> None: super().__init__() self.position = -1 self.executed = 0 r = (Stream.sensor(price, lambda p: p.value, dtype="float").pct_change()) #.log10()).diff() position = Stream.sensor(self, lambda rs: rs.position, dtype="float") executed = Stream.sensor(self, lambda rs: rs.executed, dtype="float") reward = (position * r - executed).fillna(0).rename("reward") self.feed = DataFeed([reward]) self.feed.compile() def on_action(self, action: int, hasOrder: bool, current_step: int) -> None: self.position = -1 if action == 0 else 1 performance = pd.DataFrame.from_dict(portfolio.performance, orient='index') net_worths = performance["net_worth"] self.executed = len(self.broker.executed) * 0.002606 def get_reward(self, portfolio: 'Portfolio') -> float: #n_executed = len(self.broker.executed)/2 return (self.feed.next()["reward"]) # - n_executed*0.002606) def reset(self) -> None: """Resets the `position` and `feed` of the reward scheme.""" self.position = -1 self.feed.reset() self.executed = 0
class PBR(TensorTradeRewardScheme): """A reward scheme for position-based returns. * Let :math:`p_t` denote the price at time t. * Let :math:`x_t` denote the position at time t. * Let :math:`R_t` denote the reward at time t. Then the reward is defined as, :math:`R_{t} = (p_{t} - p_{t-1}) \cdot x_{t}`. Parameters ---------- price : `Stream` The price stream to use for computing rewards. """ registered_name = "pbr" def __init__(self, price: 'Stream') -> None: super().__init__() self.position = -1 r = Stream.sensor(price, lambda p: p.value, dtype="float").diff() position = Stream.sensor(self, lambda rs: rs.position, dtype="float") reward = (position * r).fillna(0).rename("reward") self.feed = DataFeed([reward]) self.feed.compile() def on_action(self, action: int) -> None: self.position = -1 if action == 0 else 1 def get_reward(self, portfolio: 'Portfolio') -> float: reward_ = self.feed.next()["reward"] #print("Reward: {}".format(reward_)) return reward_ def reset(self) -> None: """Resets the `position` and `feed` of the reward scheme.""" self.position = -1 self.feed.reset()
def test_exchange_with_wallets_feed(): ex1 = Exchange("bitfinex", service=execute_order)( Stream.source([7000, 7500, 8300], dtype="float").rename("USD-BTC"), Stream.source([200, 212, 400], dtype="float").rename("USD-ETH")) ex2 = Exchange("binance", service=execute_order)( Stream.source([7005, 7600, 8200], dtype="float").rename("USD-BTC"), Stream.source([201, 208, 402], dtype="float").rename("USD-ETH"), Stream.source([56, 52, 60], dtype="float").rename("USD-LTC")) wallet_btc = Wallet(ex1, 10 * BTC) wallet_btc_ds = _create_wallet_source(wallet_btc) wallet_usd = Wallet(ex2, 1000 * USD) wallet_usd.withdraw(quantity=400 * USD, reason="test") wallet_usd.deposit(quantity=Quantity(USD, 400, path_id="fake_id"), reason="test") wallet_usd_ds = _create_wallet_source(wallet_usd, include_worth=False) streams = ex1.streams() + ex2.streams() + wallet_btc_ds + wallet_usd_ds feed = DataFeed(streams) assert feed.next() == { "bitfinex:/USD-BTC": 7000, "bitfinex:/USD-ETH": 200, "bitfinex:/BTC:/free": 10, "bitfinex:/BTC:/locked": 0, "bitfinex:/BTC:/total": 10, "bitfinex:/BTC:/worth": 70000, "binance:/USD-BTC": 7005, "binance:/USD-ETH": 201, "binance:/USD-LTC": 56, "binance:/USD:/free": 600, "binance:/USD:/locked": 400, "binance:/USD:/total": 1000 }
# # Setup trading env coinbase = Exchange("coinbase", service=execute_order)(Stream.source( prices["close"].tolist(), dtype="float").rename("USD-BTC")) portfolio = Portfolio( USD, [Wallet(coinbase, 10000 * USD)], ) with NameSpace("coinbase"): streams = [ Stream.source(df[c].tolist(), dtype="float").rename(c) for c in df.columns ] feed = DataFeed(streams) print(feed.next()) # # Screen log env = default.create( portfolio=portfolio, action_scheme="managed-risk", reward_scheme="risk-adjusted", feed=feed, renderer="screen-log", # ScreenLogger used with default settings window_size=20, ) print(env.observation_space) print(env.action_space) # model = A2C(MlpLstmPolicy, env, verbose=1)
class IntradayObserver(Observer): """The IntradayObserver observer that is compatible with the other `default` components. Parameters ---------- portfolio : `Portfolio` The portfolio to be used to create the internal data feed mechanism. feed : `DataFeed` The feed to be used to collect observations to the observation window. renderer_feed : `DataFeed` The feed to be used for giving information to the renderer. stop_time : datetime.time The time at which the episode will stop. window_size : int The size of the observation window. min_periods : int The amount of steps needed to warmup the `feed`. randomize : bool Whether or not to select a random episode when reset. **kwargs : keyword arguments Additional keyword arguments for observer creation. Attributes ---------- feed : `DataFeed` The master feed in charge of streaming the internal, external, and renderer data feeds. stop_time : datetime.time The time at which the episode will stop. window_size : int The size of the observation window. min_periods : int The amount of steps needed to warmup the `feed`. randomize : bool Whether or not a random episode is selected when reset. history : `ObservationHistory` The observation history. renderer_history : `List[dict]` The history of the renderer data feed. """ def __init__(self, portfolio: 'Portfolio', feed: 'DataFeed' = None, renderer_feed: 'DataFeed' = None, stop_time: 'datetime.time' = dt.time(16, 0, 0), window_size: int = 1, min_periods: int = None, randomize: bool = False, **kwargs) -> None: internal_group = Stream.group( _create_internal_streams(portfolio)).rename("internal") external_group = Stream.group(feed.inputs).rename("external") if renderer_feed: renderer_group = Stream.group( renderer_feed.inputs).rename("renderer") self.feed = DataFeed( [internal_group, external_group, renderer_group]) else: self.feed = DataFeed([internal_group, external_group]) self.stop_time = stop_time self.window_size = window_size self.min_periods = min_periods self.randomize = randomize self._observation_dtype = kwargs.get('dtype', np.float32) self._observation_lows = kwargs.get('observation_lows', -np.inf) self._observation_highs = kwargs.get('observation_highs', np.inf) self.history = ObservationHistory(window_size=window_size) initial_obs = self.feed.next()["external"] initial_obs.pop('timestamp', None) n_features = len(initial_obs.keys()) self._observation_space = Box(low=self._observation_lows, high=self._observation_highs, shape=(self.window_size, n_features), dtype=self._observation_dtype) self.feed = self.feed.attach(portfolio) self.renderer_history = [] if self.randomize: self.num_episodes = 0 while (self.feed.has_next()): ts = self.feed.next()["external"]["timestamp"] if ts.time() == self.stop_time: self.num_episodes += 1 self.feed.reset() self.warmup() self.stop = False @property def observation_space(self) -> Space: return self._observation_space def warmup(self) -> None: """Warms up the data feed. """ if self.min_periods is not None: for _ in range(self.min_periods): if self.has_next(): obs_row = self.feed.next()["external"] obs_row.pop('timestamp', None) self.history.push(obs_row) def observe(self, env: 'TradingEnv') -> np.array: """Observes the environment. As a consequence of observing the `env`, a new observation is generated from the `feed` and stored in the observation history. Returns ------- `np.array` The current observation of the environment. """ data = self.feed.next() # Save renderer information to history if "renderer" in data.keys(): self.renderer_history += [data["renderer"]] # Push new observation to observation history obs_row = data["external"] try: obs_ts = obs_row.pop('timestamp') except KeyError: raise KeyError( "Include Stream of Timestamps named 'timestamp' in feed") self.history.push(obs_row) # Check if episode should be stopped if obs_ts.time() == self.stop_time: self.stop = True obs = self.history.observe() obs = obs.astype(self._observation_dtype) return obs def has_next(self) -> bool: """Checks if there is another observation to be generated. Returns ------- bool Whether there is another observation to be generated. """ return self.feed.has_next() and not self.stop def reset(self) -> None: """Resets the observer""" self.renderer_history = [] self.history.reset() if self.randomize or not self.feed.has_next(): self.feed.reset() if self.randomize: episode_num = 0 while (episode_num < randrange(self.num_episodes)): ts = self.feed.next()["external"]["timestamp"] if ts.time() == self.stop_time: episode_num += 1 self.warmup() self.stop = False
class TensorTradeObserver(Observer): """The TensorTrade observer that is compatible with the other `default` components. Parameters ---------- portfolio : `Portfolio` The portfolio to be used to create the internal data feed mechanism. feed : `DataFeed` The feed to be used to collect observations to the observation window. renderer_feed : `DataFeed` The feed to be used for giving information to the renderer. window_size : int The size of the observation window. min_periods : int The amount of steps needed to warmup the `feed`. **kwargs : keyword arguments Additional keyword arguments for observer creation. Attributes ---------- feed : `DataFeed` The master feed in charge of streaming the internal, external, and renderer data feeds. window_size : int The size of the observation window. min_periods : int The amount of steps needed to warmup the `feed`. history : `ObservationHistory` The observation history. renderer_history : `List[dict]` The history of the renderer data feed. """ def __init__(self, portfolio: 'Portfolio', feed: 'DataFeed' = None, renderer_feed: 'DataFeed' = None, window_size: int = 1, min_periods: int = None, **kwargs) -> None: internal_group = Stream.group( _create_internal_streams(portfolio)).rename("internal") external_group = Stream.group(feed.inputs).rename("external") if renderer_feed: renderer_group = Stream.group( renderer_feed.inputs).rename("renderer") self.feed = DataFeed( [internal_group, external_group, renderer_group]) else: self.feed = DataFeed([internal_group, external_group]) self.window_size = window_size self.min_periods = min_periods self._observation_dtype = kwargs.get('dtype', np.float32) self._observation_lows = kwargs.get('observation_lows', -np.inf) self._observation_highs = kwargs.get('observation_highs', np.inf) self.history = ObservationHistory(window_size=window_size) initial_obs = self.feed.next()["external"] n_features = len(initial_obs.keys()) self._observation_space = Box(low=self._observation_lows, high=self._observation_highs, shape=(self.window_size, n_features), dtype=self._observation_dtype) self.feed = self.feed.attach(portfolio) self.renderer_history = [] self.feed.reset() self.warmup() @property def observation_space(self) -> Space: return self._observation_space def warmup(self) -> None: """Warms up the data feed. """ if self.min_periods is not None: for _ in range(self.min_periods): if self.has_next(): obs_row = self.feed.next()["external"] self.history.push(obs_row) def observe(self, env: 'TradingEnv') -> np.array: """Observes the environment. As a consequence of observing the `env`, a new observation is generated from the `feed` and stored in the observation history. Returns ------- `np.array` The current observation of the environment. """ data = self.feed.next() # Save renderer information to history if "renderer" in data.keys(): self.renderer_history += [data["renderer"]] # Push new observation to observation history obs_row = data["external"] self.history.push(obs_row) obs = self.history.observe() obs = obs.astype(self._observation_dtype) return obs def has_next(self) -> bool: """Checks if there is another observation to be generated. Returns ------- bool Whether there is another observation to be generated. """ return self.feed.has_next() def reset(self) -> None: """Resets the observer""" self.renderer_history = [] self.history.reset() self.feed.reset() self.warmup()
def test_create_internal_data_feed(): ex1 = Exchange("bitfinex", service=execute_order)( Stream.source([7000, 7500, 8300], dtype="float").rename("USD-BTC"), Stream.source([200, 212, 400], dtype="float").rename("USD-ETH")) ex2 = Exchange("binance", service=execute_order)( Stream.source([7005, 7600, 8200], dtype="float").rename("USD-BTC"), Stream.source([201, 208, 402], dtype="float").rename("USD-ETH"), Stream.source([56, 52, 60], dtype="float").rename("USD-LTC")) portfolio = Portfolio(USD, [ Wallet(ex1, 10000 * USD), Wallet(ex1, 10 * BTC), Wallet(ex1, 5 * ETH), Wallet(ex2, 1000 * USD), Wallet(ex2, 5 * BTC), Wallet(ex2, 20 * ETH), Wallet(ex2, 3 * LTC), ]) feed = DataFeed(_create_internal_streams(portfolio)) data = { "bitfinex:/USD-BTC": 7000, "bitfinex:/USD-ETH": 200, "bitfinex:/USD:/free": 10000, "bitfinex:/USD:/locked": 0, "bitfinex:/USD:/total": 10000, "bitfinex:/BTC:/free": 10, "bitfinex:/BTC:/locked": 0, "bitfinex:/BTC:/total": 10, "bitfinex:/BTC:/worth": 7000 * 10, "bitfinex:/ETH:/free": 5, "bitfinex:/ETH:/locked": 0, "bitfinex:/ETH:/total": 5, "bitfinex:/ETH:/worth": 200 * 5, "binance:/USD-BTC": 7005, "binance:/USD-ETH": 201, "binance:/USD-LTC": 56, "binance:/USD:/free": 1000, "binance:/USD:/locked": 0, "binance:/USD:/total": 1000, "binance:/BTC:/free": 5, "binance:/BTC:/locked": 0, "binance:/BTC:/total": 5, "binance:/BTC:/worth": 7005 * 5, "binance:/ETH:/free": 20, "binance:/ETH:/locked": 0, "binance:/ETH:/total": 20, "binance:/ETH:/worth": 201 * 20, "binance:/LTC:/free": 3, "binance:/LTC:/locked": 0, "binance:/LTC:/total": 3, "binance:/LTC:/worth": 56 * 3, } bitfinex_net_worth = 10000 + (10 * 7000) + (5 * 200) binance_net_worth = 1000 + (5 * 7005) + (20 * 201) + (3 * 56) data['net_worth'] = sum( data[k] if k.endswith("worth") or k.endswith("USD:/total") else 0 for k in data.keys()) assert feed.next() == data
from tensortrade.oms.instruments import USD, BTC from tensortrade.oms.wallets import Wallet, Portfolio bitfinex = Exchange("bitfinex", service=execute_order)(Stream.source( price_history['Close'].tolist(), dtype="float").rename("USD-BTC")) portfolio = Portfolio(USD, [ Wallet(bitfinex, 10000 * USD), Wallet(bitfinex, 10 * BTC), ]) with NameSpace("bitfinex"): streams = [ Stream.source(dataset[c].tolist(), dtype="float").rename(c) for c in dataset.columns ] feed = DataFeed(streams) feed.next() def create_env(config): p = Stream.source(price_history['Close'].tolist(), dtype="float").rename("USD-BTC") bitfinex = Exchange("bitfinex", service=execute_order)(Stream.source( price_history['Close'].tolist(), dtype="float").rename("USD-BTC")) cash = Wallet(bitfinex, 100000 * USD) asset = Wallet(bitfinex, 0 * BTC) portfolio = Portfolio(USD, [cash, asset]) feed = DataFeed([ p, p.rolling(window=10).mean().rename("fast"), p.rolling(window=50).mean().rename("medium"), p.rolling(window=100).mean().rename("slow"),