class SupervisorOrdersTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_add_order(self): new_order = Order(order_type='Limit', qty=228, price=1000, side='Buy') self.supervisor.add_order(new_order) self.assertIn(new_order, self.supervisor.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)
class SyncPositionTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_enter_long_position(self): self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor.correct_position_size = Mock() self.supervisor.position_size = 100 self.supervisor.sync_position() self.supervisor.correct_position_size.assert_called_once_with(qty=100) def test_correct_position_size(self): self.supervisor.position_size = 20 self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor.correct_position_size(20) self.assertEqual(20, self.supervisor.position_size) self.exchange_mock.place_market_order.assert_called_once_with(qty=20)
class SupervisorCycleTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_run_cycle(self): self.supervisor.run_cycle() self.assertTrue(self.supervisor._run_thread.is_set()) self.assertFalse(self.supervisor._exit_sync_thread.is_set()) self.assertFalse(self.supervisor._stopped.is_set()) self.assertTrue(self.supervisor.sync_thread.is_alive()) def test_exit_cycle(self): self.supervisor.run_cycle() self.assertTrue(self.supervisor.sync_thread.is_alive()) self.supervisor.exit_cycle() self.assertFalse(self.supervisor.sync_thread.is_alive()) def test_stop_cycle(self): self.supervisor.run_cycle() self.supervisor.stop_cycle() self.assertTrue(self.supervisor._stopped.is_set()) self.assertFalse(self.supervisor._run_thread.is_set()) def test_continue_cycle(self): self.supervisor.run_cycle() self.supervisor.stop_cycle() self.supervisor.run_cycle() # assert that events conditions are the same as in just-created supervisor self.assertTrue(self.supervisor._run_thread.is_set()) self.assertFalse(self.supervisor._exit_sync_thread.is_set()) self.assertFalse(self.supervisor._stopped.is_set()) # cycle should also be alive of course :) self.assertTrue(self.supervisor.sync_thread.is_alive()) def test_exit_from_exited_cycle(self): self.supervisor.run_cycle() self.supervisor.exit_cycle() self.assertFalse(self.supervisor.sync_thread.is_alive()) self.supervisor.exit_cycle() # must not raise anything self.assertFalse(self.supervisor.sync_thread.is_alive()) def test_reset(self): self.supervisor.run_cycle() self.supervisor.reset() self.assertEqual(0, self.supervisor.position_size) self.assertListEqual([], self.supervisor._orders)
class SyncOrdersTests(unittest.TestCase): """All methods that associated with placing and cancelling orders in cycle.""" def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() 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 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_several_needless_orders(self): order1 = Order() order2 = Order() order3 = Order() self.exchange_mock.get_open_orders_ws.return_value = [ order1, order2, order3 ] expected_orders = [order1, order2, order3] 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 test_cancel_duplicates(self): order1 = Order(order_type='Limit', qty=228, price=1000, side='Buy') order2 = Order(order_type='Limit', qty=229, price=1001, side='Buy') self.exchange_mock.get_open_orders_ws.return_value = [ order1, order2, order2 ] self.supervisor.add_order(order1) self.supervisor.add_order(order2) expected_orders = [order2] 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 test_cancel_two_same_needed_orders(self): order1 = Order(order_type='Limit', qty=228, price=1000, side='Buy') order2 = Order(order_type='Limit', qty=228, price=1000, side='Buy') order3 = Order(order_type='Limit', qty=229, price=1001, side='Buy') self.exchange_mock.get_open_orders_ws.return_value = [order1, order2] self.supervisor.add_order(order1) self.supervisor.add_order(order2) self.supervisor.add_order(order3) self.supervisor.cancel_needless_orders() self.exchange_mock.bulk_cancel_orders.assert_not_called() 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) def test_check_several_unplaced_orders(self): order1 = Order(order_type='Limit', qty=228, price=1001, side='Buy') order2 = Order(order_type='Limit', qty=229, price=1002, side='Buy') order3 = Order(order_type='Limit', qty=2210, price=1003, side='Buy') self.supervisor.add_order(order1) self.supervisor.add_order(order2) self.supervisor.add_order(order3) self.supervisor.check_needed_orders() self.exchange_mock.bulk_place_orders.assert_called_once_with( [order1, order2, order3]) def test_check_rejected_order(self): def order_status_mock(_order): if _order == order: return 'Rejected' on_reject_mock = Mock() self.exchange_mock.get_order_status_ws.side_effect = order_status_mock order = Order(order_type='Limit', qty=228, price=1000, side='Buy') order.order_id = '1234' order._on_reject = on_reject_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_reject_mock.assert_called_once() def test_check_filled_order(self): def order_status_mock(_order): if _order == order: return 'Filled' on_filled_mock = Mock() self.exchange_mock.get_order_status_ws.side_effect = order_status_mock order = Order(order_type='Limit', qty=228, price=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_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_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)
class SupervisorEntryTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_market_entry(self): self.supervisor.enter_by_market_order(228) self.exchange_mock.place_market_order.assert_called_once_with(qty=228) def test_fb_entry_last_price(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.enter_fb_method(qty=228, price_type='last', max_retry=5, timeout=3) 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_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_fb_entry_first_orderbook_price(self): self.exchange_mock.get_first_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='first_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_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_fb_entry_with_deviation(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.enter_fb_method(qty=228, price_type='deviant', max_retry=5, timeout=1, deviation=-10) order = Order(order_type='Limit', qty=228, price=900, side='Buy', passive=True) self.exchange_mock.place_order.assert_called_once_with(order)
# 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) supervisor.add_order(tp2) supervisor.add_trailing_order(trailing_stop, offset=10) # order will trail on 10% distance from current price input('Enter to enter position') supervisor.stop_cycle() supervisor.enter_by_market_order(10) supervisor.run_cycle() input('Enter to exit cycle') supervisor.exit_cycle()
class TrailingStopTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) self.exchange_mock.conn.base_url = 'https://testnet.bitmex.com/api/v1' self.exchange_mock.conn.get_tick_size.return_value = 0.5 def tearDown(self) -> None: self.supervisor.exit_cycle() def test_buy_order_will_follow_the_price(self): order = Order(order_type='Stop', side='Buy', qty=228, stop_px=10000) 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, offset=10) self.supervisor.run_cycle() self.exchange_mock.get_last_price_ws.return_value = 8000 order.tracker.min_price = 8000 self.assertEqual(8800, order.stop_px) self.exchange_mock.get_last_price_ws.return_value = 7000 order.tracker.min_price = 7000 self.assertEqual(7700, order.stop_px) 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 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)
class SupervisorEntryTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_market_position_enter_while_running_cycle(self): self.supervisor.run_cycle() self.supervisor.stop_cycle() self.supervisor.enter_by_market_order(qty=20) self.exchange_mock.get_position_size_ws.return_value = 20 self.supervisor.run_cycle() sleep(1) # assert that Supervisor change position only once self.exchange_mock.place_market_order.assert_called_once_with(qty=20) def test_fb_last_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='last', timeout=5, max_retry=3) self.exchange_mock.get_position_size_ws.return_value = 20 self.supervisor.run_cycle() sleep(1) order = Order(order_type='Limit', qty=20, price=1000, 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_fb_first_ob_position_enter_while_running_cycle(self): self.exchange_mock.get_first_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.run_cycle() self.supervisor.stop_cycle() self.supervisor.enter_fb_method(qty=20, price_type='first_ob', timeout=5, max_retry=3) self.exchange_mock.get_position_size_ws.return_value = 20 self.supervisor.run_cycle() sleep(1) order = Order(order_type='Limit', qty=20, price=1000, 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_fb_third_ob_position_enter_while_running_cycle(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.run_cycle() self.supervisor.stop_cycle() self.supervisor.enter_fb_method(qty=20, price_type='third_ob', timeout=5, max_retry=3) self.exchange_mock.get_position_size_ws.return_value = 20 self.supervisor.run_cycle() sleep(1) order = Order(order_type='Limit', qty=20, price=1000, 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_fb_positive_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=1150, 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_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)
class OrdersCallbacksTests(unittest.TestCase): def setUp(self) -> None: self.exchange_mock = Mock() self.exchange_mock.get_open_orders_ws.return_value = [] self.exchange_mock.get_position_size_ws.return_value = 0 self.supervisor = Supervisor(interface=self.exchange_mock) def tearDown(self) -> None: self.supervisor.exit_cycle() def test_filled_callback_will_be_called(self): order = Order(order_type='Limit', side='Sell', qty=228, price=1000) order.order_id = 123456 callback = Mock() order._on_fill = callback self.supervisor.add_order(order) self.exchange_mock.get_order_status_ws.return_value = 'Filled' self.supervisor.run_cycle() sleep(1) callback.assert_called_once() self.assertListEqual([], self.supervisor._orders) 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 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)