2 import market
, store
, portfolio
5 class MarketTest(WebMockTestCase
):
9 self
.ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
11 def test_values(self
):
12 m
= market
.Market(self
.ccxt
, self
.market_args())
14 self
.assertEqual(self
.ccxt
, m
.ccxt
)
15 self
.assertFalse(m
.debug
)
16 self
.assertIsInstance(m
.report
, market
.ReportStore
)
17 self
.assertIsInstance(m
.trades
, market
.TradeStore
)
18 self
.assertIsInstance(m
.balances
, market
.BalanceStore
)
19 self
.assertEqual(m
, m
.report
.market
)
20 self
.assertEqual(m
, m
.trades
.market
)
21 self
.assertEqual(m
, m
.balances
.market
)
22 self
.assertEqual(m
, m
.ccxt
._market
)
24 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=True))
25 self
.assertTrue(m
.debug
)
27 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=False))
28 self
.assertFalse(m
.debug
)
30 with mock
.patch("market.ReportStore") as report_store
:
31 with self
.subTest(quiet
=False):
32 m
= market
.Market(self
.ccxt
, self
.market_args(quiet
=False))
33 report_store
.assert_called_with(m
, verbose_print
=True)
34 report_store().log_market
.assert_called_once()
35 report_store
.reset_mock()
36 with self
.subTest(quiet
=True):
37 m
= market
.Market(self
.ccxt
, self
.market_args(quiet
=True))
38 report_store
.assert_called_with(m
, verbose_print
=False)
39 report_store().log_market
.assert_called_once()
41 @mock.patch("market.ccxt")
42 def test_from_config(self
, ccxt
):
43 with mock
.patch("market.ReportStore"):
44 ccxt
.poloniexE
.return_value
= self
.ccxt
46 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
, self
.market_args())
48 self
.assertEqual(self
.ccxt
, m
.ccxt
)
50 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
, self
.market_args(debug
=True))
51 self
.assertEqual(True, m
.debug
)
53 def test_get_tickers(self
):
54 self
.ccxt
.fetch_tickers
.side_effect
= [
59 m
= market
.Market(self
.ccxt
, self
.market_args())
60 self
.assertEqual("tickers", m
.get_tickers())
61 self
.assertEqual("tickers", m
.get_tickers())
62 self
.ccxt
.fetch_tickers
.assert_called_once()
64 self
.assertIsNone(m
.get_tickers(refresh
=self
.time
.time()))
66 def test_get_ticker(self
):
67 with self
.subTest(get_tickers
=True):
68 self
.ccxt
.fetch_tickers
.return_value
= {
69 "ETH/ETC": { "bid": 1, "ask": 3 }
,
70 "XVG/ETH": { "bid": 10, "ask": 40 }
,
72 m
= market
.Market(self
.ccxt
, self
.market_args())
74 ticker
= m
.get_ticker("ETH", "ETC")
75 self
.assertEqual(1, ticker
["bid"])
76 self
.assertEqual(3, ticker
["ask"])
77 self
.assertEqual(2, ticker
["average"])
78 self
.assertFalse(ticker
["inverted"])
80 ticker
= m
.get_ticker("ETH", "XVG")
81 self
.assertEqual(0.0625, ticker
["average"])
82 self
.assertTrue(ticker
["inverted"])
83 self
.assertIn("original", ticker
)
84 self
.assertEqual(10, ticker
["original"]["bid"])
85 self
.assertEqual(25, ticker
["original"]["average"])
87 ticker
= m
.get_ticker("XVG", "XMR")
88 self
.assertIsNone(ticker
)
90 with self
.subTest(get_tickers
=False):
91 self
.ccxt
.fetch_tickers
.return_value
= None
92 self
.ccxt
.fetch_ticker
.side_effect
= [
93 { "bid": 1, "ask": 3 }
,
94 market
.ExchangeError("foo"),
95 { "bid": 10, "ask": 40 }
,
96 market
.ExchangeError("foo"),
97 market
.ExchangeError("foo"),
100 m
= market
.Market(self
.ccxt
, self
.market_args())
102 ticker
= m
.get_ticker("ETH", "ETC")
103 self
.ccxt
.fetch_ticker
.assert_called_with("ETH/ETC")
104 self
.assertEqual(1, ticker
["bid"])
105 self
.assertEqual(3, ticker
["ask"])
106 self
.assertEqual(2, ticker
["average"])
107 self
.assertFalse(ticker
["inverted"])
109 ticker
= m
.get_ticker("ETH", "XVG")
110 self
.assertEqual(0.0625, ticker
["average"])
111 self
.assertTrue(ticker
["inverted"])
112 self
.assertIn("original", ticker
)
113 self
.assertEqual(10, ticker
["original"]["bid"])
114 self
.assertEqual(25, ticker
["original"]["average"])
116 ticker
= m
.get_ticker("XVG", "XMR")
117 self
.assertIsNone(ticker
)
119 def test_fetch_fees(self
):
120 m
= market
.Market(self
.ccxt
, self
.market_args())
121 self
.ccxt
.fetch_fees
.return_value
= "Foo"
122 self
.assertEqual("Foo", m
.fetch_fees())
123 self
.ccxt
.fetch_fees
.assert_called_once()
124 self
.ccxt
.reset_mock()
125 self
.assertEqual("Foo", m
.fetch_fees())
126 self
.ccxt
.fetch_fees
.assert_not_called()
128 @mock.patch.object(market
.Portfolio
, "repartition")
129 @mock.patch.object(market
.Market
, "get_ticker")
130 @mock.patch.object(market
.TradeStore
, "compute_trades")
131 def test_prepare_trades(self
, compute_trades
, get_ticker
, repartition
):
132 repartition
.return_value
= {
133 "XEM": (D("0.75"), "long"),
134 "BTC": (D("0.25"), "long"),
136 def _get_ticker(c1
, c2
):
137 if c1
== "USDT" and c2
== "BTC":
138 return { "average": D("0.0001") }
139 if c1
== "XVG" and c2
== "BTC":
140 return { "average": D("0.000001") }
141 self
.fail("Should be called with {}, {}".format(c1
, c2
))
142 get_ticker
.side_effect
= _get_ticker
144 with mock
.patch("market.ReportStore"):
145 m
= market
.Market(self
.ccxt
, self
.market_args())
146 self
.ccxt
.fetch_all_balances
.return_value
= {
148 "exchange_free": D("10000.0"),
149 "exchange_used": D("0.0"),
150 "exchange_total": D("10000.0"),
151 "total": D("10000.0")
154 "exchange_free": D("10000.0"),
155 "exchange_used": D("0.0"),
156 "exchange_total": D("10000.0"),
157 "total": D("10000.0")
161 m
.balances
.fetch_balances(tag
="tag")
164 compute_trades
.assert_called()
166 call
= compute_trades
.call_args
167 self
.assertEqual(1, call
[0][0]["USDT"].value
)
168 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
169 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
170 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
171 m
.report
.log_stage
.assert_called_once_with("prepare_trades",
172 base_currency
='BTC', compute_value
='average',
173 liquidity
='medium', only
=None, repartition
=None)
174 m
.report
.log_balances
.assert_called_once_with(tag
="tag")
177 @mock.patch.object(market
.time
, "sleep")
178 @mock.patch.object(market
.TradeStore
, "all_orders")
179 def test_follow_orders(self
, all_orders
, time_mock
):
180 for debug
, sleep
in [
181 (False, None), (True, None),
182 (False, 12), (True, 12)]:
183 with self
.subTest(sleep
=sleep
, debug
=debug
), \
184 mock
.patch("market.ReportStore"):
185 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=debug
))
187 order_mock1
= mock
.Mock()
188 order_mock2
= mock
.Mock()
189 order_mock3
= mock
.Mock()
190 all_orders
.side_effect
= [
191 [order_mock1
, order_mock2
],
192 [order_mock1
, order_mock2
],
194 [order_mock1
, order_mock3
],
195 [order_mock1
, order_mock3
],
197 [order_mock1
, order_mock3
],
198 [order_mock1
, order_mock3
],
203 order_mock1
.get_status
.side_effect
= ["open", "open", "closed"]
204 order_mock2
.get_status
.side_effect
= ["open"]
205 order_mock3
.get_status
.side_effect
= ["open", "closed"]
207 order_mock1
.trade
= mock
.Mock()
208 order_mock2
.trade
= mock
.Mock()
209 order_mock3
.trade
= mock
.Mock()
211 m
.follow_orders(sleep
=sleep
)
213 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 1)
214 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 2)
215 self
.assertEqual(2, order_mock1
.trade
.update_order
.call_count
)
216 self
.assertEqual(3, order_mock1
.get_status
.call_count
)
218 order_mock2
.trade
.update_order
.assert_any_call(order_mock2
, 1)
219 self
.assertEqual(1, order_mock2
.trade
.update_order
.call_count
)
220 self
.assertEqual(1, order_mock2
.get_status
.call_count
)
222 order_mock3
.trade
.update_order
.assert_any_call(order_mock3
, 2)
223 self
.assertEqual(1, order_mock3
.trade
.update_order
.call_count
)
224 self
.assertEqual(2, order_mock3
.get_status
.call_count
)
225 m
.report
.log_stage
.assert_called()
227 mock
.call("follow_orders_begin"),
228 mock
.call("follow_orders_tick_1"),
229 mock
.call("follow_orders_tick_2"),
230 mock
.call("follow_orders_tick_3"),
231 mock
.call("follow_orders_end"),
233 m
.report
.log_stage
.assert_has_calls(calls
)
234 m
.report
.log_orders
.assert_called()
235 self
.assertEqual(3, m
.report
.log_orders
.call_count
)
237 mock
.call([order_mock1
, order_mock2
], tick
=1),
238 mock
.call([order_mock1
, order_mock3
], tick
=2),
239 mock
.call([order_mock1
, order_mock3
], tick
=3),
241 m
.report
.log_orders
.assert_has_calls(calls
)
243 mock
.call(order_mock1
, 3, finished
=True),
244 mock
.call(order_mock3
, 3, finished
=True),
246 m
.report
.log_order
.assert_has_calls(calls
)
250 m
.report
.log_debug_action
.assert_called_with("Set follow_orders tick to 7s")
251 time_mock
.assert_called_with(7)
253 time_mock
.assert_called_with(30)
255 time_mock
.assert_called_with(sleep
)
257 with self
.subTest("disappearing order"), \
258 mock
.patch("market.ReportStore"):
259 all_orders
.reset_mock()
260 m
= market
.Market(self
.ccxt
, self
.market_args())
262 order_mock1
= mock
.Mock()
263 order_mock2
= mock
.Mock()
264 all_orders
.side_effect
= [
265 [order_mock1
, order_mock2
],
266 [order_mock1
, order_mock2
],
268 [order_mock1
, order_mock2
],
269 [order_mock1
, order_mock2
],
274 order_mock1
.get_status
.side_effect
= ["open", "closed"]
275 order_mock2
.get_status
.side_effect
= ["open", "error_disappeared"]
277 order_mock1
.trade
= mock
.Mock()
278 trade_mock
= mock
.Mock()
279 order_mock2
.trade
= trade_mock
281 trade_mock
.tick_actions_recreate
.return_value
= "tick1"
285 trade_mock
.tick_actions_recreate
.assert_called_once_with(2)
286 trade_mock
.prepare_order
.assert_called_once_with(compute_value
="tick1")
287 m
.report
.log_error
.assert_called_once_with("follow_orders", message
=mock
.ANY
)
289 @mock.patch.object(market
.BalanceStore
, "fetch_balances")
290 def test_move_balance(self
, fetch_balances
):
291 for debug
in [True, False]:
292 with self
.subTest(debug
=debug
),\
293 mock
.patch("market.ReportStore"):
294 m
= market
.Market(self
.ccxt
, self
.market_args(debug
=debug
))
296 value_from
= portfolio
.Amount("BTC", "1.0")
297 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
298 value_to
= portfolio
.Amount("BTC", "10.0")
299 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
301 value_from
= portfolio
.Amount("BTC", "0.0")
302 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
303 value_to
= portfolio
.Amount("BTC", "-3.0")
304 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
306 value_from
= portfolio
.Amount("USDT", "0.0")
307 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
308 value_to
= portfolio
.Amount("USDT", "-50.0")
309 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG", m
)
311 m
.trades
.all
= [trade1
, trade2
, trade3
]
312 balance1
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
313 balance2
= portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" }
)
314 balance3
= portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" }
)
315 m
.balances
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
319 fetch_balances
.assert_called_with()
320 m
.report
.log_move_balances
.assert_called_once()
323 m
.report
.log_debug_action
.assert_called()
324 self
.assertEqual(3, m
.report
.log_debug_action
.call_count
)
326 self
.ccxt
.transfer_balance
.assert_any_call("BTC", 3, "exchange", "margin")
327 self
.ccxt
.transfer_balance
.assert_any_call("USDT", 100, "exchange", "margin")
328 self
.ccxt
.transfer_balance
.assert_any_call("ETC", 5, "margin", "exchange")
330 m
.report
.reset_mock()
331 fetch_balances
.reset_mock()
332 with self
.subTest(retry
=True):
333 with mock
.patch("market.ReportStore"):
334 m
= market
.Market(self
.ccxt
, self
.market_args())
336 value_from
= portfolio
.Amount("BTC", "0.0")
337 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
338 value_to
= portfolio
.Amount("BTC", "-3.0")
339 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
341 m
.trades
.all
= [trade
]
342 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
343 m
.balances
.all
= {"BTC": balance}
345 m
.ccxt
.transfer_balance
.side_effect
= [
346 market
.ccxt
.RequestTimeout
,
347 market
.ccxt
.InvalidNonce
,
351 self
.ccxt
.transfer_balance
.assert_has_calls([
352 mock
.call("BTC", 3, "exchange", "margin"),
353 mock
.call("BTC", 3, "exchange", "margin"),
354 mock
.call("BTC", 3, "exchange", "margin")
356 self
.assertEqual(3, fetch_balances
.call_count
)
357 m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying", exception
=mock
.ANY
)
358 self
.assertEqual(3, m
.report
.log_move_balances
.call_count
)
360 self
.ccxt
.transfer_balance
.reset_mock()
361 m
.report
.reset_mock()
362 fetch_balances
.reset_mock()
363 with self
.subTest(retry
=True, too_much
=True):
364 with mock
.patch("market.ReportStore"):
365 m
= market
.Market(self
.ccxt
, self
.market_args())
367 value_from
= portfolio
.Amount("BTC", "0.0")
368 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
369 value_to
= portfolio
.Amount("BTC", "-3.0")
370 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
372 m
.trades
.all
= [trade
]
373 balance
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
374 m
.balances
.all
= {"BTC": balance}
376 m
.ccxt
.transfer_balance
.side_effect
= [
377 market
.ccxt
.RequestTimeout
,
378 market
.ccxt
.RequestTimeout
,
379 market
.ccxt
.RequestTimeout
,
380 market
.ccxt
.RequestTimeout
,
381 market
.ccxt
.RequestTimeout
,
383 with self
.assertRaises(market
.ccxt
.RequestTimeout
):
386 self
.ccxt
.transfer_balance
.reset_mock()
387 m
.report
.reset_mock()
388 fetch_balances
.reset_mock()
389 with self
.subTest(retry
=True, partial_result
=True):
390 with mock
.patch("market.ReportStore"):
391 m
= market
.Market(self
.ccxt
, self
.market_args())
393 value_from
= portfolio
.Amount("BTC", "1.0")
394 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
395 value_to
= portfolio
.Amount("BTC", "10.0")
396 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
398 value_from
= portfolio
.Amount("BTC", "0.0")
399 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
400 value_to
= portfolio
.Amount("BTC", "-3.0")
401 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
403 value_from
= portfolio
.Amount("USDT", "0.0")
404 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
405 value_to
= portfolio
.Amount("USDT", "-50.0")
406 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG", m
)
408 m
.trades
.all
= [trade1
, trade2
, trade3
]
409 balance1
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
410 balance2
= portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" }
)
411 balance3
= portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" }
)
412 m
.balances
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
414 call_counts
= { "BTC": 0, "USDT": 0, "ETC": 0 }
415 def _transfer_balance(currency
, amount
, from_
, to_
):
416 call_counts
[currency
] += 1
417 if currency
== "BTC":
418 m
.balances
.all
["BTC"] = portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" }
)
419 if currency
== "USDT":
420 if call_counts
["USDT"] == 1:
421 raise market
.ccxt
.RequestTimeout
423 m
.balances
.all
["USDT"] = portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" }
)
424 if currency
== "ETC":
425 m
.balances
.all
["ETC"] = portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" }
)
428 m
.ccxt
.transfer_balance
.side_effect
= _transfer_balance
431 self
.ccxt
.transfer_balance
.assert_has_calls([
432 mock
.call("BTC", 3, "exchange", "margin"),
433 mock
.call('USDT', 100, 'exchange', 'margin'),
434 mock
.call('USDT', 100, 'exchange', 'margin'),
435 mock
.call("ETC", 5, "margin", "exchange")
437 self
.assertEqual(2, fetch_balances
.call_count
)
438 m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying", exception
=mock
.ANY
)
439 self
.assertEqual(2, m
.report
.log_move_balances
.call_count
)
440 m
.report
.log_move_balances
.asser_has_calls([
443 'BTC': portfolio
.Amount("BTC", "3"),
444 'USDT': portfolio
.Amount("USDT", "150"),
445 'ETC': portfolio
.Amount("ETC", "10"),
448 'BTC': portfolio
.Amount("BTC", "3"),
449 'USDT': portfolio
.Amount("USDT", "100"),
453 'BTC': portfolio
.Amount("BTC", "3"),
454 'USDT': portfolio
.Amount("USDT", "150"),
455 'ETC': portfolio
.Amount("ETC", "10"),
458 'BTC': portfolio
.Amount("BTC", "0"),
459 'USDT': portfolio
.Amount("USDT", "100"),
460 'ETC': portfolio
.Amount("ETC", "-5"),
465 def test_store_file_report(self
):
466 file_open
= mock
.mock_open()
467 m
= market
.Market(self
.ccxt
,
468 self
.market_args(report_path
="present"), user_id
=1)
469 with self
.subTest(file="present"),\
470 mock
.patch("market.open", file_open
),\
471 mock
.patch
.object(m
, "report") as report
,\
472 mock
.patch
.object(market
, "datetime") as time_mock
:
474 report
.print_logs
= [[time_mock
.now(), "Foo"], [time_mock
.now(), "Bar"]]
475 report
.to_json
.return_value
= "json_content"
477 m
.store_file_report(datetime
.datetime(2018, 2, 25))
479 file_open
.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
480 file_open
.assert_any_call("present/2018-02-25T00:00:00_1.log", "w")
481 file_open().write
.assert_any_call("json_content")
482 file_open().write
.assert_any_call("Foo\nBar")
483 m
.report
.to_json
.assert_called_once_with()
485 m
= market
.Market(self
.ccxt
, self
.market_args(report_path
="error"), user_id
=1)
486 with self
.subTest(file="error"),\
487 mock
.patch("market.open") as file_open
,\
488 mock
.patch
.object(m
, "report") as report
,\
489 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
490 file_open
.side_effect
= FileNotFoundError
492 m
.store_file_report(datetime
.datetime(2018, 2, 25))
494 self
.assertRegex(stdout_mock
.getvalue(), "impossible to store report file: FileNotFoundError;")
496 @mock.patch.object(market
, "psycopg2")
497 def test_store_database_report(self
, psycopg2
):
498 connect_mock
= mock
.Mock()
499 cursor_mock
= mock
.MagicMock()
501 connect_mock
.cursor
.return_value
= cursor_mock
502 psycopg2
.connect
.return_value
= connect_mock
503 m
= market
.Market(self
.ccxt
, self
.market_args(),
504 pg_config
={"config": "pg_config"}
, user_id
=1)
505 cursor_mock
.fetchone
.return_value
= [42]
507 with self
.subTest(error
=False),\
508 mock
.patch
.object(m
, "report") as report
:
509 report
.to_json_array
.return_value
= [
510 ("date1", "type1", "payload1"),
511 ("date2", "type2", "payload2"),
513 m
.store_database_report(datetime
.datetime(2018, 3, 24))
514 connect_mock
.assert_has_calls([
516 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)),
517 mock
.call
.cursor().fetchone(),
518 mock
.call
.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date1', 42, 'type1', 'payload1')),
519 mock
.call
.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date2', 42, 'type2', 'payload2')),
521 mock
.call
.cursor().close(),
525 connect_mock
.reset_mock()
526 with self
.subTest(error
=True),\
527 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
528 psycopg2
.connect
.side_effect
= Exception("Bouh")
529 m
.store_database_report(datetime
.datetime(2018, 3, 24))
530 self
.assertEqual(stdout_mock
.getvalue(), "impossible to store report to database: Exception; Bouh\n")
532 def test_store_report(self
):
533 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False), user_id
=1)
534 with self
.subTest(file=None, pg_config
=None),\
535 mock
.patch
.object(m
, "report") as report
,\
536 mock
.patch
.object(m
, "store_database_report") as db_report
,\
537 mock
.patch
.object(m
, "store_file_report") as file_report
:
539 report
.merge
.assert_called_with(store
.Portfolio
.report
)
541 file_report
.assert_not_called()
542 db_report
.assert_not_called()
545 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=False, report_path
="present"), user_id
=1)
546 with self
.subTest(file="present", pg_config
=None),\
547 mock
.patch
.object(m
, "report") as report
,\
548 mock
.patch
.object(m
, "store_file_report") as file_report
,\
549 mock
.patch
.object(m
, "store_database_report") as db_report
,\
550 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
552 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
556 report
.merge
.assert_called_with(store
.Portfolio
.report
)
557 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
558 db_report
.assert_not_called()
561 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"), user_id
=1)
562 with self
.subTest(file="present", pg_config
=None, report_db
=True),\
563 mock
.patch
.object(m
, "report") as report
,\
564 mock
.patch
.object(m
, "store_file_report") as file_report
,\
565 mock
.patch
.object(m
, "store_database_report") as db_report
,\
566 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
568 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
572 report
.merge
.assert_called_with(store
.Portfolio
.report
)
573 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
574 db_report
.assert_not_called()
577 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True), pg_config
="present", user_id
=1)
578 with self
.subTest(file=None, pg_config
="present"),\
579 mock
.patch
.object(m
, "report") as report
,\
580 mock
.patch
.object(m
, "store_file_report") as file_report
,\
581 mock
.patch
.object(m
, "store_database_report") as db_report
,\
582 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
584 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
588 report
.merge
.assert_called_with(store
.Portfolio
.report
)
589 file_report
.assert_not_called()
590 db_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
593 m
= market
.Market(self
.ccxt
, self
.market_args(report_db
=True, report_path
="present"),
594 pg_config
="pg_config", user_id
=1)
595 with self
.subTest(file="present", pg_config
="present"),\
596 mock
.patch
.object(m
, "report") as report
,\
597 mock
.patch
.object(m
, "store_file_report") as file_report
,\
598 mock
.patch
.object(m
, "store_database_report") as db_report
,\
599 mock
.patch
.object(market
.datetime
, "datetime") as time_mock
:
601 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
605 report
.merge
.assert_called_with(store
.Portfolio
.report
)
606 file_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
607 db_report
.assert_called_once_with(datetime
.datetime(2018, 2, 25))
609 def test_print_orders(self
):
610 m
= market
.Market(self
.ccxt
, self
.market_args())
611 with mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
612 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
613 mock
.patch
.object(m
, "prepare_trades") as prepare_trades
,\
614 mock
.patch
.object(m
.trades
, "prepare_orders") as prepare_orders
:
617 log_stage
.assert_called_with("print_orders")
618 fetch_balances
.assert_called_with(tag
="print_orders")
619 prepare_trades
.assert_called_with(base_currency
="BTC",
620 compute_value
="average")
621 prepare_orders
.assert_called_with(compute_value
="average")
623 def test_print_balances(self
):
624 m
= market
.Market(self
.ccxt
, self
.market_args())
626 with mock
.patch
.object(m
.balances
, "in_currency") as in_currency
,\
627 mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
628 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
629 mock
.patch
.object(m
.report
, "print_log") as print_log
:
631 in_currency
.return_value
= {
632 "BTC": portfolio
.Amount("BTC", "0.65"),
633 "ETH": portfolio
.Amount("BTC", "0.3"),
638 log_stage
.assert_called_once_with("print_balances")
639 fetch_balances
.assert_called_with()
640 print_log
.assert_has_calls([
642 mock
.call(portfolio
.Amount("BTC", "0.95")),
645 @mock.patch("market.Processor.process")
646 @mock.patch("market.ReportStore.log_error")
647 @mock.patch("market.Market.store_report")
648 def test_process(self
, store_report
, log_error
, process
):
649 m
= market
.Market(self
.ccxt
, self
.market_args())
650 with self
.subTest(before
=False, after
=False):
653 process
.assert_not_called()
654 store_report
.assert_called_once()
655 log_error
.assert_not_called()
658 log_error
.reset_mock()
659 store_report
.reset_mock()
660 with self
.subTest(before
=True, after
=False):
661 m
.process(None, before
=True)
663 process
.assert_called_once_with("sell_all", steps
="before")
664 store_report
.assert_called_once()
665 log_error
.assert_not_called()
668 log_error
.reset_mock()
669 store_report
.reset_mock()
670 with self
.subTest(before
=False, after
=True):
671 m
.process(None, after
=True)
673 process
.assert_called_once_with("sell_all", steps
="after")
674 store_report
.assert_called_once()
675 log_error
.assert_not_called()
678 log_error
.reset_mock()
679 store_report
.reset_mock()
680 with self
.subTest(before
=True, after
=True):
681 m
.process(None, before
=True, after
=True)
683 process
.assert_has_calls([
684 mock
.call("sell_all", steps
="before"),
685 mock
.call("sell_all", steps
="after"),
687 store_report
.assert_called_once()
688 log_error
.assert_not_called()
691 log_error
.reset_mock()
692 store_report
.reset_mock()
693 with self
.subTest(action
="print_balances"),\
694 mock
.patch
.object(m
, "print_balances") as print_balances
:
695 m
.process(["print_balances"])
697 process
.assert_not_called()
698 log_error
.assert_not_called()
699 store_report
.assert_called_once()
700 print_balances
.assert_called_once_with()
702 log_error
.reset_mock()
703 store_report
.reset_mock()
704 with self
.subTest(action
="print_orders"),\
705 mock
.patch
.object(m
, "print_orders") as print_orders
,\
706 mock
.patch
.object(m
, "print_balances") as print_balances
:
707 m
.process(["print_orders", "print_balances"])
709 process
.assert_not_called()
710 log_error
.assert_not_called()
711 store_report
.assert_called_once()
712 print_orders
.assert_called_once_with()
713 print_balances
.assert_called_once_with()
715 log_error
.reset_mock()
716 store_report
.reset_mock()
717 with self
.subTest(action
="unknown"):
718 m
.process(["unknown"])
719 log_error
.assert_called_once_with("market_process", message
="Unknown action unknown")
720 store_report
.assert_called_once()
722 log_error
.reset_mock()
723 store_report
.reset_mock()
724 with self
.subTest(unhandled_exception
=True):
725 process
.side_effect
= Exception("bouh")
727 m
.process(None, before
=True)
728 log_error
.assert_called_with("market_process", exception
=mock
.ANY
)
729 store_report
.assert_called_once()
732 class ProcessorTest(WebMockTestCase
):
733 def test_values(self
):
734 processor
= market
.Processor(self
.m
)
736 self
.assertEqual(self
.m
, processor
.market
)
738 def test_run_action(self
):
739 processor
= market
.Processor(self
.m
)
741 with mock
.patch
.object(processor
, "parse_args") as parse_args
:
742 method_mock
= mock
.Mock()
743 parse_args
.return_value
= [method_mock
, { "foo": "bar" }
]
745 processor
.run_action("foo", "bar", "baz")
747 parse_args
.assert_called_with("foo", "bar", "baz")
749 method_mock
.assert_called_with(foo
="bar")
751 processor
.run_action("wait_for_recent", "bar", "baz")
753 method_mock
.assert_called_with(foo
="bar")
755 def test_select_step(self
):
756 processor
= market
.Processor(self
.m
)
758 scenario
= processor
.scenarios
["sell_all"]
760 self
.assertEqual(scenario
, processor
.select_steps(scenario
, "all"))
761 self
.assertEqual(["all_sell"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "before"))))
762 self
.assertEqual(["wait", "all_buy"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "after"))))
763 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, 2))))
764 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "wait"))))
766 with self
.assertRaises(TypeError):
767 processor
.select_steps(scenario
, ["wait"])
769 @mock.patch("market.Processor.process_step")
770 def test_process(self
, process_step
):
771 processor
= market
.Processor(self
.m
)
773 processor
.process("sell_all", foo
="bar")
774 self
.assertEqual(3, process_step
.call_count
)
776 steps
= list(map(lambda x
: x
[1][1]["name"], process_step
.mock_calls
))
777 scenario_names
= list(map(lambda x
: x
[1][0], process_step
.mock_calls
))
778 kwargs
= list(map(lambda x
: x
[1][2], process_step
.mock_calls
))
779 self
.assertEqual(["all_sell", "wait", "all_buy"], steps
)
780 self
.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names
)
781 self
.assertEqual([{"foo":"bar"}
, {"foo":"bar"}
, {"foo":"bar"}
], kwargs
)
783 process_step
.reset_mock()
785 processor
.process("sell_needed", steps
=["before", "after"])
786 self
.assertEqual(3, process_step
.call_count
)
788 def test_method_arguments(self
):
789 ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
790 m
= market
.Market(ccxt
, self
.market_args())
792 processor
= market
.Processor(m
)
794 method
, arguments
= processor
.method_arguments("wait_for_recent")
795 self
.assertEqual(market
.Portfolio
.wait_for_recent
, method
)
796 self
.assertEqual(["delta", "poll"], arguments
)
798 method
, arguments
= processor
.method_arguments("prepare_trades")
799 self
.assertEqual(m
.prepare_trades
, method
)
800 self
.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments
)
802 method
, arguments
= processor
.method_arguments("prepare_orders")
803 self
.assertEqual(m
.trades
.prepare_orders
, method
)
805 method
, arguments
= processor
.method_arguments("move_balances")
806 self
.assertEqual(m
.move_balances
, method
)
808 method
, arguments
= processor
.method_arguments("run_orders")
809 self
.assertEqual(m
.trades
.run_orders
, method
)
811 method
, arguments
= processor
.method_arguments("follow_orders")
812 self
.assertEqual(m
.follow_orders
, method
)
814 method
, arguments
= processor
.method_arguments("close_trades")
815 self
.assertEqual(m
.trades
.close_trades
, method
)
817 def test_process_step(self
):
818 processor
= market
.Processor(self
.m
)
820 with mock
.patch
.object(processor
, "run_action") as run_action
:
821 step
= processor
.scenarios
["sell_needed"][1]
823 processor
.process_step("foo", step
, {"foo":"bar"}
)
825 self
.m
.report
.log_stage
.assert_has_calls([
826 mock
.call("process_foo__1_sell_begin"),
827 mock
.call("process_foo__1_sell_end"),
829 self
.m
.balances
.fetch_balances
.assert_has_calls([
830 mock
.call(tag
="process_foo__1_sell_begin"),
831 mock
.call(tag
="process_foo__1_sell_end"),
834 self
.assertEqual(5, run_action
.call_count
)
836 run_action
.assert_has_calls([
837 mock
.call('prepare_trades', {}, {'foo': 'bar'}
),
838 mock
.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}
, {'foo': 'bar'}
),
839 mock
.call('run_orders', {}, {'foo': 'bar'}
),
840 mock
.call('follow_orders', {}, {'foo': 'bar'}
),
841 mock
.call('close_trades', {}, {'foo': 'bar'}
),
845 with mock
.patch
.object(processor
, "run_action") as run_action
:
846 step
= processor
.scenarios
["sell_needed"][0]
848 processor
.process_step("foo", step
, {"foo":"bar"}
)
849 self
.m
.balances
.fetch_balances
.assert_not_called()
851 def test_parse_args(self
):
852 processor
= market
.Processor(self
.m
)
854 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
855 method_mock
= mock
.Mock()
856 method_arguments
.return_value
= [
860 method
, args
= processor
.parse_args("action", {"foo": "bar", "foo2": "bar"}
, {"foo": "bar2", "bla": "bla"}
)
862 self
.assertEqual(method_mock
, method
)
863 self
.assertEqual({"foo": "bar2", "foo2": "bar"}
, args
)
865 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
866 method_mock
= mock
.Mock()
867 method_arguments
.return_value
= [
871 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {})
873 self
.assertEqual(1, len(args
["repartition"]))
874 self
.assertIn("BTC", args
["repartition"])
876 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
877 method_mock
= mock
.Mock()
878 method_arguments
.return_value
= [
880 ["repartition", "base_currency"]
882 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {"base_currency": "USDT"}
)
884 self
.assertEqual(1, len(args
["repartition"]))
885 self
.assertIn("USDT", args
["repartition"])
887 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
888 method_mock
= mock
.Mock()
889 method_arguments
.return_value
= [
891 ["repartition", "base_currency"]
893 method
, args
= processor
.parse_args("action", {"repartition": { "ETH": 1 }
}, {"base_currency": "USDT"}
)
895 self
.assertEqual(1, len(args
["repartition"]))
896 self
.assertIn("ETH", args
["repartition"])