def get_orders(self, action: int, portfolio: 'Portfolio') -> 'List[Order]': if action == 0: return [] (ep, (criteria, proportion, duration, side)) = self.actions[action] instrument = side.instrument(ep.pair) wallet = portfolio.get_wallet(ep.exchange.id, instrument=instrument) balance = wallet.balance.as_float() size = (balance * proportion) size = min(balance, size) quantity = (size * instrument).quantize() if size < 10 ** -instrument.precision \ or size < self.min_order_pct * portfolio.net_worth \ or size < self.min_order_abs: return [] order = Order(step=self.clock.step, side=side, trade_type=self._trade_type, exchange_pair=ep, price=ep.price, quantity=quantity, criteria=criteria, end=self.clock.step + duration if duration else None, portfolio=portfolio) if self._order_listener is not None: order.attach(self._order_listener) return [order]
def test_iadd(mock_exchange_class, mock_order_spec_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) # Market order order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) order_spec = mock_order_spec_class.return_value assert len(order._specs) == 0 order.add_order_spec(order_spec) assert len(order._specs) == 1 assert order_spec in order._specs
def test_on_complete(mock_trade_class, mock_exchange_class, complete_listener): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "bitfinex" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) order.attach(complete_listener) order.execute() trade = mock_trade_class.return_value trade.size = Decimal(5197.00) trade.quantity = trade.size * USD trade.commission = 3.00 * USD order.fill(trade) assert not complete_listener.listened order.complete() assert complete_listener.listened
def test_attach(mock_exchange_class, mock_order_listener_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) listener = mock_order_listener_class.return_value assert len(order.listeners) == 0 order.attach(listener) assert len(order.listeners) == 1 assert listener in order.listeners
def test_is_complete(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) # Market order order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) assert not order.is_complete order.remaining = 0 * USD assert order.is_complete
def test_properties(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.LIMIT, quantity=5000.00 * USD, portfolio=portfolio, price=7000.00) assert order assert order.step == 0 assert order.base_instrument == USD assert order.quote_instrument == BTC assert order.size == 5000.00 * USD assert order.price == 7000.00 assert order.trades == [] assert order.is_buy assert not order.is_sell assert not order.is_market_order assert order.is_limit_order
def test_str(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) pattern = re.compile("<[A-Z][a-zA-Z]*:\\s(\\w+=.*,\\s)*(\\w+=.*)>") string = str(order) assert string assert string == pattern.fullmatch(string).string
def create_order(self, order: 'Order') -> 'Order': """Creates an order following from another order. Parameters ---------- order : `Order` The previous order in the order path. Returns ------- `Order` The order created from the specification parameters and the parameters of `order`. """ wallet_instrument = self.side.instrument(self.exchange_pair.pair) exchange = order.exchange_pair.exchange wallet = order.portfolio.get_wallet(exchange.id, instrument=wallet_instrument) quantity = wallet.locked.get(order.path_id, None) if not quantity or quantity.size == 0: return None return Order(step=exchange.clock.step, side=self.side, trade_type=self.type, exchange_pair=self.exchange_pair, quantity=quantity, portfolio=order.portfolio, price=self.exchange_pair.price, criteria=self.criteria, end=order.end, path_id=order.path_id)
def test_init(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000 * USD, price=7000, portfolio=portfolio) assert order assert order.id assert order.path_id assert order.step == 0 assert order.quantity.instrument == USD assert order.remaining == order.quantity assert isinstance(order.pair, TradingPair) assert order.pair.base == USD assert order.pair.quote == BTC
def market_order(side: "TradeSide", exchange_pair: "ExchangePair", price: float, size: float, portfolio: "Portfolio") -> "Order": """Creates a market order. Parameters ---------- side : `TradeSide` The side of the order. exchange_pair : `ExchangePair` The exchange pair to perform the order for. price : float The current price. size : float The size of the order. portfolio : `Portfolio` The portfolio being used in the order. Returns ------- `Order` A market order. """ instrument = side.instrument(exchange_pair.pair) order = Order(step=portfolio.clock.step, side=side, trade_type=TradeType.MARKET, exchange_pair=exchange_pair, price=price, quantity=(size * instrument), portfolio=portfolio) return order
def test_release(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) order.execute() wallet_usd = portfolio.get_wallet(exchange.id, USD) assert wallet_usd.balance == 4800 * USD assert wallet_usd.locked_balance == 5200 * USD assert order.path_id in wallet_usd.locked.keys() order.release() assert wallet_usd.balance == 10000 * USD assert wallet_usd.locked_balance == 0 * USD assert order.path_id not in wallet_usd.locked.keys()
def test_to_json(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) d = { "id": str(order.id), "step": int(order.step), "exchange_pair": str(order.exchange_pair), "status": str(order.status), "type": str(order.type), "side": str(order.side), "base_symbol": str(order.pair.base.symbol), "quote_symbol": str(order.pair.quote.symbol), "quantity": str(order.quantity), "size": float(order.size), "remaining": str(order.remaining), "price": float(order.price), "criteria": str(order.criteria), "path_id": str(order.path_id), "created_at": str(order.created_at) } assert order.to_json() == d
def test_on_cancel(mock_exchange_class, cancel_listener): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "bitfinex" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) order.attach(cancel_listener) assert not cancel_listener.listened order.cancel() assert cancel_listener.listened
def test_complete_basic_order(mock_order_listener_class, mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) listener = mock_order_listener_class.return_value listener.on_complete = mock.Mock(return_value=None) order.attach(listener) order.execute() trade = mock_trade_class.return_value trade.size = Decimal(5197.00) trade.quantity = 5197.00 * USD trade.commission = 3.00 * USD order.fill(trade) assert order.status == OrderStatus.PARTIALLY_FILLED next_order = order.complete() assert order.status == OrderStatus.FILLED listener.on_complete.assert_called_once_with(order) assert not next_order
def hidden_limit_order(side: "TradeSide", exchange_pair: "ExchangePair", limit_price: float, size: float, portfolio: "Portfolio", start: int = None, end: int = None): """Creates a hidden limit order. Parameters ---------- side : `TradeSide` The side of the order. exchange_pair : `ExchangePair` The exchange pair to perform the order for. limit_price : float The limit price of the order. size : float The size of the order. portfolio : `Portfolio` The portfolio being used in the order. start : int, optional The start time of the order. end : int, optional The end time of the order. Returns ------- `Order` A hidden limit order. """ side = TradeSide[side] instrument = side.instrument(exchange_pair.pair) order = Order(step=portfolio.clock.step, side=side, trade_type=TradeType.MARKET, exchange_pair=exchange_pair, price=limit_price, quantity=(size * instrument), start=start, end=end, portfolio=portfolio, criteria=Limit(limit_price=limit_price)) return order
def test_on_fill(mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options.max_trade_size = 1e6 exchange.id = "fake_exchange_id" exchange.name = "bitfinex" exchange.quote_price = lambda pair: Decimal(7000.00) broker = Broker() broker.exchanges = [exchange] wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=7000.00) order.attach(broker) order.execute() broker.executed[order.id] = order trade = mock_trade_class.return_value trade.quantity = 5197.00 * USD trade.commission = 3.00 * USD trade.order_id = order.id assert order.status == OrderStatus.OPEN order.fill(trade) assert order.status == OrderStatus.FILLED assert order.remaining == 0 assert trade in broker.trades[order.id]
def test_execute(mock_order_listener_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) listener = mock_order_listener_class.return_value listener.on_execute = mock.Mock(return_value=None) order.attach(listener) assert order.status == OrderStatus.PENDING order.execute() assert order.status == OrderStatus.OPEN wallet_usd = portfolio.get_wallet(exchange.id, USD) wallet_btc = portfolio.get_wallet(exchange.id, BTC) assert wallet_usd.balance == 4800 * USD assert wallet_usd.locked_balance == 5200 * USD assert order.path_id in wallet_usd.locked.keys() assert wallet_btc.balance == 0 * BTC listener.on_execute.assert_called_once_with(order)
def assert_execute_order(current_price, base_balance, quote_balance, order_side, order_quantity, order_price, ): mock_clock = mock.Mock() clock = mock_clock.return_value clock.step = 3 base = base_balance.instrument quote = quote_balance.instrument current_price = Decimal(current_price).quantize(Decimal(10) ** -base.precision) order_price = Decimal(order_price).quantize(Decimal(10) ** -base.precision) options = ExchangeOptions() mock_exchange = mock.Mock() exchange = mock_exchange.return_value exchange.name = "coinbase" exchange.options = options exchange.quote_price = lambda pair: current_price base_wallet = Wallet(exchange, base_balance) quote_wallet = Wallet(exchange, quote_balance) portfolio = Portfolio(USD, [ base_wallet, quote_wallet ]) order = Order( step=1, side=order_side, trade_type=TradeType.MARKET, exchange_pair=ExchangePair(exchange, base/quote), quantity=order_quantity, portfolio=portfolio, price=order_price, path_id="fake_id" ) order.status = OrderStatus.OPEN if order_side == TradeSide.BUY: trade = execute_buy_order( order, base_wallet, quote_wallet, current_price=current_price, options=options, clock=clock, ) base_balance = base_wallet.locked['fake_id'].size quote_balance = quote_wallet.locked['fake_id'].size expected_base_balance = order_quantity.size - (trade.size + trade.commission.size) expected_quote_balance = trade.size / current_price expected_base_balance = expected_base_balance.quantize(Decimal(10) ** -base.precision) expected_quote_balance = expected_quote_balance.quantize(Decimal(10) ** -quote.precision) assert base_balance == expected_base_balance assert quote_balance == expected_quote_balance else: trade = execute_sell_order( order, base_wallet, quote_wallet, current_price=current_price, options=options, clock=clock, ) base_balance = base_wallet.locked['fake_id'].size quote_balance = quote_wallet.locked['fake_id'].size expected_base_balance = trade.size * current_price expected_base_balance = expected_base_balance.quantize(Decimal(10)**-base.precision) assert base_balance == expected_base_balance assert quote_balance == 0
def test_on_fill_with_complex_order(mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options.max_trade_size = 1e6 exchange.id = "fake_exchange_id" exchange.name = "bitfinex" exchange.quote_price = lambda pair: Decimal(7000.00) broker = Broker() broker.exchanges = [exchange] wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) side = TradeSide.BUY order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) risk_criteria = Stop("down", 0.03) ^ Stop("up", 0.02) risk_management = OrderSpec( side=TradeSide.SELL if side == TradeSide.BUY else TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=ExchangePair(exchange, USD / BTC), criteria=risk_criteria) order.add_order_spec(risk_management) order.attach(broker) order.execute() broker.executed[order.id] = order # Execute fake trade price = Decimal(7000.00) scale = order.price / price commission = 3.00 * USD base_size = scale * order.size - commission.size trade = mock_trade_class.return_value trade.order_id = order.id trade.size = base_size trade.quantity = base_size * USD trade.price = price trade.commission = commission base_wallet = portfolio.get_wallet(exchange.id, USD) quote_wallet = portfolio.get_wallet(exchange.id, BTC) base_size = trade.size + trade.commission.size quote_size = (order.price / trade.price) * (trade.size / trade.price) base_wallet.withdraw(quantity=Quantity(USD, size=base_size, path_id=order.path_id), reason="test") quote_wallet.deposit(quantity=Quantity(BTC, size=quote_size, path_id=order.path_id), reason="test") assert trade.order_id in broker.executed.keys() assert trade not in broker.trades assert broker.unexecuted == [] order.fill(trade) assert order.remaining == 0 assert trade in broker.trades[order.id] assert broker.unexecuted != []
def proportion_order(portfolio: 'Portfolio', source: 'Wallet', target: 'Wallet', proportion: float) -> 'Order': """Creates an order that sends a proportion of funds from one wallet to another. Parameters ---------- portfolio : `Portfolio` The portfolio that contains both wallets. source : `Wallet` The source wallet for the funds. target : `Wallet` The target wallet for the funds. proportion : float The proportion of funds to send. """ assert 0.0 < proportion <= 1.0 exchange = source.exchange base_params = { 'step': portfolio.clock.step, 'portfolio': portfolio, 'trade_type': TradeType.MARKET, 'start': portfolio.clock.step, 'end': portfolio.clock.step + 1 } pair = None is_source_base = (source.instrument == portfolio.base_instrument) is_target_base = (target.instrument == portfolio.base_instrument) if is_source_base or is_target_base: if is_source_base: pair = source.instrument / target.instrument else: pair = target.instrument / source.instrument exchange_pair = ExchangePair(exchange, pair) balance = source.balance.as_float() size = min(balance * proportion, balance) quantity = (size * source.instrument).quantize() params = { **base_params, 'side': TradeSide.BUY if is_source_base else TradeSide.SELL, 'exchange_pair': exchange_pair, 'price': exchange_pair.price, 'quantity': quantity } return Order(**params) pair = portfolio.base_instrument / source.instrument exchange_pair = ExchangePair(exchange, pair) balance = source.balance.as_float() size = min(balance * proportion, balance) quantity = (size * source.instrument).quantize() params = { **base_params, 'side': TradeSide.SELL, 'exchange_pair': exchange_pair, 'price': exchange_pair.price, 'quantity': quantity } order = Order(**params) pair = portfolio.base_instrument / target.instrument order.add_order_spec( OrderSpec(side=TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=ExchangePair(exchange, pair), criteria=None)) return order
def risk_managed_order(side: "TradeSide", trade_type: "TradeType", exchange_pair: "ExchangePair", price: float, quantity: "Quantity", down_percent: float, up_percent: float, portfolio: "Portfolio", start: int = None, end: int = None): """Create a stop order that manages for percentages above and below the entry price of the order. Parameters ---------- side : `TradeSide` The side of the order. trade_type : `TradeType` The type of trade to make when going in. exchange_pair : `ExchangePair` The exchange pair to perform the order for. price : float The current price. down_percent: float The percentage the price is allowed to drop before exiting. up_percent : float The percentage the price is allowed to rise before exiting. quantity : `Quantity` The quantity of the order. portfolio : `Portfolio` The portfolio being used in the order. start : int, optional The start time of the order. end : int, optional The end time of the order. Returns ------- `Order` A stop order controlling for the percentages above and below the entry price. """ side = TradeSide(side) instrument = side.instrument(exchange_pair.pair) order = Order(step=portfolio.clock.step, side=side, trade_type=TradeType(trade_type), exchange_pair=exchange_pair, price=price, start=start, end=end, quantity=quantity, portfolio=portfolio) risk_criteria = Stop("down", down_percent) ^ Stop("up", up_percent) risk_management = OrderSpec( side=TradeSide.SELL if side == TradeSide.BUY else TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=exchange_pair, criteria=risk_criteria) order.add_order_spec(risk_management) return order
def test_is_executable_on(mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 # Market order wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) exchange.quote_price = mock.Mock(return_value=Decimal(6800.00)) assert order.is_executable exchange.quote_price = mock.Mock(return_value=Decimal(7200.00)) assert order.is_executable # Limit order wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.LIMIT, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) exchange.quote_price = mock.Mock(return_value=Decimal(6800.00)) assert order.is_executable exchange.quote_price = mock.Mock(return_value=Decimal(7200.00)) assert order.is_executable # Stop Order wallets = [Wallet(exchange, 0 * USD), Wallet(exchange, 2 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.SELL, trade_type=TradeType.LIMIT, quantity=1 * BTC, portfolio=portfolio, price=Decimal(7000.00), criteria=Stop("down", 0.03)) exchange.quote_price = mock.Mock(return_value=Decimal(1 - 0.031) * order.price) assert order.is_executable exchange.quote_price = mock.Mock(return_value=Decimal(1 - 0.02) * order.price) assert not order.is_executable
def test_complete_complex_order(mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) side = TradeSide.BUY order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) risk_criteria = Stop("down", 0.03) ^ Stop("up", 0.02) risk_management = OrderSpec( side=TradeSide.SELL if side == TradeSide.BUY else TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=ExchangePair(exchange, USD / BTC), criteria=risk_criteria) order.add_order_spec(risk_management) order.execute() # Execute fake trade price = Decimal(7010.00) scale = order.price / price commission = 3.00 * USD base_size = scale * order.size - commission.size trade = mock_trade_class.return_value trade.size = Decimal(base_size) trade.quantity = base_size * USD trade.price = price trade.commission = commission base_wallet = portfolio.get_wallet(exchange.id, USD) quote_wallet = portfolio.get_wallet(exchange.id, BTC) base_size = trade.size + trade.commission.size quote_size = (order.price / trade.price) * (trade.size / trade.price) base_wallet.withdraw(quantity=Quantity(USD, size=base_size, path_id=order.path_id), reason="test") quote_wallet.deposit(quantity=Quantity(BTC, size=quote_size, path_id=order.path_id), reason="test") # Fill fake trade order.fill(trade) assert order.path_id in portfolio.get_wallet(exchange.id, USD).locked assert order.status == OrderStatus.PARTIALLY_FILLED next_order = order.complete() assert order.status == OrderStatus.FILLED assert next_order assert next_order.path_id == order.path_id assert next_order.size assert next_order.status == OrderStatus.PENDING assert next_order.side == TradeSide.SELL assert next_order.exchange_pair == ExchangePair(exchange, USD / BTC)
def get_orders(self, action: int, portfolio: 'Portfolio') -> 'List[Order]': # HOLD Action if action == 0: return [] #(ep, (criteria, proportion, duration, side)) = self.actions[action] #ep = self.actions[action][0] #(criteria, proportion, duration, side) = self.actions[action][1] ep, side = self.actions[action] # Empty Orders List orders = [] # check different action if (action != self.action): # proportion of used balance proportion = 1 duration = None criteria = None # get base/quote wallets quote_wallet = portfolio.get_wallet(ep.exchange.id, instrument=ep.pair.quote) base_wallet = portfolio.get_wallet(ep.exchange.id, instrument=ep.pair.base) # get current price price = ep.price # create orders to close all already open-position positions = quote_wallet.get_positions().values( ) # avoid RuntimeError: dictionary changed size during iteration, when remove position from dict. for position in positions: if position.is_open: #close positions based on last opened orders #quantity = Quantity(instrument=ep.pair.quote, size=position.quantity.size) # adjut worth quantity - if sell-position already open quantity = Quantity(instrument=ep.pair.quote, size=position.get_worth_quantity()) position.is_locked = True order = Order(path_id=position.id, step=self.clock.step, side=TradeSide.CLOSE, trade_type=self._trade_type, exchange_pair=ep, price=ep.price, quantity=quantity, criteria=criteria, end=self.clock.step + duration if duration else None, portfolio=portfolio) # Transfer Funds from Quote Wallet to Base Wallet transfer = Wallet.transfer( source=quote_wallet, target=base_wallet, quantity=order.quantity, commission=Quantity(instrument=ep.pair.quote, size=0), exchange_pair=order.exchange_pair, reason="CLOSE") self.broker.submit(order) self.broker.update() if side != TradeSide.CLOSE: # return money to base-wallets instrument = side.instrument(ep.pair) balance = base_wallet.balance.as_float() size = (balance * proportion) size = min(balance, size) quantity = (size * instrument).quantize() value = size * float(price) if size < 10 ** -instrument.precision \ or value < self.min_order_pct*portfolio.net_worth: return [] order = Order(step=self.clock.step, side=side, trade_type=self._trade_type, exchange_pair=ep, price=ep.price, quantity=quantity, criteria=criteria, end=self.clock.step + duration if duration else None, portfolio=portfolio) orders.append(order) if self._order_listener is not None: order.attach(self._order_listener) # TODO: Check a way to check: check only when order is completed self.action = action return orders
def test_cancel(mock_order_listener_class, mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5200.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) listener = mock_order_listener_class.return_value listener.on_cancel = mock.Mock(return_value=None) order.attach(listener) order.execute() # Execute fake trade price = Decimal(7010.00) scale = order.price / price commission = 3.00 * USD trade = mock_trade_class.return_value trade.size = Decimal(scale * order.size - commission.size) trade.quantity = trade.size * USD trade.price = price trade.commission = commission base_wallet = portfolio.get_wallet(exchange.id, USD) quote_wallet = portfolio.get_wallet(exchange.id, BTC) base_size = trade.size + commission.size quote_size = (order.price / trade.price) * (trade.size / trade.price) base_wallet.withdraw(quantity=Quantity(USD, size=base_size, path_id=order.path_id), reason="test") quote_wallet.deposit(quantity=Quantity(BTC, size=quote_size, path_id=order.path_id), reason="test") order.fill(trade) assert order.status == OrderStatus.PARTIALLY_FILLED assert base_wallet.balance == 4800.00 * USD assert float(round(base_wallet.locked[order.path_id].size, 2)) == 7.42 assert quote_wallet.balance == 0 * BTC assert float(round(quote_wallet.locked[order.path_id].size, 8)) == 0.73925519 order.cancel() listener.on_cancel.assert_called_once_with(order) assert float(round(base_wallet.balance.size, 2)) == 4807.42 assert order.path_id not in base_wallet.locked assert float(round(quote_wallet.balance.size, 8)) == 0.73925519 assert order.path_id not in quote_wallet.locked