def test_rejected_callback_will_be_called(self): order = Order(order_type='Limit', side='Sell', qty=228, price=1000) order.order_id = 123456 callback = Mock() order._on_reject = callback self.supervisor.add_order(order) self.exchange_mock.get_order_status_ws.return_value = 'Rejected' self.supervisor.run_cycle() sleep(1) callback.assert_called_once() self.assertListEqual([], self.supervisor._orders)
def enter_fb_method(self, qty: int, price_type: str, timeout: int, max_retry: int, deviation: int = None) -> None: """ Fb method is placing n orders after timeout seconds, then entry market. :param qty: entry position size :param timeout: timeout before replacing the order :param price_type: may be last, first_ob, third_ob and deviant :param deviation: deviation from last price in percents :param max_retry: max order placing count """ if price_type == 'first_ob': init_price = self.exchange.get_first_orderbook_price_ws(bid=qty > 0) elif price_type == 'third_ob': init_price = self.exchange.get_third_orderbook_price_ws(bid=qty > 0) elif price_type == 'deviant': if qty > 0: init_price = self.exchange.get_last_price_ws() * (100 + deviation) / 100 else: init_price = self.exchange.get_last_price_ws() * (100 - deviation) / 100 else: init_price = self.exchange.get_last_price_ws() init_price = to_nearest(init_price, self.exchange.conn.get_tick_size()) entry_order = Order(order_type='Limit', qty=qty, side='Buy' if qty > 0 else 'Sell', price=init_price, passive=False) for _ in range(max_retry): self.exchange.place_order(entry_order) for _ in range(timeout): if self.exchange.get_order_status_ws(entry_order) == 'Filled': return sleep(1) self.exchange.cancel_order(entry_order) self.enter_by_market_order(qty=qty)
def test_fb_negative_deviant_position_enter_while_running_cycle(self): self.exchange_mock.get_last_price_ws.return_value = 1000 self.exchange_mock.get_order_status_ws.return_value = 'Filled' self.exchange_mock.conn.get_tick_size.return_value = 0.5 self.supervisor.run_cycle() self.supervisor.stop_cycle() self.supervisor.enter_fb_method(qty=20, price_type='deviant', timeout=5, max_retry=3, deviation=-15) self.exchange_mock.get_position_size_ws.return_value = 20 self.supervisor.run_cycle() sleep(1) order = Order(order_type='Limit', qty=20, price=850, side='Buy', passive=True) # assert that Supervisor change position only once with limit order self.exchange_mock.place_order.assert_called_once_with(order)
def test_move_order(self): order1 = Order(order_type='Limit', price=1000, qty=228, side='Sell') order1.order_id = 1234 with responses.RequestsMock() as rsps: rsps.add(responses.PUT, settings.BASE_URL + '/order', json={}) expected_order = { 'orderID': 1234, 'orderQty': 228, 'price': 1001.0, } self.exchange.move_order(order1, to=1001) self.assertEqual(1, len(rsps.calls)) self.assertEqual(json.dumps(expected_order), rsps.calls[0].request.body)
def test_stop_tracking_after_fill_order(self): order = Order(order_type='Stop', side='Sell', qty=228, stop_px=1000) self.exchange_mock.get_open_orders_ws.return_value = [order] self.exchange_mock.get_last_price_ws.return_value = 9000 self.supervisor.add_trailing_order(order, 10) self.supervisor.run_cycle() order.order_id = 1234 self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_order_status_ws.return_value = 'Filled' sleep(0.5) self.assertTrue(order.tracker.exited)
def test_export_order_to_api_dict_no_empty(self): """Do not include empty parameters to result dict.""" order = Order(order_type='Limit', qty=228, side='Buy', price=Decimal(1000)) expected_order_dict = { 'symbol': 'XBTUSD', 'ordType': 'Limit', 'orderQty': 228, 'side': 'Buy', 'price': 1000.0, } api_order_dict = order.as_dict(include_empty=False) self.assertEqual(expected_order_dict, api_order_dict)
def add_order(self, order: Order) -> None: if order.is_valid(): self._orders.append(order) self.logger.info(f'New order: {order.order_type} {order.side} {order.qty} by ' f'{order.price or order.stop_px}') else: raise ValueError('Order is not valid.')
def test_move_changed_order(self): """Test that Supervisor will amend order, not cancel it.""" order1 = Order(order_type='Limit', qty=228, price=1000, side='Buy') order1_copy = Order(order_type='Limit', qty=228, price=1000, side='Buy') self.supervisor.add_order(order1) self.exchange_mock.get_open_orders_ws.return_value = [order1_copy] order1.move(to=1001) self.supervisor.cancel_needless_orders() self.exchange_mock.bulk_cancel_orders.assert_not_called() self.exchange_mock.move_order.assert_called_once_with(order=order1)
def test_cancel_needless_order(self): order1 = Order() self.exchange_mock.get_open_orders_ws.return_value = [order1] expected_orders = [order1] self.supervisor.cancel_needless_orders() # assert that Supervisor try to cancel needless order self.exchange_mock.bulk_cancel_orders.assert_called_once_with(expected_orders)
def add_trailing_order(self, order: Order, offset: int) -> None: """ :param order: Order instance :param offset: value in percents, distance from extremum """ if order.is_valid(): order.is_trailing = True tick_size = self.exchange.conn.get_tick_size() test = 'testnet' in self.exchange.conn.base_url order.tracker = TrailingShell(order=order, offset=offset, tick_size=tick_size, test=test) order.tracker.start_trailing( initial_price=self.exchange.get_last_price_ws()) self.orders.append(order)
def setUp(self) -> None: self.order = Order(order_type='Stop', qty=228, stop_px=900, side='Sell') self.trailing_order = TrailingShell(order=self.order, offset=10, tick_size=0.5, test=True, init_ws=False)
def test_same_orders_equality(self): order1 = Order(order_type='Limit', qty=228, side='Buy', price=Decimal(1000), stop_px=None, hidden=True, close=False, reduce_only=False, passive=True) order2 = Order(order_type='Limit', qty=228, side='Buy', price=Decimal(1000), stop_px=None, hidden=True, close=False, reduce_only=False, passive=True) self.assertEqual(order1, order2)
def test_check_filled_stop_order(self): def order_status_mock(_order): if _order == order: return 'Triggered' on_filled_mock = Mock() self.exchange_mock.get_order_status_ws.side_effect = order_status_mock order = Order(order_type='Stop', qty=228, stop_px=1000, side='Buy') order.order_id = '1234' order._on_fill = on_filled_mock self.supervisor.add_order(order) self.supervisor.check_needed_orders() # assert that we didn`t place this order self.exchange_mock.place_order.not_called(order) # assert that Supervisor forget this order self.assertNotIn(order, self.supervisor.orders) # assert that Supervisor call matching callback on_filled_mock.assert_called_once()
def test_fb_entry_third_orderbook_price(self): self.exchange_mock.get_third_orderbook_price_ws.return_value = 1000 self.exchange_mock.get_order_status_ws.return_value = 'Filled' self.exchange_mock.conn.get_tick_size.return_value = 0.5 self.supervisor.enter_fb_method( qty=228, price_type='third_ob', max_retry=5, timeout=1 ) order = Order(order_type='Limit', qty=228, price=1000, side='Buy', passive=True) self.exchange_mock.place_order.assert_called_once_with(order)
def test_orders_are_not_equal(self): order1 = Order(order_type='Limit', qty=228, side='Buy', price=Decimal(1000), stop_px=None, hidden=True, close=False, reduce_only=False, passive=True) order2 = Order( order_type='Limit', qty=229, # Other quantity side='Buy', price=Decimal(1000), stop_px=None, hidden=True, close=False, reduce_only=False, passive=True) self.assertNotEqual(order1, order2)
def test_import_stop_order_from_dict(self): order_dict = { 'symbol': 'XBTUSD', 'clOrdID': None, 'orderID': None, 'ordType': 'Stop', 'orderQty': 228, 'side': 'Buy', 'price': None, 'stopPx': 1000.0, 'displayQty': 0, 'execInst': 'Close,ReduceOnly,ParticipateDoNotInitiate' } expected_order = Order(order_type='Stop', qty=228, side='Buy', stop_px=Decimal(1000), hidden=True, close=True, reduce_only=True, passive=True) self.assertTrue(expected_order == Order.from_dict(order_dict))
def test_validation_error_while_placing_order(self): validation_error = requests.HTTPError() validation_error.response = Mock() validation_error.response.text = 'Order price is above the liquidation price of current' self.exchange_mock.place_order.side_effect = validation_error order = Order(order_type='Limit', qty=228, price=1000, side='Buy') self.supervisor.add_order(order) self.supervisor.check_needed_orders() # assert that method has been called self.exchange_mock.place_order.assert_called_once_with(order) # assert that we catch the exception and forget the order self.assertNotIn(order, self.supervisor.orders)
def test_export_order_to_api_dict_include_empty(self): order = Order(order_type='Limit', qty=228, side='Buy', price=Decimal(1000), stop_px=None, hidden=True, close=True, reduce_only=True, passive=True) expected_order_dict = { 'symbol': 'XBTUSD', 'clOrdID': None, 'orderID': None, 'ordType': 'Limit', 'orderQty': 228, 'side': 'Buy', 'price': 1000.0, 'stopPx': None, 'displayQty': 0, 'execInst': 'Close,ReduceOnly,ParticipateDoNotInitiate' } api_order_dict = order.as_dict(include_empty=True) self.assertEqual(expected_order_dict, api_order_dict)
def test_fb_entry_last_price_timedout(self): self.exchange_mock.get_last_price_ws.return_value = 1000 self.exchange_mock.get_order_status_ws.return_value = 'New' self.exchange_mock.conn.get_tick_size.return_value = 0.5 self.supervisor.enter_fb_method( qty=228, price_type='last', max_retry=3, timeout=1 ) order = Order(order_type='Limit', qty=228, price=1000, side='Buy', passive=True) expected_calls = [call(order)] * 3 self.exchange_mock.place_order.assert_has_calls(expected_calls) self.exchange_mock.place_market_order.assert_called_once_with(qty=228)
def test_sell_order_will_follow_the_price(self): order = Order(order_type='Stop', side='Sell', qty=228, stop_px=1000) self.exchange_mock.get_open_orders_ws.return_value = [order] self.exchange_mock.get_last_price_ws.return_value = 1000 self.supervisor.add_trailing_order(order, offset=10) self.supervisor.run_cycle() self.exchange_mock.get_last_price_ws.return_value = 7000 order.tracker.max_price = 7000 self.assertEqual(6300, order.stop_px) self.exchange_mock.get_last_price_ws.return_value = 8000 order.tracker.max_price = 8000 self.assertEqual(7200, order.stop_px)
def get_open_orders_ws(self): return [Order.from_dict(o) for o in self.conn.open_orders()]
def test_make_close_stop_order_with_no_qty(self): order = Order(order_type='Stop', side='Sell', stop_px=1000, close=True) self.assertTrue(order.is_valid())
def test_check_unplaced_order(self): order = Order(order_type='Limit', qty=228, price=1000, side='Buy') self.supervisor.add_order(order) self.supervisor.check_needed_orders() self.exchange_mock.place_order.assert_called_once_with(order)
TEST_API_KEY = 'your-api-key' TEST_API_SECRET = 'your-api-secret' if __name__ == '__main__': # Exchange is an interface-like class to call api methods exchange = Exchange(api_key=TEST_API_KEY, api_secret=TEST_API_SECRET, test=True, symbol='XBTUSD') supervisor = Supervisor(interface=exchange) # you can disable managing position or orders by set to False needed properties # supervisor.manage_position = False # supervisor.manage_orders = False # create Order objects stop_loss = Order(order_type='Stop', stop_px=2000, qty=10, side='Sell') tp1 = Order(order_type='Limit', price=15000, qty=6, side='Sell', passive=True) tp2 = Order(order_type='Limit', price=20000, qty=4, side='Sell', hidden=True) trailing_stop = Order(order_type='Stop', stop_px=3000, qty=10, side='Sell') # attach some callbacks to stop-loss, note that events starts with "_" # DO NOT USE stop_cycle() method in callbacks!!! It causes deadlock stop_loss._on_reject = lambda: print('Rejected') stop_loss._on_fill = lambda: print('We lost position(') input('Enter to run cycle') supervisor.run_cycle() input('Enter to add orders to needed') supervisor.add_order(stop_loss) supervisor.add_order(tp1)
def test_make_stop_order_with_no_qty(self): order = Order(order_type='Stop', side='Sell', stop_px=1000) self.assertFalse(order.is_valid())
def get_order_by_clordid_ws(self, clordid): orders = self.conn.get_orders() orders = list(filter(lambda x: x['clOrdID'] == clordid, orders)) if len(orders) == 1: return Order.from_dict(orders[0]) return None
def get_filled_orders_ws(self): return [Order.from_dict(o) for o in self.conn.filled_orders()]
def test_add_empty_order(self): with self.assertRaises(ValueError): self.supervisor.add_order(Order())
def test_remove_order(self): new_order = Order(order_type='Limit', qty=228, price=1000, side='Buy') self.supervisor.add_order(new_order) self.supervisor.remove_order(new_order) self.assertNotIn(new_order, self.supervisor.orders)
def test_several_filled_orders_at_the_same_time(self): order_1 = Order(order_type='Limit', side='Sell', qty=228, price=1000) order_1.order_id = 123456 order_2 = Order(order_type='Limit', side='Sell', qty=229, price=1001) order_2.order_id = 123457 order_3 = Order(order_type='Limit', side='Sell', qty=2210, price=1002) order_3.order_id = 123458 order_4 = Order(order_type='Limit', side='Sell', qty=2211, price=1003) order_4.order_id = 123459 callback_1 = Mock() callback_2 = Mock() callback_3 = Mock() callback_4 = Mock() order_1._on_fill = callback_1 order_2._on_fill = callback_2 order_3._on_fill = callback_3 order_4._on_fill = callback_4 self.supervisor.add_order(order_1) self.supervisor.add_order(order_2) self.supervisor.add_order(order_3) self.supervisor.add_order(order_4) self.exchange_mock.get_order_status_ws.return_value = 'Filled' self.supervisor.run_cycle() sleep(1) callback_1.assert_called_once() callback_2.assert_called_once() callback_3.assert_called_once() callback_4.assert_called_once() self.assertListEqual([], self.supervisor._orders)