Source code for tensortrade.orders.order

# Copyright 2019 The TensorTrade Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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


from enum import Enum
from typing import Callable

from tensortrade.base import TimedIdentifiable
from tensortrade.base.exceptions import InvalidOrderQuantity, InsufficientFunds
from tensortrade.instruments import Quantity
from tensortrade.orders import Trade, TradeSide, TradeType


[docs]class OrderStatus(Enum): PENDING = 'pending' OPEN = 'open' CANCELLED = 'cancelled' PARTIALLY_FILLED = 'partially_filled' FILLED = 'filled' def __str__(self): return self.value
[docs]class Order(TimedIdentifiable): """ Responsibilities of the Order: 1. Confirming its own validity. 2. Tracking its trades and reporting it back to the broker. 3. Managing movement of quantities from order to order. 4. Generating the next order in its path given that there is a 'OrderSpec' for how to make the next order. 5. Managing its own state changes when it can. """ def __init__(self, step: int, side: TradeSide, trade_type: TradeType, pair: 'TradingPair', quantity: 'Quantity', portfolio: 'Portfolio', price: float, criteria: Callable[['Order', 'Exchange'], bool] = None, path_id: str = None, start: int = None, end: int = None): super().__init__() if quantity.size == 0: raise InvalidOrderQuantity(quantity) self.step = step self.side = side self.type = trade_type self.pair = pair self.quantity = quantity self.portfolio = portfolio self.price = price self.criteria = criteria self.path_id = path_id or self.id self.start = start or step self.end = end self.status = OrderStatus.PENDING self.filled_size = 0 self.remaining_size = self.size self._specs = [] self._listeners = [] self._trades = [] self.quantity.lock_for(self.path_id) @property def size(self) -> float: if self.pair.base is self.quantity.instrument: return self.quantity.size return self.quantity.size * self.price @property def price(self) -> float: return self._price @price.setter def price(self, price: float): self._price = price @property def base_instrument(self) -> 'Instrument': return self.pair.base @property def quote_instrument(self) -> 'Instrument': return self.pair.quote @property def trades(self): return self._trades @property def is_buy(self) -> bool: return self.side == TradeSide.BUY @property def is_sell(self) -> bool: return self.side == TradeSide.SELL @property def is_limit_order(self) -> bool: return self.type == TradeType.LIMIT @property def is_market_order(self) -> bool: return self.type == TradeType.MARKET
[docs] def is_executable_on(self, exchange: 'Exchange'): if not exchange.is_pair_tradable(self.pair): return False return self.criteria is None or self.criteria(self, exchange)
[docs] def is_complete(self): return self.remaining_size == 0
[docs] def add_order_spec(self, order_spec: 'OrderSpec') -> 'Order': self._specs = [order_spec] + self._specs return self
[docs] def attach(self, listener: 'OrderListener'): self._listeners += [listener]
[docs] def detach(self, listener: 'OrderListener'): self._listeners.remove(listener)
[docs] def execute(self, exchange: 'Exchange'): self.status = OrderStatus.OPEN instrument = self.side.instrument(self.pair) wallet = self.portfolio.get_wallet(exchange.id, instrument=instrument) if self.path_id not in wallet.locked.keys(): try: wallet -= self.size * instrument except InsufficientFunds: size = wallet.balance.size wallet -= size * instrument self.quantity = Quantity(instrument, size, path_id=self.path_id) wallet += self.quantity if self.portfolio.order_listener: self.attach(self.portfolio.order_listener) for listener in self._listeners or []: listener.on_execute(self, exchange) exchange.execute_order(self, self.portfolio)
[docs] def fill(self, exchange: 'Exchange', trade: Trade): self.status = OrderStatus.PARTIALLY_FILLED fill_size = round(trade.size + trade.commission.size, self.pair.base.precision) self.filled_size += fill_size self.remaining_size -= fill_size for listener in self._listeners or []: listener.on_fill(self, exchange, trade)
[docs] def complete(self, exchange: 'Exchange') -> 'Order': self.status = OrderStatus.FILLED order = None if self._specs: order_spec = self._specs.pop() order = order_spec.create_order(self, exchange) for listener in self._listeners or []: listener.on_complete(self, exchange) self._listeners = [] return order or self.release()
[docs] def cancel(self): self.status = OrderStatus.CANCELLED for listener in self._listeners or []: listener.on_cancel(self) self._listeners = [] self.release()
[docs] def release(self): for wallet in self.portfolio.wallets: wallet.deallocate(self.path_id)
[docs] def to_dict(self): return { "id": self.id, "step": self.step, "status": self.status, "type": self.type, "side": self.side, "pair": self.pair, "quantity": self.quantity, "size": self.size, "filled_size": self.filled_size, "price": self.price, "criteria": self.criteria, "path_id": self.path_id, "created_at": self.created_at }
[docs] def to_json(self): return { "id": str(self.id), "step": int(self.step), "status": str(self.status), "type": str(self.type), "side": str(self.side), "base_symbol": str(self.pair.base.symbol), "quote_symbol": str(self.pair.quote.symbol), "quantity": str(self.quantity), "size": float(self.size), "filled_size": self.filled_size, "price": float(self.price), "criteria": str(self.criteria), "path_id": str(self.path_id), "created_at": str(self.created_at) }
def __iadd__(self, recipe: 'OrderSpec') -> 'Order': return self.add_order_spec(recipe) def __str__(self): data = ['{}={}'.format(k, v) for k, v in self.to_dict().items()] return '<{}: {}>'.format(self.__class__.__name__, ', '.join(data)) def __repr__(self): return str(self)