return type('Args', (object,), { "debug": debug, "quiet": quiet })()
def setUp(self):
- super(WebMockTestCase, self).setUp()
+ super().setUp()
self.wm = requests_mock.Mocker()
self.wm.start()
for patcher in self.patchers:
patcher.stop()
self.wm.stop()
- super(WebMockTestCase, self).tearDown()
+ super().tearDown()
@unittest.skipUnless("unit" in limits, "Unit skipped")
class poloniexETest(unittest.TestCase):
def setUp(self):
- super(poloniexETest, self).setUp()
+ super().setUp()
self.wm = requests_mock.Mocker()
self.wm.start()
def tearDown(self):
self.wm.stop()
- super(poloniexETest, self).tearDown()
+ super().tearDown()
def test__init(self):
with mock.patch("market.ccxt.poloniexE.session") as session:
retry_call.assert_called_with(request,
delay=1, tries=10, fargs=["foo"],
fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
- exceptions=(market.ccxt.RequestTimeout,))
+ exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
request.assert_not_called()
with self.subTest(desc="private GET"):
retry_call.assert_called_with(request,
delay=1, tries=10, fargs=["foo"],
fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
- exceptions=(market.ccxt.RequestTimeout,))
+ exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
request.assert_not_called()
with self.subTest(desc="private POST regexp"):
retry_call.assert_called_with(request,
delay=1, tries=10, fargs=["returnFoo"],
fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
- exceptions=(market.ccxt.RequestTimeout,))
+ exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
request.assert_not_called()
with self.subTest(desc="private POST non-regexp"):
retry_call.assert_called_with(request,
delay=1, tries=10, fargs=["getMarginPosition"],
fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
- exceptions=(market.ccxt.RequestTimeout,))
+ exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
request.assert_not_called()
retry_call.reset_mock()
request.reset_mock()
@unittest.skipUnless("unit" in limits, "Unit skipped")
class PortfolioTest(WebMockTestCase):
def setUp(self):
- super(PortfolioTest, self).setUp()
+ super().setUp()
with open("test_samples/test_portfolio.json") as example:
self.json_response = example.read()
@unittest.skipUnless("unit" in limits, "Unit skipped")
class MarketTest(WebMockTestCase):
def setUp(self):
- super(MarketTest, self).setUp()
+ super().setUp()
self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
with self.subTest(quiet=False):
m = market.Market(self.ccxt, self.market_args(quiet=False))
report_store.assert_called_with(m, verbose_print=True)
+ report_store().log_market.assert_called_once()
+ report_store.reset_mock()
with self.subTest(quiet=True):
m = market.Market(self.ccxt, self.market_args(quiet=True))
report_store.assert_called_with(m, verbose_print=False)
+ report_store().log_market.assert_called_once()
@mock.patch("market.ccxt")
def test_from_config(self, ccxt):
else:
time_mock.assert_called_with(sleep)
+ with self.subTest("disappearing order"), \
+ mock.patch("market.ReportStore"):
+ all_orders.reset_mock()
+ m = market.Market(self.ccxt, self.market_args())
+
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ all_orders.side_effect = [
+ [order_mock1, order_mock2],
+ [order_mock1, order_mock2],
+
+ [order_mock1, order_mock2],
+ [order_mock1, order_mock2],
+
+ []
+ ]
+
+ order_mock1.get_status.side_effect = ["open", "closed"]
+ order_mock2.get_status.side_effect = ["open", "error_disappeared"]
+
+ order_mock1.trade = mock.Mock()
+ trade_mock = mock.Mock()
+ order_mock2.trade = trade_mock
+
+ trade_mock.tick_actions_recreate.return_value = "tick1"
+
+ m.follow_orders()
+
+ trade_mock.tick_actions_recreate.assert_called_once_with(2)
+ trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
+ m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
+
@mock.patch.object(market.BalanceStore, "fetch_balances")
def test_move_balance(self, fetch_balances):
for debug in [True, False]:
m.ccxt.transfer_balance.side_effect = [
market.ccxt.RequestTimeout,
+ market.ccxt.InvalidNonce,
True
]
m.move_balances()
self.ccxt.transfer_balance.assert_has_calls([
+ mock.call("BTC", 3, "exchange", "margin"),
mock.call("BTC", 3, "exchange", "margin"),
mock.call("BTC", 3, "exchange", "margin")
])
- self.assertEqual(2, fetch_balances.call_count)
+ self.assertEqual(3, fetch_balances.call_count)
m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
- self.assertEqual(2, m.report.log_move_balances.call_count)
+ self.assertEqual(3, m.report.log_move_balances.call_count)
self.ccxt.transfer_balance.reset_mock()
m.report.reset_mock()
@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceStoreTest(WebMockTestCase):
def setUp(self):
- super(BalanceStoreTest, self).setUp()
+ super().setUp()
self.fetch_balance = {
"ETC": {
D("125"), "FOO", "long", self.m,
trade, close_if_possible=False)
+ def test_tick_actions_recreate(self):
+ value_from = portfolio.Amount("BTC", "0.5")
+ value_from.linked_to = portfolio.Amount("ETH", "10.0")
+ value_to = portfolio.Amount("BTC", "1.0")
+ trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
+
+ self.assertEqual("average", trade.tick_actions_recreate(0))
+ self.assertEqual("foo", trade.tick_actions_recreate(0, default="foo"))
+ self.assertEqual("average", trade.tick_actions_recreate(1))
+ self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(2))
+ self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(3))
+ self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(5))
+ self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(6))
+ self.assertEqual("default", trade.tick_actions_recreate(7))
+ self.assertEqual("default", trade.tick_actions_recreate(8))
@mock.patch.object(portfolio.Trade, "prepare_order")
def test_update_order(self, prepare_order):
self.m.ccxt.privatePostReturnOrderTrades.return_value = [
{
"tradeID": 42, "type": "buy", "fee": "0.0015",
- "date": "2017-12-30 12:00:12", "rate": "0.1",
+ "date": "2017-12-30 13:00:12", "rate": "0.1",
"amount": "3", "total": "0.3"
},
{
"tradeID": 43, "type": "buy", "fee": "0.0015",
- "date": "2017-12-30 13:00:12", "rate": "0.2",
+ "date": "2017-12-30 12:00:12", "rate": "0.2",
"amount": "2", "total": "0.4"
}
]
self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
self.assertEqual(2, len(order.mouvements))
- self.assertEqual(42, order.mouvements[0].id)
- self.assertEqual(43, order.mouvements[1].id)
+ self.assertEqual(43, order.mouvements[0].id)
+ self.assertEqual(42, order.mouvements[1].id)
self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
self.m.report.log_debug_action.assert_called_once()
@mock.patch.object(portfolio.Order, "fetch_mouvements")
+ @mock.patch.object(portfolio.Order, "mark_disappeared_order")
@mock.patch.object(portfolio.Order, "mark_finished_order")
- def test_fetch(self, mark_finished_order, fetch_mouvements):
+ def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
D("0.1"), "BTC", "long", self.m, "trade")
order.id = 45
self.m.report.log_debug_action.reset_mock()
self.m.ccxt.fetch_order.assert_not_called()
mark_finished_order.assert_not_called()
+ mark_disappeared_order.assert_not_called()
fetch_mouvements.assert_not_called()
with self.subTest(debug=False):
self.assertEqual(1, len(order.results))
self.m.report.log_debug_action.assert_not_called()
mark_finished_order.assert_called_once()
+ mark_disappeared_order.assert_called_once()
mark_finished_order.reset_mock()
with self.subTest(missing_order=True):
self.assertEqual("closed_unknown", order.status)
mark_finished_order.assert_called_once()
+ def test_mark_disappeared_order(self):
+ with self.subTest("Open order"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("pending", order.status)
+
+ with self.subTest("Non-zero amount"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000010",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("closed", order.status)
+
+ with self.subTest("Other mouvements"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000001",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("error_disappeared", order.status)
+
+ with self.subTest("Order disappeared"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("error_disappeared", order.status)
+
@mock.patch.object(portfolio.Order, "fetch")
def test_get_status(self, fetch):
with self.subTest(debug=True):
self.assertEqual(5, self.m.report.log_error.call_count)
self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception=mock.ANY)
+ self.m.reset_mock()
+ with self.subTest(invalid_nonce=True):
+ with self.subTest(retry_success=True):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ self.m.ccxt.create_order.side_effect = [
+ portfolio.InvalidNonce,
+ portfolio.InvalidNonce,
+ { "id": 123 },
+ ]
+ order.run()
+ self.m.ccxt.create_order.assert_has_calls([
+ mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+ mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+ mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+ ])
+ self.assertEqual(3, self.m.ccxt.create_order.call_count)
+ self.assertEqual(3, order.tries)
+ self.m.report.log_error.assert_called()
+ self.assertEqual(2, self.m.report.log_error.call_count)
+ self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after invalid nonce", exception=mock.ANY)
+ self.assertEqual(123, order.id)
+
+ self.m.reset_mock()
+ with self.subTest(retry_success=False):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ self.m.ccxt.create_order.side_effect = [
+ portfolio.InvalidNonce,
+ portfolio.InvalidNonce,
+ portfolio.InvalidNonce,
+ portfolio.InvalidNonce,
+ portfolio.InvalidNonce,
+ ]
+ order.run()
+ self.assertEqual(5, self.m.ccxt.create_order.call_count)
+ self.assertEqual(5, order.tries)
+ self.m.report.log_error.assert_called()
+ self.assertEqual(5, self.m.report.log_error.call_count)
+ self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after invalid nonce", exception=mock.ANY)
+ self.assertEqual("error", order.status)
+
self.m.reset_mock()
with self.subTest(request_timeout=True):
order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
'response': 'Hey'
})
+ @mock.patch.object(market.ReportStore, "add_log")
+ def test_log_market(self, add_log):
+ report_store = market.ReportStore(self.m)
+ class Args:
+ def __init__(self):
+ self.debug = True
+ self.quiet = False
+
+ report_store.log_market(Args(), 4, 1, "report", True)
+ add_log.assert_called_once_with({
+ "type": "market",
+ "commit": "$Format:%H$",
+ "args": { "debug": True, "quiet": False },
+ "user_id": 4,
+ "market_id": 1,
+ "report_path": "report",
+ "debug": True
+ })
+
@mock.patch.object(market.ReportStore, "print_log")
@mock.patch.object(market.ReportStore, "add_log")
def test_log_error(self, add_log, print_log):