2 import market
, store
, portfolio
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 repartition
.return_value
= {
134 "XEM": (D("0.75"), "long"),
135 "BTC": (D("0.25"), "long"),
137 def _get_ticker(c1
, c2
):
138 if c1
== "USDT" and c2
== "BTC":
139 return { "average": D("0.0001") }
140 if c1
== "XVG" and c2
== "BTC":
141 return { "average": D("0.000001") }
142 self
.fail("Should be called with {}, {}".format(c1
, c2
))
143 get_ticker
.side_effect
= _get_ticker
145 with mock
.patch("market.ReportStore"):
146 m
= market
.Market(self
.ccxt
, self
.market_args())
147 self
.ccxt
.fetch_all_balances
.return_value
= {
149 "exchange_free": D("10000.0"),
150 "exchange_used": D("0.0"),
151 "exchange_total": D("10000.0"),
152 "total": D("10000.0")
155 "exchange_free": D("10000.0"),
156 "exchange_used": D("0.0"),
157 "exchange_total": D("10000.0"),
158 "total": D("10000.0")
162 m
.balances
.fetch_balances(tag
="tag")
165 compute_trades
.assert_called()
167 call
= compute_trades
.call_args
168 self
.assertEqual(1, call
[0][0]["USDT"].value
)
169 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
170 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
171 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
172 m
.report
.log_stage
.assert_called_once_with("prepare_trades",
173 base_currency
='BTC', compute_value
='average',
174 liquidity
='medium', only
=None, repartition
=None)
175 m
.report
.log_balances
.assert_called_once_with(tag
="tag")
178 @mock.patch.object(market
.time
, "sleep")
179 @mock.patch.object(market
.TradeStore
, "all_orders")
180 def test_follow_orders(self
, all_orders
, time_mock
):
181 for debug
, sleep
in [
182 (False, None), (True, None),
183 (False, 12), (True, 12)]:
184 with self
.subTest(sleep
=sleep
, debug
=debug
), \
185 mock
.patch("market.ReportStore"):
186 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=debug
))
188 order_mock1
= mock
.Mock()
189 order_mock2
= mock
.Mock()
190 order_mock3
= mock
.Mock()
191 all_orders
.side_effect
= [
192 [order_mock1
, order_mock2
],
193 [order_mock1
, order_mock2
],
195 [order_mock1
, order_mock3
],
196 [order_mock1
, order_mock3
],
198 [order_mock1
, order_mock3
],
199 [order_mock1
, order_mock3
],
204 order_mock1
.get_status
.side_effect
= ["open", "open", "closed"]
205 order_mock2
.get_status
.side_effect
= ["open"]
206 order_mock3
.get_status
.side_effect
= ["open", "closed"]
208 order_mock1
.trade
= mock
.Mock()
209 order_mock2
.trade
= mock
.Mock()
210 order_mock3
.trade
= mock
.Mock()
212 m
.follow_orders(sleep
=sleep
)
214 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 1)
215 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 2)
216 self
.assertEqual(2, order_mock1
.trade
.update_order
.call_count
)
217 self
.assertEqual(3, order_mock1
.get_status
.call_count
)
219 order_mock2
.trade
.update_order
.assert_any_call(order_mock2
, 1)
220 self
.assertEqual(1, order_mock2
.trade
.update_order
.call_count
)
221 self
.assertEqual(1, order_mock2
.get_status
.call_count
)
223 order_mock3
.trade
.update_order
.assert_any_call(order_mock3
, 2)
224 self
.assertEqual(1, order_mock3
.trade
.update_order
.call_count
)
225 self
.assertEqual(2, order_mock3
.get_status
.call_count
)
226 m
.report
.log_stage
.assert_called()
228 mock
.call("follow_orders_begin"),
229 mock
.call("follow_orders_tick_1"),
230 mock
.call("follow_orders_tick_2"),
231 mock
.call("follow_orders_tick_3"),
232 mock
.call("follow_orders_end"),
234 m
.report
.log_stage
.assert_has_calls(calls
)
235 m
.report
.log_orders
.assert_called()
236 self
.assertEqual(3, m
.report
.log_orders
.call_count
)
238 mock
.call([order_mock1
, order_mock2
], tick
=1),
239 mock
.call([order_mock1
, order_mock3
], tick
=2),
240 mock
.call([order_mock1
, order_mock3
], tick
=3),
242 m
.report
.log_orders
.assert_has_calls(calls
)
244 mock
.call(order_mock1
, 3, finished
=True),
245 mock
.call(order_mock3
, 3, finished
=True),
247 m
.report
.log_order
.assert_has_calls(calls
)
251 m
.report
.log_debug_action
.assert_called_with("Set follow_orders tick to 7s")
252 time_mock
.assert_called_with(7)
254 time_mock
.assert_called_with(30)
256 time_mock
.assert_called_with(sleep
)
258 with self
.subTest("disappearing order"), \
259 mock
.patch("market.ReportStore"):
260 all_orders
.reset_mock()
261 m
= market
.Market(self
.ccxt
, self
.market_args())
263 order_mock1
= mock
.Mock()
264 order_mock2
= mock
.Mock()
265 all_orders
.side_effect
= [
266 [order_mock1
, order_mock2
],
267 [order_mock1
, order_mock2
],
269 [order_mock1
, order_mock2
],
270 [order_mock1
, order_mock2
],
275 order_mock1
.get_status
.side_effect
= ["open", "closed"]
276 order_mock2
.get_status
.side_effect
= ["open", "error_disappeared"]
278 order_mock1
.trade
= mock
.Mock()
279 trade_mock
= mock
.Mock()
280 order_mock2
.trade
= trade_mock
282 trade_mock
.tick_actions_recreate
.return_value
= "tick1"
286 trade_mock
.tick_actions_recreate
.assert_called_once_with(2)
287 trade_mock
.prepare_order
.assert_called_once_with(compute_value
="tick1")
288 m
.report
.log_error
.assert_called_once_with("follow_orders", message
=mock
.ANY
)
290 @mock.patch.object(market
.BalanceStore
, "fetch_balances")
291 def test_move_balance(self
, fetch_balances
):
292 for debug
in [True, False]:
293 with self
.subTest(debug
=debug
),\
294 mock
.patch("market.ReportStore"):
295 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=debug
))
297 value_from
= portfolio
.Amount("BTC", "1.0")
298 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
299 value_to
= portfolio
.Amount("BTC", "10.0")
300 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
302 value_from
= portfolio
.Amount("BTC", "0.0")
303 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
304 value_to
= portfolio
.Amount("BTC", "-3.0")
305 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
307 value_from
= portfolio
.Amount("USDT", "0.0")
308 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
309 value_to
= portfolio
.Amount("USDT", "-50.0")
310 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG", m
)
312 m
.trades
.all
= [trade1
, trade2
, trade3
]
313 balance1
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
314 balance2
= portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" }
)
315 balance3
= portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" }
)
316 m
.balances
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
320 fetch_balances
.assert_called_with()
321 m
.report
.log_move_balances
.assert_called_once()
324 m
.report
.log_debug_action
.assert_called()
325 self
.assertEqual(3, m
.report
.log_debug_action
.call_count
)
327 self
.ccxt
.transfer_balance
.assert_any_call("BTC", 3, "exchange", "margin")
328 self
.ccxt
.transfer_balance
.assert_any_call("USDT", 100, "exchange", "margin")
329 self
.ccxt
.transfer_balance
.assert_any_call("ETC", 5, "margin", "exchange")
331 m
.report
.reset_mock()
332 fetch_balances
.reset_mock()
333 with self
.subTest(retry
=True):
334 with mock
.patch("market.ReportStore"):
335 m
= market
.Market(self
.ccxt
, self
.market_args())
337 value_from
= portfolio
.Amount("BTC", "0.0")
338 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
339 value_to
= portfolio
.Amount("BTC", "-3.0")
340 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
342 m
.trades
.all
= [trade
]
343 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
344 m
.balances
.all
= {"BTC": balance}
346 m
.ccxt
.transfer_balance
.side_effect
= [
347 market
.ccxt
.RequestTimeout
,
348 market
.ccxt
.InvalidNonce
,
352 self
.ccxt
.transfer_balance
.assert_has_calls([
353 mock
.call("BTC", 3, "exchange", "margin"),
354 mock
.call("BTC", 3, "exchange", "margin"),
355 mock
.call("BTC", 3, "exchange", "margin")
357 self
.assertEqual(3, fetch_balances
.call_count
)
358 m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying", exception
=mock
.ANY
)
359 self
.assertEqual(3, m
.report
.log_move_balances
.call_count
)
361 self
.ccxt
.transfer_balance
.reset_mock()
362 m
.report
.reset_mock()
363 fetch_balances
.reset_mock()
364 with self
.subTest(retry
=True, too_much
=True):
365 with mock
.patch("market.ReportStore"):
366 m
= market
.Market(self
.ccxt
, self
.market_args())
368 value_from
= portfolio
.Amount("BTC", "0.0")
369 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
370 value_to
= portfolio
.Amount("BTC", "-3.0")
371 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
373 m
.trades
.all
= [trade
]
374 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
375 m
.balances
.all
= {"BTC": balance}
377 m
.ccxt
.transfer_balance
.side_effect
= [
378 market
.ccxt
.RequestTimeout
,
379 market
.ccxt
.RequestTimeout
,
380 market
.ccxt
.RequestTimeout
,
381 market
.ccxt
.RequestTimeout
,
382 market
.ccxt
.RequestTimeout
,
384 with self
.assertRaises(market
.ccxt
.RequestTimeout
):
387 self
.ccxt
.transfer_balance
.reset_mock()
388 m
.report
.reset_mock()
389 fetch_balances
.reset_mock()
390 with self
.subTest(retry
=True, partial_result
=True):
391 with mock
.patch("market.ReportStore"):
392 m
= market
.Market(self
.ccxt
, self
.market_args())
394 value_from
= portfolio
.Amount("BTC", "1.0")
395 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
396 value_to
= portfolio
.Amount("BTC", "10.0")
397 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
399 value_from
= portfolio
.Amount("BTC", "0.0")
400 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
401 value_to
= portfolio
.Amount("BTC", "-3.0")
402 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
404 value_from
= portfolio
.Amount("USDT", "0.0")
405 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
406 value_to
= portfolio
.Amount("USDT", "-50.0")
407 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG", m
)
409 m
.trades
.all
= [trade1
, trade2
, trade3
]
410 balance1
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
411 balance2
= portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" }
)
412 balance3
= portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" }
)
413 m
.balances
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
415 call_counts
= { "BTC": 0, "USDT": 0, "ETC": 0 }
416 def _transfer_balance(currency
, amount
, from_
, to_
):
417 call_counts
[currency
] += 1
418 if currency
== "BTC":
419 m
.balances
.all
["BTC"] = portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" }
)
420 if currency
== "USDT":
421 if call_counts
["USDT"] == 1:
422 raise market
.ccxt
.RequestTimeout
424 m
.balances
.all
["USDT"] = portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" }
)
425 if currency
== "ETC":
426 m
.balances
.all
["ETC"] = portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" }
)
429 m
.ccxt
.transfer_balance
.side_effect
= _transfer_balance
432 self
.ccxt
.transfer_balance
.assert_has_calls([
433 mock
.call("BTC", 3, "exchange", "margin"),
434 mock
.call('USDT', 100, 'exchange', 'margin'),
435 mock
.call('USDT', 100, 'exchange', 'margin'),
436 mock
.call("ETC", 5, "margin", "exchange")
438 self
.assertEqual(2, fetch_balances
.call_count
)
439 m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying", exception
=mock
.ANY
)
440 self
.assertEqual(2, m
.report
.log_move_balances
.call_count
)
441 m
.report
.log_move_balances
.asser_has_calls([
444 'BTC': portfolio
.Amount("BTC", "3"),
445 'USDT': portfolio
.Amount("USDT", "150"),
446 'ETC': portfolio
.Amount("ETC", "10"),
449 'BTC': portfolio
.Amount("BTC", "3"),
450 'USDT': portfolio
.Amount("USDT", "100"),
454 'BTC': portfolio
.Amount("BTC", "3"),
455 'USDT': portfolio
.Amount("USDT", "150"),
456 'ETC': portfolio
.Amount("ETC", "10"),
459 'BTC': portfolio
.Amount("BTC", "0"),
460 'USDT': portfolio
.Amount("USDT", "100"),
461 'ETC': portfolio
.Amount("ETC", "-5"),
466 def test_store_file_report(self
):
467 file_open
= mock
.mock_open()
468 m
= market
.Market(self
.ccxt
,
469 self
.market_args(report_path
="present"), user_id
=1)
470 with self
.subTest(file="present"),\
471 mock
.patch("market.open", file_open
),\
472 mock
.patch
.object(m
, "report") as report
,\
473 mock
.patch
.object(market
, "datetime") as time_mock
:
475 report
.print_logs
= [[time_mock
.now(), "Foo"], [time_mock
.now(), "Bar"]]
476 report
.to_json
.return_value
= "json_content"
478 m
.store_file_report(datetime
.datetime(2018, 2, 25))
480 file_open
.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
481 file_open
.assert_any_call("present/2018-02-25T00:00:00_1.log", "w")
482 file_open().write
.assert_any_call("json_content")
483 file_open().write
.assert_any_call("Foo\nBar")
484 m
.report
.to_json
.assert_called_once_with()
486 m
= market
.Market(self
.ccxt
, self
.market_args(report_path
="error"), user_id
=1)
487 with self
.subTest(file="error"),\
488 mock
.patch("market.open") as file_open
,\
489 mock
.patch
.object(m
, "report") as report
,\
490 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
491 file_open
.side_effect
= FileNotFoundError
493 m
.store_file_report(datetime
.datetime(2018, 2, 25))
495 self
.assertRegex(stdout_mock
.getvalue(), "impossible to store report file: FileNotFoundError;")
497 @mock.patch.object(market
, "psycopg2")
498 def test_store_database_report(self
, psycopg2
):
499 connect_mock
= mock
.Mock()
500 cursor_mock
= mock
.MagicMock()
502 connect_mock
.cursor
.return_value
= cursor_mock
503 psycopg2
.connect
.return_value
= connect_mock
504 m
= market
.Market(self
.ccxt
, self
.market_args(),
505 pg_config
={"config": "pg_config"}
, user_id
=1)
506 cursor_mock
.fetchone
.return_value
= [42]
508 with self
.subTest(error
=False),\
509 mock
.patch
.object(m
, "report") as report
:
510 report
.to_json_array
.return_value
= [
511 ("date1", "type1", "payload1"),
512 ("date2", "type2", "payload2"),
514 m
.store_database_report(datetime
.datetime(2018, 3, 24))
515 connect_mock
.assert_has_calls([
517 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)),
518 mock
.call
.cursor().fetchone(),
519 mock
.call
.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date1', 42, 'type1', 'payload1')),
520 mock
.call
.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date2', 42, 'type2', 'payload2')),
522 mock
.call
.cursor().close(),
526 connect_mock
.reset_mock()
527 with self
.subTest(error
=True),\
528 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
529 psycopg2
.connect
.side_effect
= Exception("Bouh")
530 m
.store_database_report(datetime
.datetime(2018, 3, 24))
531 self
.assertEqual(stdout_mock
.getvalue(), "impossible to store report to database: Exception; Bouh\n")
533 def test_store_report(self
):
534 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False), user_id
=1)
535 with self
.subTest(file=None, pg_config
=None),\
536 mock
.patch
.object(m
, "report") as report
,\
537 mock
.patch
.object(m
, "store_database_report") as db_report
,\
538 mock
.patch
.object(m
, "store_file_report") as file_report
:
540 report
.merge
.assert_called_with(store
.Portfolio
.report
)
542 file_report
.assert_not_called()
543 db_report
.assert_not_called()
546 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False, report_path
="present"), user_id
=1)
547 with self
.subTest(file="present", pg_config
=None),\
548 mock
.patch
.object(m
, "report") as report
,\
549 mock
.patch
.object(m
, "store_file_report") as file_report
,\
550 mock
.patch
.object(m
, "store_database_report") as db_report
,\
551 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
553 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
557 report
.merge
.assert_called_with(store
.Portfolio
.report
)
558 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
559 db_report
.assert_not_called()
562 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"), user_id
=1)
563 with self
.subTest(file="present", pg_config
=None, report_db
=True),\
564 mock
.patch
.object(m
, "report") as report
,\
565 mock
.patch
.object(m
, "store_file_report") as file_report
,\
566 mock
.patch
.object(m
, "store_database_report") as db_report
,\
567 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
569 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
573 report
.merge
.assert_called_with(store
.Portfolio
.report
)
574 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
575 db_report
.assert_not_called()
578 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True), pg_config
="present", user_id
=1)
579 with self
.subTest(file=None, pg_config
="present"),\
580 mock
.patch
.object(m
, "report") as report
,\
581 mock
.patch
.object(m
, "store_file_report") as file_report
,\
582 mock
.patch
.object(m
, "store_database_report") as db_report
,\
583 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
585 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
589 report
.merge
.assert_called_with(store
.Portfolio
.report
)
590 file_report
.assert_not_called()
591 db_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
594 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"),
595 pg_config
="pg_config", user_id
=1)
596 with self
.subTest(file="present", pg_config
="present"),\
597 mock
.patch
.object(m
, "report") as report
,\
598 mock
.patch
.object(m
, "store_file_report") as file_report
,\
599 mock
.patch
.object(m
, "store_database_report") as db_report
,\
600 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
602 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
606 report
.merge
.assert_called_with(store
.Portfolio
.report
)
607 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
608 db_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
610 def test_print_orders(self
):
611 m
= market
.Market(self
.ccxt
, self
.market_args())
612 with mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
613 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
614 mock
.patch
.object(m
, "prepare_trades") as prepare_trades
,\
615 mock
.patch
.object(m
.trades
, "prepare_orders") as prepare_orders
:
618 log_stage
.assert_called_with("print_orders")
619 fetch_balances
.assert_called_with(tag
="print_orders")
620 prepare_trades
.assert_called_with(base_currency
="BTC",
621 compute_value
="average")
622 prepare_orders
.assert_called_with(compute_value
="average")
624 def test_print_balances(self
):
625 m
= market
.Market(self
.ccxt
, self
.market_args())
627 with mock
.patch
.object(m
.balances
, "in_currency") as in_currency
,\
628 mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
629 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
630 mock
.patch
.object(m
.report
, "print_log") as print_log
:
632 in_currency
.return_value
= {
633 "BTC": portfolio
.Amount("BTC", "0.65"),
634 "ETH": portfolio
.Amount("BTC", "0.3"),
639 log_stage
.assert_called_once_with("print_balances")
640 fetch_balances
.assert_called_with()
641 print_log
.assert_has_calls([
643 mock
.call(portfolio
.Amount("BTC", "0.95")),
646 @mock.patch("market.Processor.process")
647 @mock.patch("market.ReportStore.log_error")
648 @mock.patch("market.Market.store_report")
649 def test_process(self
, store_report
, log_error
, process
):
650 m
= market
.Market(self
.ccxt
, self
.market_args())
651 with self
.subTest(before
=False, after
=False):
654 process
.assert_not_called()
655 store_report
.assert_called_once()
656 log_error
.assert_not_called()
659 log_error
.reset_mock()
660 store_report
.reset_mock()
661 with self
.subTest(before
=True, after
=False):
662 m
.process(None, before
=True)
664 process
.assert_called_once_with("sell_all", steps
="before")
665 store_report
.assert_called_once()
666 log_error
.assert_not_called()
669 log_error
.reset_mock()
670 store_report
.reset_mock()
671 with self
.subTest(before
=False, after
=True):
672 m
.process(None, after
=True)
674 process
.assert_called_once_with("sell_all", steps
="after")
675 store_report
.assert_called_once()
676 log_error
.assert_not_called()
679 log_error
.reset_mock()
680 store_report
.reset_mock()
681 with self
.subTest(before
=True, after
=True):
682 m
.process(None, before
=True, after
=True)
684 process
.assert_has_calls([
685 mock
.call("sell_all", steps
="before"),
686 mock
.call("sell_all", steps
="after"),
688 store_report
.assert_called_once()
689 log_error
.assert_not_called()
692 log_error
.reset_mock()
693 store_report
.reset_mock()
694 with self
.subTest(action
="print_balances"),\
695 mock
.patch
.object(m
, "print_balances") as print_balances
:
696 m
.process(["print_balances"])
698 process
.assert_not_called()
699 log_error
.assert_not_called()
700 store_report
.assert_called_once()
701 print_balances
.assert_called_once_with()
703 log_error
.reset_mock()
704 store_report
.reset_mock()
705 with self
.subTest(action
="print_orders"),\
706 mock
.patch
.object(m
, "print_orders") as print_orders
,\
707 mock
.patch
.object(m
, "print_balances") as print_balances
:
708 m
.process(["print_orders", "print_balances"])
710 process
.assert_not_called()
711 log_error
.assert_not_called()
712 store_report
.assert_called_once()
713 print_orders
.assert_called_once_with()
714 print_balances
.assert_called_once_with()
716 log_error
.reset_mock()
717 store_report
.reset_mock()
718 with self
.subTest(action
="unknown"):
719 m
.process(["unknown"])
720 log_error
.assert_called_once_with("market_process", message
="Unknown action unknown")
721 store_report
.assert_called_once()
723 log_error
.reset_mock()
724 store_report
.reset_mock()
725 with self
.subTest(unhandled_exception
=True):
726 process
.side_effect
= Exception("bouh")
728 m
.process(None, before
=True)
729 log_error
.assert_called_with("market_process", exception
=mock
.ANY
)
730 store_report
.assert_called_once()
733 @unittest.skipUnless("unit" in limits
, "Unit skipped")
734 class ProcessorTest(WebMockTestCase
):
735 def test_values(self
):
736 processor
= market
.Processor(self
.m
)
738 self
.assertEqual(self
.m
, processor
.market
)
740 def test_run_action(self
):
741 processor
= market
.Processor(self
.m
)
743 with mock
.patch
.object(processor
, "parse_args") as parse_args
:
744 method_mock
= mock
.Mock()
745 parse_args
.return_value
= [method_mock
, { "foo": "bar" }
]
747 processor
.run_action("foo", "bar", "baz")
749 parse_args
.assert_called_with("foo", "bar", "baz")
751 method_mock
.assert_called_with(foo
="bar")
753 processor
.run_action("wait_for_recent", "bar", "baz")
755 method_mock
.assert_called_with(foo
="bar")
757 def test_select_step(self
):
758 processor
= market
.Processor(self
.m
)
760 scenario
= processor
.scenarios
["sell_all"]
762 self
.assertEqual(scenario
, processor
.select_steps(scenario
, "all"))
763 self
.assertEqual(["all_sell"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "before"))))
764 self
.assertEqual(["wait", "all_buy"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "after"))))
765 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, 2))))
766 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "wait"))))
768 with self
.assertRaises(TypeError):
769 processor
.select_steps(scenario
, ["wait"])
771 @mock.patch("market.Processor.process_step")
772 def test_process(self
, process_step
):
773 processor
= market
.Processor(self
.m
)
775 processor
.process("sell_all", foo
="bar")
776 self
.assertEqual(3, process_step
.call_count
)
778 steps
= list(map(lambda x
: x
[1][1]["name"], process_step
.mock_calls
))
779 scenario_names
= list(map(lambda x
: x
[1][0], process_step
.mock_calls
))
780 kwargs
= list(map(lambda x
: x
[1][2], process_step
.mock_calls
))
781 self
.assertEqual(["all_sell", "wait", "all_buy"], steps
)
782 self
.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names
)
783 self
.assertEqual([{"foo":"bar"}
, {"foo":"bar"}
, {"foo":"bar"}
], kwargs
)
785 process_step
.reset_mock()
787 processor
.process("sell_needed", steps
=["before", "after"])
788 self
.assertEqual(3, process_step
.call_count
)
790 def test_method_arguments(self
):
791 ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
792 m
= market
.Market(ccxt
, self
.market_args())
794 processor
= market
.Processor(m
)
796 method
, arguments
= processor
.method_arguments("wait_for_recent")
797 self
.assertEqual(market
.Portfolio
.wait_for_recent
, method
)
798 self
.assertEqual(["delta", "poll"], arguments
)
800 method
, arguments
= processor
.method_arguments("prepare_trades")
801 self
.assertEqual(m
.prepare_trades
, method
)
802 self
.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments
)
804 method
, arguments
= processor
.method_arguments("prepare_orders")
805 self
.assertEqual(m
.trades
.prepare_orders
, method
)
807 method
, arguments
= processor
.method_arguments("move_balances")
808 self
.assertEqual(m
.move_balances
, method
)
810 method
, arguments
= processor
.method_arguments("run_orders")
811 self
.assertEqual(m
.trades
.run_orders
, method
)
813 method
, arguments
= processor
.method_arguments("follow_orders")
814 self
.assertEqual(m
.follow_orders
, method
)
816 method
, arguments
= processor
.method_arguments("close_trades")
817 self
.assertEqual(m
.trades
.close_trades
, method
)
819 def test_process_step(self
):
820 processor
= market
.Processor(self
.m
)
822 with mock
.patch
.object(processor
, "run_action") as run_action
:
823 step
= processor
.scenarios
["sell_needed"][1]
825 processor
.process_step("foo", step
, {"foo":"bar"}
)
827 self
.m
.report
.log_stage
.assert_has_calls([
828 mock
.call("process_foo__1_sell_begin"),
829 mock
.call("process_foo__1_sell_end"),
831 self
.m
.balances
.fetch_balances
.assert_has_calls([
832 mock
.call(tag
="process_foo__1_sell_begin"),
833 mock
.call(tag
="process_foo__1_sell_end"),
836 self
.assertEqual(5, run_action
.call_count
)
838 run_action
.assert_has_calls([
839 mock
.call('prepare_trades', {}, {'foo': 'bar'}
),
840 mock
.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}
, {'foo': 'bar'}
),
841 mock
.call('run_orders', {}, {'foo': 'bar'}
),
842 mock
.call('follow_orders', {}, {'foo': 'bar'}
),
843 mock
.call('close_trades', {}, {'foo': 'bar'}
),
847 with mock
.patch
.object(processor
, "run_action") as run_action
:
848 step
= processor
.scenarios
["sell_needed"][0]
850 processor
.process_step("foo", step
, {"foo":"bar"}
)
851 self
.m
.balances
.fetch_balances
.assert_not_called()
853 def test_parse_args(self
):
854 processor
= market
.Processor(self
.m
)
856 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
857 method_mock
= mock
.Mock()
858 method_arguments
.return_value
= [
862 method
, args
= processor
.parse_args("action", {"foo": "bar", "foo2": "bar"}
, {"foo": "bar2", "bla": "bla"}
)
864 self
.assertEqual(method_mock
, method
)
865 self
.assertEqual({"foo": "bar2", "foo2": "bar"}
, args
)
867 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
868 method_mock
= mock
.Mock()
869 method_arguments
.return_value
= [
873 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {})
875 self
.assertEqual(1, len(args
["repartition"]))
876 self
.assertIn("BTC", args
["repartition"])
878 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
879 method_mock
= mock
.Mock()
880 method_arguments
.return_value
= [
882 ["repartition", "base_currency"]
884 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {"base_currency": "USDT"}
)
886 self
.assertEqual(1, len(args
["repartition"]))
887 self
.assertIn("USDT", args
["repartition"])
889 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
890 method_mock
= mock
.Mock()
891 method_arguments
.return_value
= [
893 ["repartition", "base_currency"]
895 method
, args
= processor
.parse_args("action", {"repartition": { "ETH": 1 }
}, {"base_currency": "USDT"}
)
897 self
.assertEqual(1, len(args
["repartition"]))
898 self
.assertIn("ETH", args
["repartition"])