]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blame - tests/test_market.py
Merge branch 'dev'
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / tests / test_market.py
CommitLineData
c682bdf4 1from .helper import *
30700830 2import market, store, portfolio, dbs
c682bdf4
IB
3import datetime
4
3080f31d 5@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
6class MarketTest(WebMockTestCase):
7 def setUp(self):
8 super().setUp()
9
10 self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
11
12 def test_values(self):
13 m = market.Market(self.ccxt, self.market_args())
14
15 self.assertEqual(self.ccxt, m.ccxt)
16 self.assertFalse(m.debug)
17 self.assertIsInstance(m.report, market.ReportStore)
18 self.assertIsInstance(m.trades, market.TradeStore)
19 self.assertIsInstance(m.balances, market.BalanceStore)
20 self.assertEqual(m, m.report.market)
21 self.assertEqual(m, m.trades.market)
22 self.assertEqual(m, m.balances.market)
23 self.assertEqual(m, m.ccxt._market)
24
25 m = market.Market(self.ccxt, self.market_args(debug=True))
26 self.assertTrue(m.debug)
27
28 m = market.Market(self.ccxt, self.market_args(debug=False))
29 self.assertFalse(m.debug)
30
31 with mock.patch("market.ReportStore") as report_store:
32 with self.subTest(quiet=False):
33 m = market.Market(self.ccxt, self.market_args(quiet=False))
34 report_store.assert_called_with(m, verbose_print=True)
35 report_store().log_market.assert_called_once()
36 report_store.reset_mock()
37 with self.subTest(quiet=True):
38 m = market.Market(self.ccxt, self.market_args(quiet=True))
39 report_store.assert_called_with(m, verbose_print=False)
40 report_store().log_market.assert_called_once()
41
42 @mock.patch("market.ccxt")
43 def test_from_config(self, ccxt):
44 with mock.patch("market.ReportStore"):
45 ccxt.poloniexE.return_value = self.ccxt
46
47 m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args())
48
49 self.assertEqual(self.ccxt, m.ccxt)
50
51 m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args(debug=True))
52 self.assertEqual(True, m.debug)
53
54 def test_get_tickers(self):
55 self.ccxt.fetch_tickers.side_effect = [
56 "tickers",
57 market.NotSupported
58 ]
59
60 m = market.Market(self.ccxt, self.market_args())
61 self.assertEqual("tickers", m.get_tickers())
62 self.assertEqual("tickers", m.get_tickers())
63 self.ccxt.fetch_tickers.assert_called_once()
64
65 self.assertIsNone(m.get_tickers(refresh=self.time.time()))
66
67 def test_get_ticker(self):
68 with self.subTest(get_tickers=True):
69 self.ccxt.fetch_tickers.return_value = {
70 "ETH/ETC": { "bid": 1, "ask": 3 },
71 "XVG/ETH": { "bid": 10, "ask": 40 },
72 }
73 m = market.Market(self.ccxt, self.market_args())
74
75 ticker = m.get_ticker("ETH", "ETC")
76 self.assertEqual(1, ticker["bid"])
77 self.assertEqual(3, ticker["ask"])
78 self.assertEqual(2, ticker["average"])
79 self.assertFalse(ticker["inverted"])
80
81 ticker = m.get_ticker("ETH", "XVG")
82 self.assertEqual(0.0625, ticker["average"])
83 self.assertTrue(ticker["inverted"])
84 self.assertIn("original", ticker)
85 self.assertEqual(10, ticker["original"]["bid"])
86 self.assertEqual(25, ticker["original"]["average"])
87
88 ticker = m.get_ticker("XVG", "XMR")
89 self.assertIsNone(ticker)
90
91 with self.subTest(get_tickers=False):
92 self.ccxt.fetch_tickers.return_value = None
93 self.ccxt.fetch_ticker.side_effect = [
94 { "bid": 1, "ask": 3 },
95 market.ExchangeError("foo"),
96 { "bid": 10, "ask": 40 },
97 market.ExchangeError("foo"),
98 market.ExchangeError("foo"),
99 ]
100
101 m = market.Market(self.ccxt, self.market_args())
102
103 ticker = m.get_ticker("ETH", "ETC")
104 self.ccxt.fetch_ticker.assert_called_with("ETH/ETC")
105 self.assertEqual(1, ticker["bid"])
106 self.assertEqual(3, ticker["ask"])
107 self.assertEqual(2, ticker["average"])
108 self.assertFalse(ticker["inverted"])
109
110 ticker = m.get_ticker("ETH", "XVG")
111 self.assertEqual(0.0625, ticker["average"])
112 self.assertTrue(ticker["inverted"])
113 self.assertIn("original", ticker)
114 self.assertEqual(10, ticker["original"]["bid"])
115 self.assertEqual(25, ticker["original"]["average"])
116
117 ticker = m.get_ticker("XVG", "XMR")
118 self.assertIsNone(ticker)
119
120 def test_fetch_fees(self):
121 m = market.Market(self.ccxt, self.market_args())
122 self.ccxt.fetch_fees.return_value = "Foo"
123 self.assertEqual("Foo", m.fetch_fees())
124 self.ccxt.fetch_fees.assert_called_once()
125 self.ccxt.reset_mock()
126 self.assertEqual("Foo", m.fetch_fees())
127 self.ccxt.fetch_fees.assert_not_called()
128
129 @mock.patch.object(market.Portfolio, "repartition")
130 @mock.patch.object(market.Market, "get_ticker")
131 @mock.patch.object(market.TradeStore, "compute_trades")
132 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
96959cea
IB
133 with self.subTest(available_balance_only=False),\
134 mock.patch("market.ReportStore"):
135 def _get_ticker(c1, c2):
136 if c1 == "USDT" and c2 == "BTC":
137 return { "average": D("0.0001") }
138 if c1 == "XVG" and c2 == "BTC":
139 return { "average": D("0.000001") }
140 self.fail("Should not be called with {}, {}".format(c1, c2))
141 get_ticker.side_effect = _get_ticker
142
143 repartition.return_value = {
144 "XEM": (D("0.75"), "long"),
145 "BTC": (D("0.25"), "long"),
146 }
c682bdf4
IB
147 m = market.Market(self.ccxt, self.market_args())
148 self.ccxt.fetch_all_balances.return_value = {
149 "USDT": {
150 "exchange_free": D("10000.0"),
151 "exchange_used": D("0.0"),
152 "exchange_total": D("10000.0"),
153 "total": D("10000.0")
154 },
155 "XVG": {
156 "exchange_free": D("10000.0"),
157 "exchange_used": D("0.0"),
158 "exchange_total": D("10000.0"),
159 "total": D("10000.0")
160 },
161 }
162
163 m.balances.fetch_balances(tag="tag")
164
165 m.prepare_trades()
166 compute_trades.assert_called()
167
168 call = compute_trades.call_args
169 self.assertEqual(1, call[0][0]["USDT"].value)
170 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
171 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
172 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
173 m.report.log_stage.assert_called_once_with("prepare_trades",
174 base_currency='BTC', compute_value='average',
96959cea
IB
175 available_balance_only=False, liquidity='medium',
176 only=None, repartition=None)
bb127bc8 177 m.report.log_balances.assert_called_once_with(tag="tag", checkpoint=None)
c682bdf4 178
96959cea
IB
179 compute_trades.reset_mock()
180 with self.subTest(available_balance_only=True),\
181 mock.patch("market.ReportStore"):
182 def _get_ticker(c1, c2):
183 if c1 == "ZRC" and c2 == "BTC":
184 return { "average": D("0.0001") }
185 if c1 == "DOGE" and c2 == "BTC":
186 return { "average": D("0.000001") }
187 if c1 == "ETH" and c2 == "BTC":
188 return { "average": D("0.1") }
3d6f74ee
IB
189 if c1 == "FOO" and c2 == "BTC":
190 return { "average": D("0.1") }
96959cea
IB
191 self.fail("Should not be called with {}, {}".format(c1, c2))
192 get_ticker.side_effect = _get_ticker
193
194 repartition.return_value = {
3d6f74ee
IB
195 "DOGE": (D("0.20"), "short"),
196 "BTC": (D("0.20"), "long"),
197 "ETH": (D("0.20"), "long"),
198 "XMR": (D("0.20"), "long"),
199 "FOO": (D("0.20"), "long"),
96959cea
IB
200 }
201 m = market.Market(self.ccxt, self.market_args())
202 self.ccxt.fetch_all_balances.return_value = {
203 "ZRC": {
204 "exchange_free": D("2.0"),
205 "exchange_used": D("0.0"),
206 "exchange_total": D("2.0"),
207 "total": D("2.0")
208 },
209 "DOGE": {
210 "exchange_free": D("5.0"),
211 "exchange_used": D("0.0"),
212 "exchange_total": D("5.0"),
213 "total": D("5.0")
214 },
215 "BTC": {
3d6f74ee 216 "exchange_free": D("0.065"),
96959cea 217 "exchange_used": D("0.02"),
3d6f74ee
IB
218 "exchange_total": D("0.085"),
219 "margin_available": D("0.035"),
96959cea 220 "margin_in_position": D("0.01"),
3d6f74ee 221 "margin_total": D("0.045"),
96959cea
IB
222 "total": D("0.13")
223 },
224 "ETH": {
225 "exchange_free": D("1.0"),
226 "exchange_used": D("0.0"),
227 "exchange_total": D("1.0"),
228 "total": D("1.0")
229 },
3d6f74ee
IB
230 "FOO": {
231 "exchange_free": D("0.1"),
232 "exchange_used": D("0.0"),
233 "exchange_total": D("0.1"),
234 "total": D("0.1"),
235 },
96959cea
IB
236 }
237
238 m.balances.fetch_balances(tag="tag")
239 m.prepare_trades(available_balance_only=True)
240 compute_trades.assert_called_once()
241
242 call = compute_trades.call_args[0]
243 values_in_base = call[0]
244 new_repartition = call[1]
245
246 self.assertEqual(portfolio.Amount("BTC", "-0.025"),
247 new_repartition["DOGE"] - values_in_base["DOGE"])
96959cea 248 self.assertEqual(0,
3d6f74ee
IB
249 new_repartition["ETH"] - values_in_base["ETH"])
250 self.assertIsNone(new_repartition.get("ZRC"))
96959cea
IB
251 self.assertEqual(portfolio.Amount("BTC", "0.025"),
252 new_repartition["XMR"])
3d6f74ee
IB
253 self.assertEqual(portfolio.Amount("BTC", "0.015"),
254 new_repartition["FOO"] - values_in_base["FOO"])
96959cea
IB
255
256 compute_trades.reset_mock()
257 with self.subTest(available_balance_only=True, balance=0),\
258 mock.patch("market.ReportStore"):
259 def _get_ticker(c1, c2):
260 if c1 == "ETH" and c2 == "BTC":
261 return { "average": D("0.1") }
262 self.fail("Should not be called with {}, {}".format(c1, c2))
263 get_ticker.side_effect = _get_ticker
264
265 repartition.return_value = {
266 "BTC": (D("0.5"), "long"),
267 "ETH": (D("0.5"), "long"),
268 }
269 m = market.Market(self.ccxt, self.market_args())
270 self.ccxt.fetch_all_balances.return_value = {
271 "ETH": {
272 "exchange_free": D("1.0"),
273 "exchange_used": D("0.0"),
274 "exchange_total": D("1.0"),
275 "total": D("1.0")
276 },
277 }
278
279 m.balances.fetch_balances(tag="tag")
280 m.prepare_trades(available_balance_only=True)
281 compute_trades.assert_called_once()
282
283 call = compute_trades.call_args[0]
284 values_in_base = call[0]
285 new_repartition = call[1]
286
287 self.assertEqual(new_repartition["ETH"], values_in_base["ETH"])
c682bdf4
IB
288
289 @mock.patch.object(market.time, "sleep")
290 @mock.patch.object(market.TradeStore, "all_orders")
291 def test_follow_orders(self, all_orders, time_mock):
292 for debug, sleep in [
293 (False, None), (True, None),
294 (False, 12), (True, 12)]:
295 with self.subTest(sleep=sleep, debug=debug), \
296 mock.patch("market.ReportStore"):
297 m = market.Market(self.ccxt, self.market_args(debug=debug))
298
299 order_mock1 = mock.Mock()
300 order_mock2 = mock.Mock()
301 order_mock3 = mock.Mock()
302 all_orders.side_effect = [
303 [order_mock1, order_mock2],
304 [order_mock1, order_mock2],
305
306 [order_mock1, order_mock3],
307 [order_mock1, order_mock3],
308
309 [order_mock1, order_mock3],
310 [order_mock1, order_mock3],
311
312 []
313 ]
314
315 order_mock1.get_status.side_effect = ["open", "open", "closed"]
316 order_mock2.get_status.side_effect = ["open"]
317 order_mock3.get_status.side_effect = ["open", "closed"]
318
319 order_mock1.trade = mock.Mock()
320 order_mock2.trade = mock.Mock()
321 order_mock3.trade = mock.Mock()
322
323 m.follow_orders(sleep=sleep)
324
325 order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
326 order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
327 self.assertEqual(2, order_mock1.trade.update_order.call_count)
328 self.assertEqual(3, order_mock1.get_status.call_count)
329
330 order_mock2.trade.update_order.assert_any_call(order_mock2, 1)
331 self.assertEqual(1, order_mock2.trade.update_order.call_count)
332 self.assertEqual(1, order_mock2.get_status.call_count)
333
334 order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
335 self.assertEqual(1, order_mock3.trade.update_order.call_count)
336 self.assertEqual(2, order_mock3.get_status.call_count)
337 m.report.log_stage.assert_called()
338 calls = [
339 mock.call("follow_orders_begin"),
340 mock.call("follow_orders_tick_1"),
341 mock.call("follow_orders_tick_2"),
342 mock.call("follow_orders_tick_3"),
343 mock.call("follow_orders_end"),
344 ]
345 m.report.log_stage.assert_has_calls(calls)
346 m.report.log_orders.assert_called()
347 self.assertEqual(3, m.report.log_orders.call_count)
348 calls = [
349 mock.call([order_mock1, order_mock2], tick=1),
350 mock.call([order_mock1, order_mock3], tick=2),
351 mock.call([order_mock1, order_mock3], tick=3),
352 ]
353 m.report.log_orders.assert_has_calls(calls)
354 calls = [
355 mock.call(order_mock1, 3, finished=True),
356 mock.call(order_mock3, 3, finished=True),
357 ]
358 m.report.log_order.assert_has_calls(calls)
359
360 if sleep is None:
361 if debug:
362 m.report.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
363 time_mock.assert_called_with(7)
364 else:
365 time_mock.assert_called_with(30)
366 else:
367 time_mock.assert_called_with(sleep)
368
369 with self.subTest("disappearing order"), \
370 mock.patch("market.ReportStore"):
371 all_orders.reset_mock()
372 m = market.Market(self.ccxt, self.market_args())
373
374 order_mock1 = mock.Mock()
375 order_mock2 = mock.Mock()
376 all_orders.side_effect = [
377 [order_mock1, order_mock2],
378 [order_mock1, order_mock2],
379
380 [order_mock1, order_mock2],
381 [order_mock1, order_mock2],
382
383 []
384 ]
385
386 order_mock1.get_status.side_effect = ["open", "closed"]
387 order_mock2.get_status.side_effect = ["open", "error_disappeared"]
388
389 order_mock1.trade = mock.Mock()
390 trade_mock = mock.Mock()
391 order_mock2.trade = trade_mock
392
393 trade_mock.tick_actions_recreate.return_value = "tick1"
af928d32
IB
394 new_order_mock = mock.Mock()
395 trade_mock.prepare_order.return_value = new_order_mock
c682bdf4
IB
396
397 m.follow_orders()
398
399 trade_mock.tick_actions_recreate.assert_called_once_with(2)
400 trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
401 m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
af928d32
IB
402 m.report.log_order.assert_called_with(order_mock2, 2, new_order=new_order_mock)
403 new_order_mock.run.assert_called_once_with()
c682bdf4 404
84c9fe33
IB
405 with self.subTest("disappearing order no action to do"), \
406 mock.patch("market.ReportStore"):
407 all_orders.reset_mock()
408 m = market.Market(self.ccxt, self.market_args())
409
410 order_mock1 = mock.Mock()
411 order_mock2 = mock.Mock()
412 all_orders.side_effect = [
413 [order_mock1, order_mock2],
414 [order_mock1, order_mock2],
415
416 [order_mock1, order_mock2],
417 [order_mock1, order_mock2],
418
419 []
420 ]
421
422 order_mock1.get_status.side_effect = ["open", "closed"]
423 order_mock2.get_status.side_effect = ["open", "error_disappeared"]
424
425 order_mock1.trade = mock.Mock()
426 trade_mock = mock.Mock()
427 order_mock2.trade = trade_mock
428
429 trade_mock.tick_actions_recreate.return_value = "tick1"
430 trade_mock.prepare_order.return_value = None
431
432 m.follow_orders()
433
434 trade_mock.tick_actions_recreate.assert_called_once_with(2)
435 trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
436 m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
437 m.report.log_order.assert_called_with(order_mock2, 2, finished=True)
438
c682bdf4
IB
439 @mock.patch.object(market.BalanceStore, "fetch_balances")
440 def test_move_balance(self, fetch_balances):
441 for debug in [True, False]:
442 with self.subTest(debug=debug),\
443 mock.patch("market.ReportStore"):
444 m = market.Market(self.ccxt, self.market_args(debug=debug))
445
446 value_from = portfolio.Amount("BTC", "1.0")
447 value_from.linked_to = portfolio.Amount("ETH", "10.0")
448 value_to = portfolio.Amount("BTC", "10.0")
449 trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
450
451 value_from = portfolio.Amount("BTC", "0.0")
452 value_from.linked_to = portfolio.Amount("ETH", "0.0")
453 value_to = portfolio.Amount("BTC", "-3.0")
454 trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
455
456 value_from = portfolio.Amount("USDT", "0.0")
457 value_from.linked_to = portfolio.Amount("XVG", "0.0")
458 value_to = portfolio.Amount("USDT", "-50.0")
459 trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
460
461 m.trades.all = [trade1, trade2, trade3]
462 balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
463 balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
464 balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
465 m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
466
467 m.move_balances()
468
469 fetch_balances.assert_called_with()
470 m.report.log_move_balances.assert_called_once()
471
472 if debug:
473 m.report.log_debug_action.assert_called()
474 self.assertEqual(3, m.report.log_debug_action.call_count)
475 else:
476 self.ccxt.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
477 self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
478 self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
479
480 m.report.reset_mock()
481 fetch_balances.reset_mock()
482 with self.subTest(retry=True):
483 with mock.patch("market.ReportStore"):
484 m = market.Market(self.ccxt, self.market_args())
485
486 value_from = portfolio.Amount("BTC", "0.0")
487 value_from.linked_to = portfolio.Amount("ETH", "0.0")
488 value_to = portfolio.Amount("BTC", "-3.0")
489 trade = portfolio.Trade(value_from, value_to, "ETH", m)
490
491 m.trades.all = [trade]
492 balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
493 m.balances.all = {"BTC": balance}
494
495 m.ccxt.transfer_balance.side_effect = [
496 market.ccxt.RequestTimeout,
497 market.ccxt.InvalidNonce,
498 True
499 ]
500 m.move_balances()
501 self.ccxt.transfer_balance.assert_has_calls([
502 mock.call("BTC", 3, "exchange", "margin"),
503 mock.call("BTC", 3, "exchange", "margin"),
504 mock.call("BTC", 3, "exchange", "margin")
505 ])
506 self.assertEqual(3, fetch_balances.call_count)
507 m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
508 self.assertEqual(3, m.report.log_move_balances.call_count)
509
510 self.ccxt.transfer_balance.reset_mock()
511 m.report.reset_mock()
512 fetch_balances.reset_mock()
513 with self.subTest(retry=True, too_much=True):
514 with mock.patch("market.ReportStore"):
515 m = market.Market(self.ccxt, self.market_args())
516
517 value_from = portfolio.Amount("BTC", "0.0")
518 value_from.linked_to = portfolio.Amount("ETH", "0.0")
519 value_to = portfolio.Amount("BTC", "-3.0")
520 trade = portfolio.Trade(value_from, value_to, "ETH", m)
521
522 m.trades.all = [trade]
523 balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
524 m.balances.all = {"BTC": balance}
525
526 m.ccxt.transfer_balance.side_effect = [
527 market.ccxt.RequestTimeout,
528 market.ccxt.RequestTimeout,
529 market.ccxt.RequestTimeout,
530 market.ccxt.RequestTimeout,
531 market.ccxt.RequestTimeout,
532 ]
533 with self.assertRaises(market.ccxt.RequestTimeout):
534 m.move_balances()
535
536 self.ccxt.transfer_balance.reset_mock()
537 m.report.reset_mock()
538 fetch_balances.reset_mock()
539 with self.subTest(retry=True, partial_result=True):
540 with mock.patch("market.ReportStore"):
541 m = market.Market(self.ccxt, self.market_args())
542
543 value_from = portfolio.Amount("BTC", "1.0")
544 value_from.linked_to = portfolio.Amount("ETH", "10.0")
545 value_to = portfolio.Amount("BTC", "10.0")
546 trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
547
548 value_from = portfolio.Amount("BTC", "0.0")
549 value_from.linked_to = portfolio.Amount("ETH", "0.0")
550 value_to = portfolio.Amount("BTC", "-3.0")
551 trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
552
553 value_from = portfolio.Amount("USDT", "0.0")
554 value_from.linked_to = portfolio.Amount("XVG", "0.0")
555 value_to = portfolio.Amount("USDT", "-50.0")
556 trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
557
558 m.trades.all = [trade1, trade2, trade3]
559 balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
560 balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
561 balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
562 m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
563
564 call_counts = { "BTC": 0, "USDT": 0, "ETC": 0 }
565 def _transfer_balance(currency, amount, from_, to_):
566 call_counts[currency] += 1
567 if currency == "BTC":
568 m.balances.all["BTC"] = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" })
569 if currency == "USDT":
570 if call_counts["USDT"] == 1:
571 raise market.ccxt.RequestTimeout
572 else:
573 m.balances.all["USDT"] = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" })
574 if currency == "ETC":
575 m.balances.all["ETC"] = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" })
576
577
578 m.ccxt.transfer_balance.side_effect = _transfer_balance
579
580 m.move_balances()
581 self.ccxt.transfer_balance.assert_has_calls([
582 mock.call("BTC", 3, "exchange", "margin"),
583 mock.call('USDT', 100, 'exchange', 'margin'),
584 mock.call('USDT', 100, 'exchange', 'margin'),
585 mock.call("ETC", 5, "margin", "exchange")
586 ])
587 self.assertEqual(2, fetch_balances.call_count)
588 m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
589 self.assertEqual(2, m.report.log_move_balances.call_count)
590 m.report.log_move_balances.asser_has_calls([
591 mock.call(
592 {
593 'BTC': portfolio.Amount("BTC", "3"),
594 'USDT': portfolio.Amount("USDT", "150"),
595 'ETC': portfolio.Amount("ETC", "10"),
596 },
597 {
598 'BTC': portfolio.Amount("BTC", "3"),
599 'USDT': portfolio.Amount("USDT", "100"),
600 }),
601 mock.call(
602 {
603 'BTC': portfolio.Amount("BTC", "3"),
604 'USDT': portfolio.Amount("USDT", "150"),
605 'ETC': portfolio.Amount("ETC", "10"),
606 },
607 {
608 'BTC': portfolio.Amount("BTC", "0"),
609 'USDT': portfolio.Amount("USDT", "100"),
610 'ETC': portfolio.Amount("ETC", "-5"),
611 }),
612 ])
613
614
615 def test_store_file_report(self):
616 file_open = mock.mock_open()
617 m = market.Market(self.ccxt,
618 self.market_args(report_path="present"), user_id=1)
619 with self.subTest(file="present"),\
620 mock.patch("market.open", file_open),\
621 mock.patch.object(m, "report") as report,\
622 mock.patch.object(market, "datetime") as time_mock:
623
624 report.print_logs = [[time_mock.now(), "Foo"], [time_mock.now(), "Bar"]]
625 report.to_json.return_value = "json_content"
626
627 m.store_file_report(datetime.datetime(2018, 2, 25))
628
629 file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
630 file_open.assert_any_call("present/2018-02-25T00:00:00_1.log", "w")
631 file_open().write.assert_any_call("json_content")
632 file_open().write.assert_any_call("Foo\nBar")
633 m.report.to_json.assert_called_once_with()
634
635 m = market.Market(self.ccxt, self.market_args(report_path="error"), user_id=1)
636 with self.subTest(file="error"),\
637 mock.patch("market.open") as file_open,\
638 mock.patch.object(m, "report") as report,\
639 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
640 file_open.side_effect = FileNotFoundError
641
642 m.store_file_report(datetime.datetime(2018, 2, 25))
643
644 self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
645
30700830
IB
646 @mock.patch.object(dbs, "psql")
647 def test_store_database_report(self, psql):
c682bdf4
IB
648 cursor_mock = mock.MagicMock()
649
30700830 650 psql.cursor.return_value = cursor_mock
c682bdf4
IB
651 m = market.Market(self.ccxt, self.market_args(),
652 pg_config={"config": "pg_config"}, user_id=1)
653 cursor_mock.fetchone.return_value = [42]
654
655 with self.subTest(error=False),\
656 mock.patch.object(m, "report") as report:
657 report.to_json_array.return_value = [
658 ("date1", "type1", "payload1"),
659 ("date2", "type2", "payload2"),
660 ]
661 m.store_database_report(datetime.datetime(2018, 3, 24))
30700830 662 psql.assert_has_calls([
c682bdf4
IB
663 mock.call.cursor(),
664 mock.call.cursor().execute('INSERT INTO reports("date", "market_config_id", "debug") VALUES (%s, %s, %s) RETURNING id;', (datetime.datetime(2018, 3, 24), None, False)),
665 mock.call.cursor().fetchone(),
666 mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date1', 42, 'type1', 'payload1')),
667 mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date2', 42, 'type2', 'payload2')),
668 mock.call.commit(),
669 mock.call.cursor().close(),
c682bdf4
IB
670 ])
671
c682bdf4
IB
672 with self.subTest(error=True),\
673 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
30700830 674 psql.cursor.side_effect = Exception("Bouh")
c682bdf4
IB
675 m.store_database_report(datetime.datetime(2018, 3, 24))
676 self.assertEqual(stdout_mock.getvalue(), "impossible to store report to database: Exception; Bouh\n")
677
30700830 678 @mock.patch.object(dbs, "redis")
1593c7a9 679 def test_store_redis_report(self, redis):
1593c7a9
IB
680 m = market.Market(self.ccxt, self.market_args(),
681 redis_config={"config": "redis_config"}, market_id=1)
682
683 with self.subTest(error=False),\
684 mock.patch.object(m, "report") as report:
685 report.to_json_redis.return_value = [
686 ("type1", "payload1"),
687 ("type2", "payload2"),
688 ]
689 m.store_redis_report(datetime.datetime(2018, 3, 24))
30700830 690 redis.assert_has_calls([
1593c7a9
IB
691 mock.call.set("/cryptoportfolio/1/2018-03-24T00:00:00/type1", "payload1", ex=31*24*60*60),
692 mock.call.set("/cryptoportfolio/1/latest/type1", "payload1"),
693 mock.call.set("/cryptoportfolio/1/2018-03-24T00:00:00/type2", "payload2", ex=31*24*60*60),
694 mock.call.set("/cryptoportfolio/1/latest/type2", "payload2"),
17fd3f75 695 mock.call.set("/cryptoportfolio/1/latest/date", "2018-03-24T00:00:00"),
1593c7a9
IB
696 ])
697
30700830 698 redis.reset_mock()
1593c7a9
IB
699 with self.subTest(error=True),\
700 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
30700830 701 redis.set.side_effect = Exception("Bouh")
1593c7a9
IB
702 m.store_redis_report(datetime.datetime(2018, 3, 24))
703 self.assertEqual(stdout_mock.getvalue(), "impossible to store report to redis: Exception; Bouh\n")
704
c682bdf4
IB
705 def test_store_report(self):
706 m = market.Market(self.ccxt, self.market_args(report_db=False), user_id=1)
30700830
IB
707 with self.subTest(file=None, pg_connected=None),\
708 mock.patch.object(dbs, "psql_connected") as psql,\
709 mock.patch.object(dbs, "redis_connected") as redis,\
c682bdf4
IB
710 mock.patch.object(m, "report") as report,\
711 mock.patch.object(m, "store_database_report") as db_report,\
1593c7a9 712 mock.patch.object(m, "store_redis_report") as redis_report,\
c682bdf4 713 mock.patch.object(m, "store_file_report") as file_report:
30700830
IB
714 psql.return_value = False
715 redis.return_value = False
c682bdf4
IB
716 m.store_report()
717 report.merge.assert_called_with(store.Portfolio.report)
718
719 file_report.assert_not_called()
720 db_report.assert_not_called()
1593c7a9 721 redis_report.assert_not_called()
c682bdf4
IB
722
723 report.reset_mock()
724 m = market.Market(self.ccxt, self.market_args(report_db=False, report_path="present"), user_id=1)
30700830
IB
725 with self.subTest(file="present", pg_connected=None),\
726 mock.patch.object(dbs, "psql_connected") as psql,\
727 mock.patch.object(dbs, "redis_connected") as redis,\
c682bdf4
IB
728 mock.patch.object(m, "report") as report,\
729 mock.patch.object(m, "store_file_report") as file_report,\
1593c7a9 730 mock.patch.object(m, "store_redis_report") as redis_report,\
c682bdf4 731 mock.patch.object(m, "store_database_report") as db_report,\
e7d7c0e5 732 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
733 psql.return_value = False
734 redis.return_value = False
c682bdf4
IB
735 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
736
737 m.store_report()
738
739 report.merge.assert_called_with(store.Portfolio.report)
740 file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
741 db_report.assert_not_called()
1593c7a9 742 redis_report.assert_not_called()
c682bdf4
IB
743
744 report.reset_mock()
745 m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"), user_id=1)
30700830
IB
746 with self.subTest(file="present", pg_connected=None, report_db=True),\
747 mock.patch.object(dbs, "psql_connected") as psql,\
748 mock.patch.object(dbs, "redis_connected") as redis,\
c682bdf4
IB
749 mock.patch.object(m, "report") as report,\
750 mock.patch.object(m, "store_file_report") as file_report,\
1593c7a9 751 mock.patch.object(m, "store_redis_report") as redis_report,\
c682bdf4 752 mock.patch.object(m, "store_database_report") as db_report,\
e7d7c0e5 753 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
754 psql.return_value = False
755 redis.return_value = False
c682bdf4
IB
756 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
757
758 m.store_report()
759
760 report.merge.assert_called_with(store.Portfolio.report)
761 file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
762 db_report.assert_not_called()
1593c7a9 763 redis_report.assert_not_called()
c682bdf4
IB
764
765 report.reset_mock()
30700830
IB
766 m = market.Market(self.ccxt, self.market_args(report_db=True), user_id=1)
767 with self.subTest(file=None, pg_connected=True),\
768 mock.patch.object(dbs, "psql_connected") as psql,\
769 mock.patch.object(dbs, "redis_connected") as redis,\
c682bdf4
IB
770 mock.patch.object(m, "report") as report,\
771 mock.patch.object(m, "store_file_report") as file_report,\
1593c7a9 772 mock.patch.object(m, "store_redis_report") as redis_report,\
c682bdf4 773 mock.patch.object(m, "store_database_report") as db_report,\
e7d7c0e5 774 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
775 psql.return_value = True
776 redis.return_value = False
c682bdf4
IB
777 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
778
779 m.store_report()
780
781 report.merge.assert_called_with(store.Portfolio.report)
782 file_report.assert_not_called()
783 db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
1593c7a9 784 redis_report.assert_not_called()
c682bdf4
IB
785
786 report.reset_mock()
787 m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"),
30700830
IB
788 user_id=1)
789 with self.subTest(file="present", pg_connected=True),\
790 mock.patch.object(dbs, "psql_connected") as psql,\
791 mock.patch.object(dbs, "redis_connected") as redis,\
c682bdf4
IB
792 mock.patch.object(m, "report") as report,\
793 mock.patch.object(m, "store_file_report") as file_report,\
1593c7a9 794 mock.patch.object(m, "store_redis_report") as redis_report,\
c682bdf4 795 mock.patch.object(m, "store_database_report") as db_report,\
e7d7c0e5 796 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
797 psql.return_value = True
798 redis.return_value = False
c682bdf4
IB
799 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
800
801 m.store_report()
802
803 report.merge.assert_called_with(store.Portfolio.report)
804 file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
805 db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
1593c7a9
IB
806 redis_report.assert_not_called()
807
808 report.reset_mock()
809 m = market.Market(self.ccxt, self.market_args(report_redis=False),
30700830
IB
810 user_id=1)
811 with self.subTest(redis_connected=True, report_redis=False),\
812 mock.patch.object(dbs, "psql_connected") as psql,\
813 mock.patch.object(dbs, "redis_connected") as redis,\
1593c7a9
IB
814 mock.patch.object(m, "report") as report,\
815 mock.patch.object(m, "store_file_report") as file_report,\
816 mock.patch.object(m, "store_redis_report") as redis_report,\
817 mock.patch.object(m, "store_database_report") as db_report,\
818 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
819 psql.return_value = False
820 redis.return_value = True
1593c7a9
IB
821 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
822
823 m.store_report()
824 redis_report.assert_not_called()
825
826 report.reset_mock()
827 m = market.Market(self.ccxt, self.market_args(report_redis=True),
828 user_id=1)
30700830
IB
829 with self.subTest(redis_connected=False, report_redis=True),\
830 mock.patch.object(dbs, "psql_connected") as psql,\
831 mock.patch.object(dbs, "redis_connected") as redis,\
1593c7a9
IB
832 mock.patch.object(m, "report") as report,\
833 mock.patch.object(m, "store_file_report") as file_report,\
834 mock.patch.object(m, "store_redis_report") as redis_report,\
835 mock.patch.object(m, "store_database_report") as db_report,\
836 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
837 psql.return_value = False
838 redis.return_value = False
1593c7a9
IB
839 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
840
841 m.store_report()
842 redis_report.assert_not_called()
843
844 report.reset_mock()
845 m = market.Market(self.ccxt, self.market_args(report_redis=True),
30700830
IB
846 user_id=1)
847 with self.subTest(redis_connected=True, report_redis=True),\
848 mock.patch.object(dbs, "psql_connected") as psql,\
849 mock.patch.object(dbs, "redis_connected") as redis,\
1593c7a9
IB
850 mock.patch.object(m, "report") as report,\
851 mock.patch.object(m, "store_file_report") as file_report,\
852 mock.patch.object(m, "store_redis_report") as redis_report,\
853 mock.patch.object(m, "store_database_report") as db_report,\
854 mock.patch.object(market.datetime, "datetime") as time_mock:
30700830
IB
855 psql.return_value = False
856 redis.return_value = True
1593c7a9
IB
857 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
858
859 m.store_report()
860 redis_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
c682bdf4 861
ceb7fc4c 862 def test_print_tickers(self):
c682bdf4
IB
863 m = market.Market(self.ccxt, self.market_args())
864
865 with mock.patch.object(m.balances, "in_currency") as in_currency,\
866 mock.patch.object(m.report, "log_stage") as log_stage,\
867 mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
868 mock.patch.object(m.report, "print_log") as print_log:
869
870 in_currency.return_value = {
871 "BTC": portfolio.Amount("BTC", "0.65"),
872 "ETH": portfolio.Amount("BTC", "0.3"),
873 }
874
ceb7fc4c 875 m.print_tickers()
c682bdf4 876
c682bdf4
IB
877 print_log.assert_has_calls([
878 mock.call("total:"),
879 mock.call(portfolio.Amount("BTC", "0.95")),
880 ])
881
882 @mock.patch("market.Processor.process")
883 @mock.patch("market.ReportStore.log_error")
884 @mock.patch("market.Market.store_report")
885 def test_process(self, store_report, log_error, process):
886 m = market.Market(self.ccxt, self.market_args())
ceb7fc4c
IB
887 with self.subTest(actions=[], before=False, after=False):
888 m.process([])
c682bdf4
IB
889
890 process.assert_not_called()
891 store_report.assert_called_once()
892 log_error.assert_not_called()
893
894 process.reset_mock()
895 log_error.reset_mock()
896 store_report.reset_mock()
897 with self.subTest(before=True, after=False):
ceb7fc4c 898 m.process(["foo"], before=True)
c682bdf4 899
ceb7fc4c 900 process.assert_called_once_with("foo", steps="before")
c682bdf4
IB
901 store_report.assert_called_once()
902 log_error.assert_not_called()
903
904 process.reset_mock()
905 log_error.reset_mock()
906 store_report.reset_mock()
907 with self.subTest(before=False, after=True):
ceb7fc4c 908 m.process(["sell_all"], after=True)
c682bdf4
IB
909
910 process.assert_called_once_with("sell_all", steps="after")
911 store_report.assert_called_once()
912 log_error.assert_not_called()
913
914 process.reset_mock()
915 log_error.reset_mock()
916 store_report.reset_mock()
ceb7fc4c
IB
917 with self.subTest(before=False, after=False):
918 m.process(["foo"])
c682bdf4 919
ceb7fc4c 920 process.assert_called_once_with("foo", steps="all")
c682bdf4
IB
921 store_report.assert_called_once()
922 log_error.assert_not_called()
923
924 process.reset_mock()
925 log_error.reset_mock()
926 store_report.reset_mock()
ceb7fc4c
IB
927 with self.subTest(before=True, after=True):
928 m.process(["sell_all"], before=True, after=True)
c682bdf4 929
ceb7fc4c 930 process.assert_called_once_with("sell_all", steps="all")
c682bdf4 931 store_report.assert_called_once()
c682bdf4 932 log_error.assert_not_called()
c682bdf4 933
5321200c
IB
934 process.reset_mock()
935 log_error.reset_mock()
936 store_report.reset_mock()
937 with self.subTest(authentication_error=True):
938 m.ccxt.check_required_credentials.side_effect = market.ccxt.AuthenticationError
939
940 m.process(["some_action"], before=True)
941 log_error.assert_called_with("market_authentication", message="Impossible to authenticate to market")
942 store_report.assert_called_once()
943
944 m.ccxt.check_required_credentials.side_effect = True
ceb7fc4c 945 process.reset_mock()
c682bdf4
IB
946 log_error.reset_mock()
947 store_report.reset_mock()
948 with self.subTest(unhandled_exception=True):
949 process.side_effect = Exception("bouh")
950
ceb7fc4c 951 m.process(["some_action"], before=True)
4ae84fb7 952 log_error.assert_called_with("market_process", exception=mock.ANY, message=mock.ANY)
c682bdf4
IB
953 store_report.assert_called_once()
954
955
3080f31d 956@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
957class ProcessorTest(WebMockTestCase):
958 def test_values(self):
959 processor = market.Processor(self.m)
960
961 self.assertEqual(self.m, processor.market)
962
963 def test_run_action(self):
964 processor = market.Processor(self.m)
965
966 with mock.patch.object(processor, "parse_args") as parse_args:
967 method_mock = mock.Mock()
968 parse_args.return_value = [method_mock, { "foo": "bar" }]
969
970 processor.run_action("foo", "bar", "baz")
971
972 parse_args.assert_called_with("foo", "bar", "baz")
973
974 method_mock.assert_called_with(foo="bar")
975
976 processor.run_action("wait_for_recent", "bar", "baz")
977
978 method_mock.assert_called_with(foo="bar")
979
980 def test_select_step(self):
981 processor = market.Processor(self.m)
982
983 scenario = processor.scenarios["sell_all"]
984
985 self.assertEqual(scenario, processor.select_steps(scenario, "all"))
986 self.assertEqual(["all_sell"], list(map(lambda x: x["name"], processor.select_steps(scenario, "before"))))
987 self.assertEqual(["wait", "all_buy"], list(map(lambda x: x["name"], processor.select_steps(scenario, "after"))))
988 self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, 2))))
989 self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, "wait"))))
990
991 with self.assertRaises(TypeError):
992 processor.select_steps(scenario, ["wait"])
993
ceb7fc4c
IB
994 def test_can_process(self):
995 processor = market.Processor(self.m)
996
997 with self.subTest(True):
998 self.assertTrue(processor.can_process("sell_all"))
999
1000 with self.subTest(False):
1001 self.assertFalse(processor.can_process("unknown_action"))
1002
c682bdf4
IB
1003 @mock.patch("market.Processor.process_step")
1004 def test_process(self, process_step):
ceb7fc4c
IB
1005 with self.subTest("unknown action"):
1006 processor = market.Processor(self.m)
1007 with self.assertRaises(TypeError):
1008 processor.process("unknown_action")
1009
1010 with self.subTest("nominal case"):
1011 processor = market.Processor(self.m)
c682bdf4 1012
ceb7fc4c
IB
1013 processor.process("sell_all", foo="bar")
1014 self.assertEqual(3, process_step.call_count)
c682bdf4 1015
ceb7fc4c
IB
1016 steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls))
1017 scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls))
1018 kwargs = list(map(lambda x: x[1][2], process_step.mock_calls))
1019 self.assertEqual(["all_sell", "wait", "all_buy"], steps)
1020 self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names)
1021 self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs)
c682bdf4 1022
ceb7fc4c 1023 process_step.reset_mock()
c682bdf4 1024
ceb7fc4c 1025 processor.process("sell_needed", steps=["before", "after"])
bb127bc8 1026 self.assertEqual(4, process_step.call_count)
c682bdf4
IB
1027
1028 def test_method_arguments(self):
1029 ccxt = mock.Mock(spec=market.ccxt.poloniexE)
1030 m = market.Market(ccxt, self.market_args())
1031
1032 processor = market.Processor(m)
1033
1034 method, arguments = processor.method_arguments("wait_for_recent")
1035 self.assertEqual(market.Portfolio.wait_for_recent, method)
1036 self.assertEqual(["delta", "poll"], arguments)
1037
1038 method, arguments = processor.method_arguments("prepare_trades")
1039 self.assertEqual(m.prepare_trades, method)
96959cea 1040 self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only', 'available_balance_only'], arguments)
c682bdf4
IB
1041
1042 method, arguments = processor.method_arguments("prepare_orders")
1043 self.assertEqual(m.trades.prepare_orders, method)
1044
1045 method, arguments = processor.method_arguments("move_balances")
1046 self.assertEqual(m.move_balances, method)
1047
1048 method, arguments = processor.method_arguments("run_orders")
1049 self.assertEqual(m.trades.run_orders, method)
1050
1051 method, arguments = processor.method_arguments("follow_orders")
1052 self.assertEqual(m.follow_orders, method)
1053
1054 method, arguments = processor.method_arguments("close_trades")
1055 self.assertEqual(m.trades.close_trades, method)
1056
ceb7fc4c
IB
1057 method, arguments = processor.method_arguments("print_tickers")
1058 self.assertEqual(m.print_tickers, method)
1059
c682bdf4
IB
1060 def test_process_step(self):
1061 processor = market.Processor(self.m)
1062
1063 with mock.patch.object(processor, "run_action") as run_action:
bb127bc8 1064 step = processor.scenarios["sell_needed"][2]
c682bdf4
IB
1065
1066 processor.process_step("foo", step, {"foo":"bar"})
1067
1068 self.m.report.log_stage.assert_has_calls([
bb127bc8
IB
1069 mock.call("process_foo__2_sell_begin"),
1070 mock.call("process_foo__2_sell_end"),
c682bdf4
IB
1071 ])
1072 self.m.balances.fetch_balances.assert_has_calls([
bb127bc8
IB
1073 mock.call(tag="process_foo__2_sell_begin"),
1074 mock.call(tag="process_foo__2_sell_end"),
c682bdf4
IB
1075 ])
1076
1077 self.assertEqual(5, run_action.call_count)
1078
1079 run_action.assert_has_calls([
1080 mock.call('prepare_trades', {}, {'foo': 'bar'}),
1081 mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}),
1082 mock.call('run_orders', {}, {'foo': 'bar'}),
1083 mock.call('follow_orders', {}, {'foo': 'bar'}),
1084 mock.call('close_trades', {}, {'foo': 'bar'}),
1085 ])
1086
1087 self.m.reset_mock()
1088 with mock.patch.object(processor, "run_action") as run_action:
1089 step = processor.scenarios["sell_needed"][0]
1090
bb127bc8
IB
1091 processor.process_step("foo", step, {"foo":"bar"})
1092
1093 self.m.report.log_stage.assert_has_calls([
1094 mock.call("process_foo__0_print_balances_begin"),
1095 mock.call("process_foo__0_print_balances_end"),
1096 ])
1097 self.m.balances.fetch_balances.assert_has_calls([
1098 mock.call(add_portfolio=True, checkpoint='end',
1099 log_tickers=True,
3a15ffc7 1100 add_usdt=True,
bb127bc8
IB
1101 tag='process_foo__0_print_balances_begin')
1102 ])
1103
1104 self.assertEqual(0, run_action.call_count)
1105
1106 self.m.reset_mock()
1107 with mock.patch.object(processor, "run_action") as run_action:
1108 step = processor.scenarios["sell_needed"][1]
1109
c682bdf4
IB
1110 processor.process_step("foo", step, {"foo":"bar"})
1111 self.m.balances.fetch_balances.assert_not_called()
1112
9b697863
IB
1113 self.m.reset_mock()
1114 with mock.patch.object(processor, "run_action") as run_action:
1115 step = processor.scenarios["print_balances"][0]
1116
1117 processor.process_step("foo", step, {"foo":"bar"})
1118 self.m.balances.fetch_balances.assert_called_once_with(
3a15ffc7 1119 add_portfolio=True, add_usdt=True, log_tickers=True,
9b697863
IB
1120 tag='process_foo__1_print_balances_begin')
1121
c682bdf4
IB
1122 def test_parse_args(self):
1123 processor = market.Processor(self.m)
1124
1125 with mock.patch.object(processor, "method_arguments") as method_arguments:
1126 method_mock = mock.Mock()
1127 method_arguments.return_value = [
1128 method_mock,
ef8fa5e5 1129 ["foo2", "foo", "foo3"]
c682bdf4 1130 ]
ef8fa5e5 1131 self.m.options = { "foo3": "coucou"}
c682bdf4
IB
1132 method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"})
1133
1134 self.assertEqual(method_mock, method)
ef8fa5e5 1135 self.assertEqual({"foo": "bar2", "foo2": "bar", "foo3": "coucou"}, args)
c682bdf4
IB
1136
1137 with mock.patch.object(processor, "method_arguments") as method_arguments:
1138 method_mock = mock.Mock()
1139 method_arguments.return_value = [
1140 method_mock,
1141 ["repartition"]
1142 ]
1143 method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {})
1144
1145 self.assertEqual(1, len(args["repartition"]))
1146 self.assertIn("BTC", args["repartition"])
1147
1148 with mock.patch.object(processor, "method_arguments") as method_arguments:
1149 method_mock = mock.Mock()
1150 method_arguments.return_value = [
1151 method_mock,
1152 ["repartition", "base_currency"]
1153 ]
1154 method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {"base_currency": "USDT"})
1155
1156 self.assertEqual(1, len(args["repartition"]))
1157 self.assertIn("USDT", args["repartition"])
1158
1159 with mock.patch.object(processor, "method_arguments") as method_arguments:
1160 method_mock = mock.Mock()
1161 method_arguments.return_value = [
1162 method_mock,
1163 ["repartition", "base_currency"]
1164 ]
1165 method, args = processor.parse_args("action", {"repartition": { "ETH": 1 }}, {"base_currency": "USDT"})
1166
1167 self.assertEqual(1, len(args["repartition"]))
1168 self.assertIn("ETH", args["repartition"])
1169
1170