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