2 import market
, store
, portfolio
, dbs
5 @unittest.skipUnless("unit" in limits
, "Unit skipped")
6 class MarketTest(WebMockTestCase
):
10 self
.ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
12 def test_values(self
):
13 m
= market
.Market(self
.ccxt
, self
.market_args())
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
)
25 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=True))
26 self
.assertTrue(m
.debug
)
28 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=False))
29 self
.assertFalse(m
.debug
)
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()
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
47 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
, self
.market_args())
49 self
.assertEqual(self
.ccxt
, m
.ccxt
)
51 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
, self
.market_args(debug
=True))
52 self
.assertEqual(True, m
.debug
)
54 def test_get_tickers(self
):
55 self
.ccxt
.fetch_tickers
.side_effect
= [
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()
65 self
.assertIsNone(m
.get_tickers(refresh
=self
.time
.time()))
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 }
,
73 m
= market
.Market(self
.ccxt
, self
.market_args())
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"])
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"])
88 ticker
= m
.get_ticker("XVG", "XMR")
89 self
.assertIsNone(ticker
)
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"),
101 m
= market
.Market(self
.ccxt
, self
.market_args())
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"])
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"])
117 ticker
= m
.get_ticker("XVG", "XMR")
118 self
.assertIsNone(ticker
)
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()
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
):
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
143 repartition
.return_value
= {
144 "XEM": (D("0.75"), "long"),
145 "BTC": (D("0.25"), "long"),
147 m
= market
.Market(self
.ccxt
, self
.market_args())
148 self
.ccxt
.fetch_all_balances
.return_value
= {
150 "exchange_free": D("10000.0"),
151 "exchange_used": D("0.0"),
152 "exchange_total": D("10000.0"),
153 "total": D("10000.0")
156 "exchange_free": D("10000.0"),
157 "exchange_used": D("0.0"),
158 "exchange_total": D("10000.0"),
159 "total": D("10000.0")
163 m
.balances
.fetch_balances(tag
="tag")
166 compute_trades
.assert_called()
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',
175 available_balance_only
=False, liquidity
='medium',
176 only
=None, repartition
=None)
177 m
.report
.log_balances
.assert_called_once_with(tag
="tag", checkpoint
=None)
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 if c1
== "FOO" and c2
== "BTC":
190 return { "average": D("0.1") }
191 self
.fail("Should not be called with {}, {}".format(c1
, c2
))
192 get_ticker
.side_effect
= _get_ticker
194 repartition
.return_value
= {
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"),
201 m
= market
.Market(self
.ccxt
, self
.market_args())
202 self
.ccxt
.fetch_all_balances
.return_value
= {
204 "exchange_free": D("2.0"),
205 "exchange_used": D("0.0"),
206 "exchange_total": D("2.0"),
210 "exchange_free": D("5.0"),
211 "exchange_used": D("0.0"),
212 "exchange_total": D("5.0"),
216 "exchange_free": D("0.065"),
217 "exchange_used": D("0.02"),
218 "exchange_total": D("0.085"),
219 "margin_available": D("0.035"),
220 "margin_in_position": D("0.01"),
221 "margin_total": D("0.045"),
225 "exchange_free": D("1.0"),
226 "exchange_used": D("0.0"),
227 "exchange_total": D("1.0"),
231 "exchange_free": D("0.1"),
232 "exchange_used": D("0.0"),
233 "exchange_total": D("0.1"),
238 m
.balances
.fetch_balances(tag
="tag")
239 m
.prepare_trades(available_balance_only
=True)
240 compute_trades
.assert_called_once()
242 call
= compute_trades
.call_args
[0]
243 values_in_base
= call
[0]
244 new_repartition
= call
[1]
246 self
.assertEqual(portfolio
.Amount("BTC", "-0.025"),
247 new_repartition
["DOGE"] - values_in_base
["DOGE"])
249 new_repartition
["ETH"] - values_in_base
["ETH"])
250 self
.assertIsNone(new_repartition
.get("ZRC"))
251 self
.assertEqual(portfolio
.Amount("BTC", "0.025"),
252 new_repartition
["XMR"])
253 self
.assertEqual(portfolio
.Amount("BTC", "0.015"),
254 new_repartition
["FOO"] - values_in_base
["FOO"])
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
265 repartition
.return_value
= {
266 "BTC": (D("0.5"), "long"),
267 "ETH": (D("0.5"), "long"),
269 m
= market
.Market(self
.ccxt
, self
.market_args())
270 self
.ccxt
.fetch_all_balances
.return_value
= {
272 "exchange_free": D("1.0"),
273 "exchange_used": D("0.0"),
274 "exchange_total": D("1.0"),
279 m
.balances
.fetch_balances(tag
="tag")
280 m
.prepare_trades(available_balance_only
=True)
281 compute_trades
.assert_called_once()
283 call
= compute_trades
.call_args
[0]
284 values_in_base
= call
[0]
285 new_repartition
= call
[1]
287 self
.assertEqual(new_repartition
["ETH"], values_in_base
["ETH"])
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
))
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
],
306 [order_mock1
, order_mock3
],
307 [order_mock1
, order_mock3
],
309 [order_mock1
, order_mock3
],
310 [order_mock1
, order_mock3
],
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"]
319 order_mock1
.trade
= mock
.Mock()
320 order_mock2
.trade
= mock
.Mock()
321 order_mock3
.trade
= mock
.Mock()
323 m
.follow_orders(sleep
=sleep
)
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
)
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
)
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()
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"),
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
)
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),
353 m
.report
.log_orders
.assert_has_calls(calls
)
355 mock
.call(order_mock1
, 3, finished
=True),
356 mock
.call(order_mock3
, 3, finished
=True),
358 m
.report
.log_order
.assert_has_calls(calls
)
362 m
.report
.log_debug_action
.assert_called_with("Set follow_orders tick to 7s")
363 time_mock
.assert_called_with(7)
365 time_mock
.assert_called_with(30)
367 time_mock
.assert_called_with(sleep
)
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())
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
],
380 [order_mock1
, order_mock2
],
381 [order_mock1
, order_mock2
],
386 order_mock1
.get_status
.side_effect
= ["open", "closed"]
387 order_mock2
.get_status
.side_effect
= ["open", "error_disappeared"]
389 order_mock1
.trade
= mock
.Mock()
390 trade_mock
= mock
.Mock()
391 order_mock2
.trade
= trade_mock
393 trade_mock
.tick_actions_recreate
.return_value
= "tick1"
394 new_order_mock
= mock
.Mock()
395 trade_mock
.prepare_order
.return_value
= new_order_mock
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
)
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()
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())
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
],
416 [order_mock1
, order_mock2
],
417 [order_mock1
, order_mock2
],
422 order_mock1
.get_status
.side_effect
= ["open", "closed"]
423 order_mock2
.get_status
.side_effect
= ["open", "error_disappeared"]
425 order_mock1
.trade
= mock
.Mock()
426 trade_mock
= mock
.Mock()
427 order_mock2
.trade
= trade_mock
429 trade_mock
.tick_actions_recreate
.return_value
= "tick1"
430 trade_mock
.prepare_order
.return_value
= None
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)
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
))
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
)
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
)
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
)
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}
469 fetch_balances
.assert_called_with()
470 m
.report
.log_move_balances
.assert_called_once()
473 m
.report
.log_debug_action
.assert_called()
474 self
.assertEqual(3, m
.report
.log_debug_action
.call_count
)
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")
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())
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
)
491 m
.trades
.all
= [trade
]
492 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
493 m
.balances
.all
= {"BTC": balance}
495 m
.ccxt
.transfer_balance
.side_effect
= [
496 market
.ccxt
.RequestTimeout
,
497 market
.ccxt
.InvalidNonce
,
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")
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
)
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())
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
)
522 m
.trades
.all
= [trade
]
523 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
524 m
.balances
.all
= {"BTC": balance}
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
,
533 with self
.assertRaises(market
.ccxt
.RequestTimeout
):
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())
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
)
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
)
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
)
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}
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
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" }
)
578 m
.ccxt
.transfer_balance
.side_effect
= _transfer_balance
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")
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([
593 'BTC': portfolio
.Amount("BTC", "3"),
594 'USDT': portfolio
.Amount("USDT", "150"),
595 'ETC': portfolio
.Amount("ETC", "10"),
598 'BTC': portfolio
.Amount("BTC", "3"),
599 'USDT': portfolio
.Amount("USDT", "100"),
603 'BTC': portfolio
.Amount("BTC", "3"),
604 'USDT': portfolio
.Amount("USDT", "150"),
605 'ETC': portfolio
.Amount("ETC", "10"),
608 'BTC': portfolio
.Amount("BTC", "0"),
609 'USDT': portfolio
.Amount("USDT", "100"),
610 'ETC': portfolio
.Amount("ETC", "-5"),
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
:
624 report
.print_logs
= [[time_mock
.now(), "Foo"], [time_mock
.now(), "Bar"]]
625 report
.to_json
.return_value
= "json_content"
627 m
.store_file_report(datetime
.datetime(2018, 2, 25))
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()
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
642 m
.store_file_report(datetime
.datetime(2018, 2, 25))
644 self
.assertRegex(stdout_mock
.getvalue(), "impossible to store report file: FileNotFoundError;")
646 @mock.patch.object(dbs
, "psql")
647 def test_store_database_report(self
, psql
):
648 cursor_mock
= mock
.MagicMock()
650 psql
.cursor
.return_value
= cursor_mock
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]
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"),
661 m
.store_database_report(datetime
.datetime(2018, 3, 24))
662 psql
.assert_has_calls([
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')),
669 mock
.call
.cursor().close(),
672 with self
.subTest(error
=True),\
673 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
674 psql
.cursor
.side_effect
= Exception("Bouh")
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")
678 @mock.patch.object(dbs
, "redis")
679 def test_store_redis_report(self
, redis
):
680 m
= market
.Market(self
.ccxt
, self
.market_args(),
681 redis_config
={"config": "redis_config"}
, market_id
=1)
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"),
689 m
.store_redis_report(datetime
.datetime(2018, 3, 24))
690 redis
.assert_has_calls([
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"),
695 mock
.call
.set("/cryptoportfolio/1/latest/date", "2018-03-24T00:00:00"),
699 with self
.subTest(error
=True),\
700 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
701 redis
.set.side_effect
= Exception("Bouh")
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")
705 def test_store_report(self
):
706 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False), user_id
=1)
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
,\
710 mock
.patch
.object(m
, "report") as report
,\
711 mock
.patch
.object(m
, "store_database_report") as db_report
,\
712 mock
.patch
.object(m
, "store_redis_report") as redis_report
,\
713 mock
.patch
.object(m
, "store_file_report") as file_report
:
714 psql
.return_value
= False
715 redis
.return_value
= False
717 report
.merge
.assert_called_with(store
.Portfolio
.report
)
719 file_report
.assert_not_called()
720 db_report
.assert_not_called()
721 redis_report
.assert_not_called()
724 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False, report_path
="present"), user_id
=1)
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
,\
728 mock
.patch
.object(m
, "report") as report
,\
729 mock
.patch
.object(m
, "store_file_report") as file_report
,\
730 mock
.patch
.object(m
, "store_redis_report") as redis_report
,\
731 mock
.patch
.object(m
, "store_database_report") as db_report
,\
732 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
733 psql
.return_value
= False
734 redis
.return_value
= False
735 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
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()
742 redis_report
.assert_not_called()
745 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"), user_id
=1)
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
,\
749 mock
.patch
.object(m
, "report") as report
,\
750 mock
.patch
.object(m
, "store_file_report") as file_report
,\
751 mock
.patch
.object(m
, "store_redis_report") as redis_report
,\
752 mock
.patch
.object(m
, "store_database_report") as db_report
,\
753 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
754 psql
.return_value
= False
755 redis
.return_value
= False
756 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
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()
763 redis_report
.assert_not_called()
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
,\
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
:
775 psql
.return_value
= True
776 redis
.return_value
= False
777 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
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))
784 redis_report
.assert_not_called()
787 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"),
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
,\
792 mock
.patch
.object(m
, "report") as report
,\
793 mock
.patch
.object(m
, "store_file_report") as file_report
,\
794 mock
.patch
.object(m
, "store_redis_report") as redis_report
,\
795 mock
.patch
.object(m
, "store_database_report") as db_report
,\
796 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
797 psql
.return_value
= True
798 redis
.return_value
= False
799 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
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))
806 redis_report
.assert_not_called()
809 m
= market
.Market(self
.ccxt
, self
.market_args(report_redis
=False),
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
,\
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
:
819 psql
.return_value
= False
820 redis
.return_value
= True
821 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
824 redis_report
.assert_not_called()
827 m
= market
.Market(self
.ccxt
, self
.market_args(report_redis
=True),
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
,\
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
:
837 psql
.return_value
= False
838 redis
.return_value
= False
839 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
842 redis_report
.assert_not_called()
845 m
= market
.Market(self
.ccxt
, self
.market_args(report_redis
=True),
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
,\
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
:
855 psql
.return_value
= False
856 redis
.return_value
= True
857 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
860 redis_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
862 def test_print_tickers(self
):
863 m
= market
.Market(self
.ccxt
, self
.market_args())
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
:
870 in_currency
.return_value
= {
871 "BTC": portfolio
.Amount("BTC", "0.65"),
872 "ETH": portfolio
.Amount("BTC", "0.3"),
877 print_log
.assert_has_calls([
879 mock
.call(portfolio
.Amount("BTC", "0.95")),
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(), options
={"foo": "bar"}
)
887 with self
.subTest(actions
=[], before
=False, after
=False):
890 process
.assert_not_called()
891 store_report
.assert_called_once()
892 log_error
.assert_not_called()
895 log_error
.reset_mock()
896 store_report
.reset_mock()
897 with self
.subTest(before
=True, after
=False):
898 m
.process(["foo"], before
=True)
900 process
.assert_called_once_with("foo", options
={"foo": "bar"}
, steps
="before")
901 store_report
.assert_called_once()
902 log_error
.assert_not_called()
905 log_error
.reset_mock()
906 store_report
.reset_mock()
907 with self
.subTest(before
=False, after
=True):
908 m
.process(["sell_all"], after
=True)
910 process
.assert_called_once_with("sell_all", options
={"foo": "bar"}
, steps
="after")
911 store_report
.assert_called_once()
912 log_error
.assert_not_called()
915 log_error
.reset_mock()
916 store_report
.reset_mock()
917 with self
.subTest(before
=False, after
=False):
920 process
.assert_called_once_with("foo", options
={"foo": "bar"}
, steps
="all")
921 store_report
.assert_called_once()
922 log_error
.assert_not_called()
925 log_error
.reset_mock()
926 store_report
.reset_mock()
927 with self
.subTest(before
=True, after
=True):
928 m
.process(["sell_all"], before
=True, after
=True)
930 process
.assert_called_once_with("sell_all", options
={"foo": "bar"}
, steps
="all")
931 store_report
.assert_called_once()
932 log_error
.assert_not_called()
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
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()
944 m
.ccxt
.check_required_credentials
.side_effect
= True
946 log_error
.reset_mock()
947 store_report
.reset_mock()
948 with self
.subTest(unhandled_exception
=True):
949 process
.side_effect
= Exception("bouh")
951 m
.process(["some_action"], before
=True)
952 log_error
.assert_called_with("market_process", exception
=mock
.ANY
, message
=mock
.ANY
)
953 store_report
.assert_called_once()
956 @unittest.skipUnless("unit" in limits
, "Unit skipped")
957 class ProcessorTest(WebMockTestCase
):
958 def test_values(self
):
959 processor
= market
.Processor(self
.m
)
961 self
.assertEqual(self
.m
, processor
.market
)
963 def test_run_action(self
):
964 processor
= market
.Processor(self
.m
)
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" }
]
970 processor
.run_action("foo", "bar", "baz")
972 parse_args
.assert_called_with("foo", "bar", "baz")
974 method_mock
.assert_called_with(foo
="bar")
976 processor
.run_action("wait_for_recent", "bar", "baz")
978 method_mock
.assert_called_with(foo
="bar")
980 def test_select_step(self
):
981 processor
= market
.Processor(self
.m
)
983 scenario
= processor
.scenarios
["sell_all"]
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"))))
991 with self
.assertRaises(TypeError):
992 processor
.select_steps(scenario
, ["wait"])
994 def test_can_process(self
):
995 processor
= market
.Processor(self
.m
)
997 with self
.subTest(True):
998 self
.assertTrue(processor
.can_process("sell_all"))
1000 with self
.subTest(False):
1001 self
.assertFalse(processor
.can_process("unknown_action"))
1003 @mock.patch("market.Processor.process_step")
1004 def test_process(self
, process_step
):
1005 with self
.subTest("unknown action"):
1006 processor
= market
.Processor(self
.m
)
1007 with self
.assertRaises(TypeError):
1008 processor
.process("unknown_action")
1010 with self
.subTest("nominal case"):
1011 processor
= market
.Processor(self
.m
)
1013 processor
.process("sell_all", options
="bar")
1014 self
.assertEqual(3, process_step
.call_count
)
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(["bar", "bar", "bar"], kwargs
)
1023 process_step
.reset_mock()
1025 processor
.process("sell_needed", steps
=["before", "after"])
1026 self
.assertEqual(4, process_step
.call_count
)
1028 def test_method_arguments(self
):
1029 ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
1030 m
= market
.Market(ccxt
, self
.market_args())
1032 processor
= market
.Processor(m
)
1034 method
, arguments
= processor
.method_arguments("wait_for_recent")
1035 self
.assertEqual(market
.Portfolio
.wait_for_recent
, method
)
1036 self
.assertEqual(["delta"], arguments
)
1038 method
, arguments
= processor
.method_arguments("prepare_trades")
1039 self
.assertEqual(m
.prepare_trades
, method
)
1040 self
.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only', 'available_balance_only'], arguments
)
1042 method
, arguments
= processor
.method_arguments("prepare_orders")
1043 self
.assertEqual(m
.trades
.prepare_orders
, method
)
1045 method
, arguments
= processor
.method_arguments("move_balances")
1046 self
.assertEqual(m
.move_balances
, method
)
1048 method
, arguments
= processor
.method_arguments("run_orders")
1049 self
.assertEqual(m
.trades
.run_orders
, method
)
1051 method
, arguments
= processor
.method_arguments("follow_orders")
1052 self
.assertEqual(m
.follow_orders
, method
)
1054 method
, arguments
= processor
.method_arguments("close_trades")
1055 self
.assertEqual(m
.trades
.close_trades
, method
)
1057 method
, arguments
= processor
.method_arguments("print_tickers")
1058 self
.assertEqual(m
.print_tickers
, method
)
1060 method
, arguments
= processor
.method_arguments("fetch_balances")
1061 self
.assertEqual(m
.balances
.fetch_balances
, method
)
1063 def test_process_step(self
):
1064 processor
= market
.Processor(self
.m
)
1066 with mock
.patch
.object(processor
, "run_action") as run_action
:
1067 step
= processor
.scenarios
["sell_needed"][2]
1069 processor
.process_step("foo", step
, {"foo":"bar"}
)
1071 self
.m
.report
.log_stage
.assert_has_calls([
1072 mock
.call("process_foo__2_sell_begin"),
1073 mock
.call("process_foo__2_sell_end"),
1076 self
.assertEqual(7, run_action
.call_count
)
1078 run_action
.assert_has_calls([
1079 mock
.call('fetch_balances', {}, {'foo': 'bar', 'tag': 'process_foo__2_sell_begin'}
),
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 mock
.call('fetch_balances', {}, {'foo': 'bar', 'tag': 'process_foo__2_sell_end'}
),
1089 with mock
.patch
.object(processor
, "run_action") as run_action
:
1090 step
= processor
.scenarios
["sell_needed"][0]
1092 processor
.process_step("foo", step
, {"foo":"bar"}
)
1094 self
.m
.report
.log_stage
.assert_has_calls([
1095 mock
.call("process_foo__0_print_balances_begin"),
1096 mock
.call("process_foo__0_print_balances_end"),
1099 self
.assertEqual(1, run_action
.call_count
)
1100 run_action
.assert_has_calls([
1101 mock
.call('fetch_balances',
1102 {'checkpoint': 'end', 'log_tickers': True, 'add_usdt': True, 'add_portfolio': True}
,
1103 {'foo': 'bar', 'tag': 'process_foo__0_print_balances_begin'}
),
1107 with mock
.patch
.object(processor
, "run_action") as run_action
:
1108 step
= processor
.scenarios
["sell_needed"][1]
1110 processor
.process_step("foo", step
, {"foo":"bar"}
)
1111 self
.assertEqual(1, run_action
.call_count
)
1114 with mock
.patch
.object(processor
, "run_action") as run_action
:
1115 step
= processor
.scenarios
["print_balances"][0]
1117 processor
.process_step("foo", step
, {"foo":"bar"}
)
1118 run_action
.assert_has_calls([
1119 mock
.call('fetch_balances',
1120 {'log_tickers': True, 'add_usdt': True, 'add_portfolio': True}
,
1121 {'foo': 'bar', 'tag': 'process_foo__1_print_balances_begin'}
),
1124 def test_parse_args(self
):
1125 processor
= market
.Processor(self
.m
)
1127 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
1128 method_mock
= mock
.Mock()
1129 method_arguments
.return_value
= [
1133 method
, args
= processor
.parse_args("action", {"foo": "bar", "foo2": "bar"}
, {"foo": "bar2", "bla": "bla"}
)
1135 self
.assertEqual(method_mock
, method
)
1136 self
.assertEqual({"foo": "bar2", "foo2": "bar"}
, args
)
1138 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
1139 method_mock
= mock
.Mock()
1140 method_arguments
.return_value
= [
1144 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {})
1146 self
.assertEqual(1, len(args
["repartition"]))
1147 self
.assertIn("BTC", args
["repartition"])
1149 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
1150 method_mock
= mock
.Mock()
1151 method_arguments
.return_value
= [
1153 ["repartition", "base_currency"]
1155 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {"base_currency": "USDT"}
)
1157 self
.assertEqual(1, len(args
["repartition"]))
1158 self
.assertIn("USDT", args
["repartition"])
1160 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
1161 method_mock
= mock
.Mock()
1162 method_arguments
.return_value
= [
1164 ["repartition", "base_currency"]
1166 method
, args
= processor
.parse_args("action", {"repartition": { "ETH": 1 }
}, {"base_currency": "USDT"}
)
1168 self
.assertEqual(1, len(args
["repartition"]))
1169 self
.assertIn("ETH", args
["repartition"])