]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blame - tests/test_portfolio.py
Fetch only enabled market
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / tests / test_portfolio.py
CommitLineData
c682bdf4
IB
1from .helper import *
2import portfolio
3import datetime
4
3080f31d 5@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
6class ComputationTest(WebMockTestCase):
7 def test_compute_value(self):
8 compute = mock.Mock()
9 portfolio.Computation.compute_value("foo", "buy", compute_value=compute)
10 compute.assert_called_with("foo", "ask")
11
12 compute.reset_mock()
13 portfolio.Computation.compute_value("foo", "sell", compute_value=compute)
14 compute.assert_called_with("foo", "bid")
15
16 compute.reset_mock()
17 portfolio.Computation.compute_value("foo", "ask", compute_value=compute)
18 compute.assert_called_with("foo", "ask")
19
20 compute.reset_mock()
21 portfolio.Computation.compute_value("foo", "bid", compute_value=compute)
22 compute.assert_called_with("foo", "bid")
23
24 compute.reset_mock()
25 portfolio.Computation.computations["test"] = compute
26 portfolio.Computation.compute_value("foo", "bid", compute_value="test")
27 compute.assert_called_with("foo", "bid")
28
3080f31d 29@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
30class TradeTest(WebMockTestCase):
31
32 def test_values_assertion(self):
33 value_from = portfolio.Amount("BTC", "1.0")
34 value_from.linked_to = portfolio.Amount("ETH", "10.0")
35 value_to = portfolio.Amount("BTC", "1.0")
36 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
37 self.assertEqual("BTC", trade.base_currency)
38 self.assertEqual("ETH", trade.currency)
39 self.assertEqual(self.m, trade.market)
40
41 with self.assertRaises(AssertionError):
42 portfolio.Trade(value_from, -value_to, "ETH", self.m)
43 with self.assertRaises(AssertionError):
44 portfolio.Trade(value_from, value_to, "ETC", self.m)
45 with self.assertRaises(AssertionError):
46 value_from.currency = "ETH"
47 portfolio.Trade(value_from, value_to, "ETH", self.m)
48 value_from.currency = "BTC"
49 with self.assertRaises(AssertionError):
50 value_from2 = portfolio.Amount("BTC", "1.0")
51 portfolio.Trade(value_from2, value_to, "ETH", self.m)
52
53 value_from = portfolio.Amount("BTC", 0)
54 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
55 self.assertEqual(0, trade.value_from.linked_to)
56
57 def test_action(self):
58 value_from = portfolio.Amount("BTC", "1.0")
59 value_from.linked_to = portfolio.Amount("ETH", "10.0")
60 value_to = portfolio.Amount("BTC", "1.0")
61 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
62
63 self.assertIsNone(trade.action)
64
65 value_from = portfolio.Amount("BTC", "1.0")
66 value_from.linked_to = portfolio.Amount("BTC", "1.0")
67 value_to = portfolio.Amount("BTC", "2.0")
68 trade = portfolio.Trade(value_from, value_to, "BTC", self.m)
69
70 self.assertIsNone(trade.action)
71
72 value_from = portfolio.Amount("BTC", "0.5")
73 value_from.linked_to = portfolio.Amount("ETH", "10.0")
74 value_to = portfolio.Amount("BTC", "1.0")
75 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
76
77 self.assertEqual("acquire", trade.action)
78
79 value_from = portfolio.Amount("BTC", "0")
80 value_from.linked_to = portfolio.Amount("ETH", "0")
81 value_to = portfolio.Amount("BTC", "-1.0")
82 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
83
84 self.assertEqual("acquire", trade.action)
85
86 def test_order_action(self):
87 value_from = portfolio.Amount("BTC", "0.5")
88 value_from.linked_to = portfolio.Amount("ETH", "10.0")
89 value_to = portfolio.Amount("BTC", "1.0")
90 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
91
92 trade.inverted = False
93 self.assertEqual("buy", trade.order_action())
94 trade.inverted = True
95 self.assertEqual("sell", trade.order_action())
96
97 value_from = portfolio.Amount("BTC", "0")
98 value_from.linked_to = portfolio.Amount("ETH", "0")
99 value_to = portfolio.Amount("BTC", "-1.0")
100 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
101
102 trade.inverted = False
103 self.assertEqual("sell", trade.order_action())
104 trade.inverted = True
105 self.assertEqual("buy", trade.order_action())
106
107 def test_trade_type(self):
108 value_from = portfolio.Amount("BTC", "0.5")
109 value_from.linked_to = portfolio.Amount("ETH", "10.0")
110 value_to = portfolio.Amount("BTC", "1.0")
111 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
112
113 self.assertEqual("long", trade.trade_type)
114
115 value_from = portfolio.Amount("BTC", "0")
116 value_from.linked_to = portfolio.Amount("ETH", "0")
117 value_to = portfolio.Amount("BTC", "-1.0")
118 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
119
120 self.assertEqual("short", trade.trade_type)
121
122 def test_is_fullfiled(self):
123 with self.subTest(inverted=False):
124 value_from = portfolio.Amount("BTC", "0.5")
125 value_from.linked_to = portfolio.Amount("ETH", "10.0")
126 value_to = portfolio.Amount("BTC", "1.0")
127 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
128
129 order1 = mock.Mock()
130 order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
131
132 order2 = mock.Mock()
133 order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
134 trade.orders.append(order1)
135 trade.orders.append(order2)
136
137 self.assertFalse(trade.is_fullfiled)
138
139 order3 = mock.Mock()
140 order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
141 trade.orders.append(order3)
142
143 self.assertTrue(trade.is_fullfiled)
144
4ae84fb7
IB
145 order1.filled_amount.assert_called_with(in_base_currency=True, refetch=True)
146 order2.filled_amount.assert_called_with(in_base_currency=True, refetch=True)
147 order3.filled_amount.assert_called_with(in_base_currency=True, refetch=True)
c682bdf4
IB
148
149 with self.subTest(inverted=True):
150 value_from = portfolio.Amount("BTC", "0.5")
151 value_from.linked_to = portfolio.Amount("USDT", "1000.0")
152 value_to = portfolio.Amount("BTC", "1.0")
153 trade = portfolio.Trade(value_from, value_to, "USDT", self.m)
154 trade.inverted = True
155
156 order1 = mock.Mock()
157 order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
158
159 order2 = mock.Mock()
160 order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
161 trade.orders.append(order1)
162 trade.orders.append(order2)
163
164 self.assertFalse(trade.is_fullfiled)
165
166 order3 = mock.Mock()
167 order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
168 trade.orders.append(order3)
169
170 self.assertTrue(trade.is_fullfiled)
171
4ae84fb7
IB
172 order1.filled_amount.assert_called_with(in_base_currency=False, refetch=True)
173 order2.filled_amount.assert_called_with(in_base_currency=False, refetch=True)
174 order3.filled_amount.assert_called_with(in_base_currency=False, refetch=True)
c682bdf4
IB
175
176
177 def test_filled_amount(self):
178 value_from = portfolio.Amount("BTC", "0.5")
179 value_from.linked_to = portfolio.Amount("ETH", "10.0")
180 value_to = portfolio.Amount("BTC", "1.0")
181 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
182
183 order1 = mock.Mock()
184 order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")
185
186 order2 = mock.Mock()
187 order2.filled_amount.return_value = portfolio.Amount("ETH", "0.01")
188 trade.orders.append(order1)
189 trade.orders.append(order2)
190
191 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount())
4ae84fb7
IB
192 order1.filled_amount.assert_called_with(in_base_currency=False, refetch=False)
193 order2.filled_amount.assert_called_with(in_base_currency=False, refetch=False)
c682bdf4
IB
194
195 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
4ae84fb7
IB
196 order1.filled_amount.assert_called_with(in_base_currency=False, refetch=False)
197 order2.filled_amount.assert_called_with(in_base_currency=False, refetch=False)
c682bdf4
IB
198
199 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
4ae84fb7
IB
200 order1.filled_amount.assert_called_with(in_base_currency=True, refetch=False)
201 order2.filled_amount.assert_called_with(in_base_currency=True, refetch=False)
c682bdf4
IB
202
203 @mock.patch.object(portfolio.Computation, "compute_value")
204 @mock.patch.object(portfolio.Trade, "filled_amount")
205 @mock.patch.object(portfolio, "Order")
206 def test_prepare_order(self, Order, filled_amount, compute_value):
207 Order.return_value = "Order"
208
209 with self.subTest(desc="Nothing to do"):
210 value_from = portfolio.Amount("BTC", "10")
211 value_from.rate = D("0.1")
212 value_from.linked_to = portfolio.Amount("FOO", "100")
213 value_to = portfolio.Amount("BTC", "10")
214 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
215
216 trade.prepare_order()
217
218 filled_amount.assert_not_called()
219 compute_value.assert_not_called()
220 self.assertEqual(0, len(trade.orders))
221 Order.assert_not_called()
222
18de421e
IB
223 self.m.get_ticker.return_value = None
224 with self.subTest(desc="Unknown ticker"):
225 filled_amount.return_value = portfolio.Amount("BTC", "3")
226 compute_value.return_value = D("0.125")
227
228 value_from = portfolio.Amount("BTC", "1")
229 value_from.rate = D("0.1")
230 value_from.linked_to = portfolio.Amount("FOO", "10")
231 value_to = portfolio.Amount("BTC", "10")
232 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
233
234 trade.prepare_order()
235
236 filled_amount.assert_not_called()
237 compute_value.assert_not_called()
238 self.assertEqual(0, len(trade.orders))
239 Order.assert_not_called()
240 self.m.report.log_error.assert_called_once_with('prepare_order',
241 message='Unknown ticker FOO/BTC')
242
c682bdf4
IB
243 self.m.get_ticker.return_value = { "inverted": False }
244 with self.subTest(desc="Already filled"):
245 filled_amount.return_value = portfolio.Amount("FOO", "100")
246 compute_value.return_value = D("0.125")
247
248 value_from = portfolio.Amount("BTC", "10")
249 value_from.rate = D("0.1")
250 value_from.linked_to = portfolio.Amount("FOO", "100")
251 value_to = portfolio.Amount("BTC", "0")
252 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
253
254 trade.prepare_order()
255
256 filled_amount.assert_called_with(in_base_currency=False)
257 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
258 self.assertEqual(0, len(trade.orders))
259 self.m.report.log_error.assert_called_with("prepare_order", message=mock.ANY)
260 Order.assert_not_called()
261
262 with self.subTest(action="dispose", inverted=False):
263 filled_amount.return_value = portfolio.Amount("FOO", "60")
264 compute_value.return_value = D("0.125")
265
266 value_from = portfolio.Amount("BTC", "10")
267 value_from.rate = D("0.1")
268 value_from.linked_to = portfolio.Amount("FOO", "100")
269 value_to = portfolio.Amount("BTC", "1")
270 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
271
272 trade.prepare_order()
273
274 filled_amount.assert_called_with(in_base_currency=False)
275 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
276 self.assertEqual(1, len(trade.orders))
277 Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
278 D("0.125"), "BTC", "long", self.m,
279 trade, close_if_possible=False)
280
281 with self.subTest(action="dispose", inverted=False, close_if_possible=True):
282 filled_amount.return_value = portfolio.Amount("FOO", "60")
283 compute_value.return_value = D("0.125")
284
285 value_from = portfolio.Amount("BTC", "10")
286 value_from.rate = D("0.1")
287 value_from.linked_to = portfolio.Amount("FOO", "100")
288 value_to = portfolio.Amount("BTC", "1")
289 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
290
291 trade.prepare_order(close_if_possible=True)
292
293 filled_amount.assert_called_with(in_base_currency=False)
294 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
295 self.assertEqual(1, len(trade.orders))
296 Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
297 D("0.125"), "BTC", "long", self.m,
298 trade, close_if_possible=True)
299
300 with self.subTest(action="acquire", inverted=False):
301 filled_amount.return_value = portfolio.Amount("BTC", "3")
302 compute_value.return_value = D("0.125")
303
304 value_from = portfolio.Amount("BTC", "1")
305 value_from.rate = D("0.1")
306 value_from.linked_to = portfolio.Amount("FOO", "10")
307 value_to = portfolio.Amount("BTC", "10")
308 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
309
310 trade.prepare_order()
311
312 filled_amount.assert_called_with(in_base_currency=True)
313 compute_value.assert_called_with(self.m.get_ticker.return_value, "buy", compute_value="default")
314 self.assertEqual(1, len(trade.orders))
315
316 Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
317 D("0.125"), "BTC", "long", self.m,
318 trade, close_if_possible=False)
319
320 with self.subTest(close_if_possible=True):
321 filled_amount.return_value = portfolio.Amount("FOO", "0")
322 compute_value.return_value = D("0.125")
323
324 value_from = portfolio.Amount("BTC", "10")
325 value_from.rate = D("0.1")
326 value_from.linked_to = portfolio.Amount("FOO", "100")
327 value_to = portfolio.Amount("BTC", "0")
328 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
329
330 trade.prepare_order()
331
332 filled_amount.assert_called_with(in_base_currency=False)
333 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
334 self.assertEqual(1, len(trade.orders))
335 Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
336 D("0.125"), "BTC", "long", self.m,
337 trade, close_if_possible=True)
338
339 self.m.get_ticker.return_value = { "inverted": True, "original": {} }
340 with self.subTest(action="dispose", inverted=True):
341 filled_amount.return_value = portfolio.Amount("FOO", "300")
342 compute_value.return_value = D("125")
343
344 value_from = portfolio.Amount("BTC", "10")
345 value_from.rate = D("0.01")
346 value_from.linked_to = portfolio.Amount("FOO", "1000")
347 value_to = portfolio.Amount("BTC", "1")
348 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
349
350 trade.prepare_order(compute_value="foo")
351
352 filled_amount.assert_called_with(in_base_currency=True)
353 compute_value.assert_called_with(self.m.get_ticker.return_value["original"], "buy", compute_value="foo")
354 self.assertEqual(1, len(trade.orders))
355 Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
356 D("125"), "FOO", "long", self.m,
357 trade, close_if_possible=False)
358
359 with self.subTest(action="acquire", inverted=True):
360 filled_amount.return_value = portfolio.Amount("BTC", "4")
361 compute_value.return_value = D("125")
362
363 value_from = portfolio.Amount("BTC", "1")
364 value_from.rate = D("0.01")
365 value_from.linked_to = portfolio.Amount("FOO", "100")
366 value_to = portfolio.Amount("BTC", "10")
367 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
368
369 trade.prepare_order(compute_value="foo")
370
371 filled_amount.assert_called_with(in_base_currency=False)
372 compute_value.assert_called_with(self.m.get_ticker.return_value["original"], "sell", compute_value="foo")
373 self.assertEqual(1, len(trade.orders))
374 Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
375 D("125"), "FOO", "long", self.m,
376 trade, close_if_possible=False)
377
378 def test_tick_actions_recreate(self):
379 value_from = portfolio.Amount("BTC", "0.5")
380 value_from.linked_to = portfolio.Amount("ETH", "10.0")
381 value_to = portfolio.Amount("BTC", "1.0")
382 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
383
384 self.assertEqual("average", trade.tick_actions_recreate(0))
385 self.assertEqual("foo", trade.tick_actions_recreate(0, default="foo"))
386 self.assertEqual("average", trade.tick_actions_recreate(1))
387 self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(2))
388 self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(3))
389 self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(5))
390 self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(6))
391 self.assertEqual("default", trade.tick_actions_recreate(7))
392 self.assertEqual("default", trade.tick_actions_recreate(8))
393
394 @mock.patch.object(portfolio.Trade, "prepare_order")
395 def test_update_order(self, prepare_order):
396 order_mock = mock.Mock()
397 new_order_mock = mock.Mock()
398
399 value_from = portfolio.Amount("BTC", "0.5")
400 value_from.linked_to = portfolio.Amount("ETH", "10.0")
401 value_to = portfolio.Amount("BTC", "1.0")
402 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
403 prepare_order.return_value = new_order_mock
404
405 for i in [0, 1, 3, 4, 6]:
406 with self.subTest(tick=i):
407 trade.update_order(order_mock, i)
408 order_mock.cancel.assert_not_called()
409 new_order_mock.run.assert_not_called()
410 self.m.report.log_order.assert_called_once_with(order_mock, i,
411 update="waiting", compute_value=None, new_order=None)
412
413 order_mock.reset_mock()
414 new_order_mock.reset_mock()
415 trade.orders = []
416 self.m.report.log_order.reset_mock()
417
418 trade.update_order(order_mock, 2)
419 order_mock.cancel.assert_called()
420 new_order_mock.run.assert_called()
421 prepare_order.assert_called()
422 self.m.report.log_order.assert_called()
423 self.assertEqual(2, self.m.report.log_order.call_count)
424 calls = [
425 mock.call(order_mock, 2, update="adjusting",
426 compute_value=mock.ANY,
427 new_order=new_order_mock),
428 mock.call(order_mock, 2, new_order=new_order_mock),
429 ]
430 self.m.report.log_order.assert_has_calls(calls)
431
432 order_mock.reset_mock()
433 new_order_mock.reset_mock()
434 trade.orders = []
435 self.m.report.log_order.reset_mock()
436
437 trade.update_order(order_mock, 5)
438 order_mock.cancel.assert_called()
439 new_order_mock.run.assert_called()
440 prepare_order.assert_called()
441 self.assertEqual(2, self.m.report.log_order.call_count)
442 self.m.report.log_order.assert_called()
443 calls = [
444 mock.call(order_mock, 5, update="adjusting",
445 compute_value=mock.ANY,
446 new_order=new_order_mock),
447 mock.call(order_mock, 5, new_order=new_order_mock),
448 ]
449 self.m.report.log_order.assert_has_calls(calls)
450
451 order_mock.reset_mock()
452 new_order_mock.reset_mock()
453 trade.orders = []
454 self.m.report.log_order.reset_mock()
455
456 trade.update_order(order_mock, 7)
457 order_mock.cancel.assert_called()
458 new_order_mock.run.assert_called()
459 prepare_order.assert_called_with(compute_value="default")
460 self.m.report.log_order.assert_called()
461 self.assertEqual(2, self.m.report.log_order.call_count)
462 calls = [
463 mock.call(order_mock, 7, update="market_fallback",
464 compute_value='default',
465 new_order=new_order_mock),
466 mock.call(order_mock, 7, new_order=new_order_mock),
467 ]
468 self.m.report.log_order.assert_has_calls(calls)
469
470 order_mock.reset_mock()
471 new_order_mock.reset_mock()
472 trade.orders = []
473 self.m.report.log_order.reset_mock()
474
475 for i in [10, 13, 16]:
476 with self.subTest(tick=i):
477 trade.update_order(order_mock, i)
478 order_mock.cancel.assert_called()
479 new_order_mock.run.assert_called()
480 prepare_order.assert_called_with(compute_value="default")
481 self.m.report.log_order.assert_called()
482 self.assertEqual(2, self.m.report.log_order.call_count)
483 calls = [
484 mock.call(order_mock, i, update="market_adjust",
485 compute_value='default',
486 new_order=new_order_mock),
487 mock.call(order_mock, i, new_order=new_order_mock),
488 ]
489 self.m.report.log_order.assert_has_calls(calls)
490
491 order_mock.reset_mock()
492 new_order_mock.reset_mock()
493 trade.orders = []
494 self.m.report.log_order.reset_mock()
495
496 for i in [8, 9, 11, 12]:
497 with self.subTest(tick=i):
498 trade.update_order(order_mock, i)
499 order_mock.cancel.assert_not_called()
500 new_order_mock.run.assert_not_called()
501 self.m.report.log_order.assert_called_once_with(order_mock, i, update="waiting",
502 compute_value=None, new_order=None)
503
504 order_mock.reset_mock()
505 new_order_mock.reset_mock()
506 trade.orders = []
507 self.m.report.log_order.reset_mock()
508
509
510 def test_print_with_order(self):
511 value_from = portfolio.Amount("BTC", "0.5")
512 value_from.linked_to = portfolio.Amount("ETH", "10.0")
513 value_to = portfolio.Amount("BTC", "1.0")
514 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
515
516 order_mock1 = mock.Mock()
517 order_mock1.__repr__ = mock.Mock()
518 order_mock1.__repr__.return_value = "Mock 1"
519 order_mock2 = mock.Mock()
520 order_mock2.__repr__ = mock.Mock()
521 order_mock2.__repr__.return_value = "Mock 2"
522 order_mock1.mouvements = []
523 mouvement_mock1 = mock.Mock()
524 mouvement_mock1.__repr__ = mock.Mock()
525 mouvement_mock1.__repr__.return_value = "Mouvement 1"
526 mouvement_mock2 = mock.Mock()
527 mouvement_mock2.__repr__ = mock.Mock()
528 mouvement_mock2.__repr__.return_value = "Mouvement 2"
529 order_mock2.mouvements = [
530 mouvement_mock1, mouvement_mock2
531 ]
532 trade.orders.append(order_mock1)
533 trade.orders.append(order_mock2)
534
535 with mock.patch.object(trade, "filled_amount") as filled:
536 filled.return_value = portfolio.Amount("BTC", "0.1")
537
538 trade.print_with_order()
539
540 self.m.report.print_log.assert_called()
541 calls = self.m.report.print_log.mock_calls
542 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0]))
543 self.assertEqual("\tMock 1", str(calls[1][1][0]))
544 self.assertEqual("\tMock 2", str(calls[2][1][0]))
545 self.assertEqual("\t\tMouvement 1", str(calls[3][1][0]))
546 self.assertEqual("\t\tMouvement 2", str(calls[4][1][0]))
547
548 self.m.report.print_log.reset_mock()
549
550 filled.return_value = portfolio.Amount("BTC", "0.5")
551 trade.print_with_order()
552 calls = self.m.report.print_log.mock_calls
553 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls[0][1][0]))
554
555 self.m.report.print_log.reset_mock()
556
557 filled.return_value = portfolio.Amount("BTC", "0.1")
558 trade.closed = True
559 trade.print_with_order()
560 calls = self.m.report.print_log.mock_calls
561 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls[0][1][0]))
562
563 def test_close(self):
564 value_from = portfolio.Amount("BTC", "0.5")
565 value_from.linked_to = portfolio.Amount("ETH", "10.0")
566 value_to = portfolio.Amount("BTC", "1.0")
567 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
568 order1 = mock.Mock()
569 trade.orders.append(order1)
570
571 trade.close()
572
573 self.assertEqual(True, trade.closed)
574 order1.cancel.assert_called_once_with()
575
576 def test_pending(self):
577 value_from = portfolio.Amount("BTC", "0.5")
578 value_from.linked_to = portfolio.Amount("ETH", "10.0")
579 value_to = portfolio.Amount("BTC", "1.0")
580 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
581
582 trade.closed = True
583 self.assertEqual(False, trade.pending)
584
585 trade.closed = False
586 self.assertEqual(True, trade.pending)
587
588 order1 = mock.Mock()
589 order1.filled_amount.return_value = portfolio.Amount("BTC", "0.5")
590 trade.orders.append(order1)
591 self.assertEqual(False, trade.pending)
592
593 def test__repr(self):
594 value_from = portfolio.Amount("BTC", "0.5")
595 value_from.linked_to = portfolio.Amount("ETH", "10.0")
596 value_to = portfolio.Amount("BTC", "1.0")
597 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
598
599 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
600
601 def test_as_json(self):
602 value_from = portfolio.Amount("BTC", "0.5")
603 value_from.linked_to = portfolio.Amount("ETH", "10.0")
604 value_to = portfolio.Amount("BTC", "1.0")
605 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
606
607 as_json = trade.as_json()
608 self.assertEqual("acquire", as_json["action"])
609 self.assertEqual(D("0.5"), as_json["from"])
610 self.assertEqual(D("1.0"), as_json["to"])
611 self.assertEqual("ETH", as_json["currency"])
612 self.assertEqual("BTC", as_json["base_currency"])
613
3080f31d 614@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
615class BalanceTest(WebMockTestCase):
616 def test_values(self):
617 balance = portfolio.Balance("BTC", {
618 "exchange_total": "0.65",
619 "exchange_free": "0.35",
620 "exchange_used": "0.30",
621 "margin_total": "-10",
622 "margin_borrowed": "10",
623 "margin_available": "0",
624 "margin_in_position": "0",
625 "margin_position_type": "short",
626 "margin_borrowed_base_currency": "USDT",
627 "margin_liquidation_price": "1.20",
628 "margin_pending_gain": "10",
629 "margin_lending_fees": "0.4",
630 "margin_borrowed_base_price": "0.15",
631 })
632 self.assertEqual(portfolio.D("0.65"), balance.exchange_total.value)
633 self.assertEqual(portfolio.D("0.35"), balance.exchange_free.value)
634 self.assertEqual(portfolio.D("0.30"), balance.exchange_used.value)
635 self.assertEqual("BTC", balance.exchange_total.currency)
636 self.assertEqual("BTC", balance.exchange_free.currency)
637 self.assertEqual("BTC", balance.exchange_total.currency)
638
639 self.assertEqual(portfolio.D("-10"), balance.margin_total.value)
640 self.assertEqual(portfolio.D("10"), balance.margin_borrowed.value)
641 self.assertEqual(portfolio.D("0"), balance.margin_available.value)
642 self.assertEqual("BTC", balance.margin_total.currency)
643 self.assertEqual("BTC", balance.margin_borrowed.currency)
644 self.assertEqual("BTC", balance.margin_available.currency)
645
646 self.assertEqual("BTC", balance.currency)
647
648 self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
649 self.assertEqual("USDT", balance.margin_lending_fees.currency)
650
651 def test__repr(self):
652 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
653 repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
654 balance = portfolio.Balance("BTX", { "exchange_total": 3,
655 "exchange_used": 1, "exchange_free": 2 })
656 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
657
658 balance = portfolio.Balance("BTX", { "exchange_total": 1, "exchange_used": 1})
659 self.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance))
660
661 balance = portfolio.Balance("BTX", { "margin_total": 3,
662 "margin_in_position": 1, "margin_available": 2 })
663 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
664
665 balance = portfolio.Balance("BTX", { "margin_total": 2, "margin_available": 2 })
666 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance))
667
668 balance = portfolio.Balance("BTX", { "margin_total": -3,
669 "margin_borrowed_base_price": D("0.1"),
670 "margin_borrowed_base_currency": "BTC",
671 "margin_lending_fees": D("0.002") })
672 self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
673
674 balance = portfolio.Balance("BTX", { "margin_total": 1,
675 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
676 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))
677
678 def test_as_json(self):
679 balance = portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })
680 as_json = balance.as_json()
681 self.assertEqual(set(portfolio.Balance.base_keys), set(as_json.keys()))
682 self.assertEqual(D(0), as_json["total"])
683 self.assertEqual(D(2), as_json["exchange_total"])
684 self.assertEqual(D(2), as_json["exchange_free"])
685 self.assertEqual(D(0), as_json["exchange_used"])
686 self.assertEqual(D(0), as_json["margin_total"])
687 self.assertEqual(D(0), as_json["margin_available"])
688 self.assertEqual(D(0), as_json["margin_borrowed"])
689
3080f31d 690@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
691class OrderTest(WebMockTestCase):
692 def test_values(self):
693 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
694 D("0.1"), "BTC", "long", "market", "trade")
695 self.assertEqual("buy", order.action)
696 self.assertEqual(10, order.amount.value)
697 self.assertEqual("ETH", order.amount.currency)
698 self.assertEqual(D("0.1"), order.rate)
699 self.assertEqual("BTC", order.base_currency)
700 self.assertEqual("market", order.market)
701 self.assertEqual("long", order.trade_type)
702 self.assertEqual("pending", order.status)
703 self.assertEqual("trade", order.trade)
704 self.assertIsNone(order.id)
705 self.assertFalse(order.close_if_possible)
706
707 def test__repr(self):
708 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
709 D("0.1"), "BTC", "long", "market", "trade")
710 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order))
711
712 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
713 D("0.1"), "BTC", "long", "market", "trade",
714 close_if_possible=True)
715 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order))
716
717 def test_as_json(self):
718 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
719 D("0.1"), "BTC", "long", "market", "trade")
720 mouvement_mock1 = mock.Mock()
721 mouvement_mock1.as_json.return_value = 1
722 mouvement_mock2 = mock.Mock()
723 mouvement_mock2.as_json.return_value = 2
724
725 order.mouvements = [mouvement_mock1, mouvement_mock2]
726 as_json = order.as_json()
727 self.assertEqual("buy", as_json["action"])
728 self.assertEqual("long", as_json["trade_type"])
729 self.assertEqual(10, as_json["amount"])
730 self.assertEqual("ETH", as_json["currency"])
731 self.assertEqual("BTC", as_json["base_currency"])
732 self.assertEqual(D("0.1"), as_json["rate"])
733 self.assertEqual("pending", as_json["status"])
734 self.assertEqual(False, as_json["close_if_possible"])
735 self.assertIsNone(as_json["id"])
736 self.assertEqual([1, 2], as_json["mouvements"])
737
738 def test_account(self):
739 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
740 D("0.1"), "BTC", "long", "market", "trade")
741 self.assertEqual("exchange", order.account)
742
743 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
744 D("0.1"), "BTC", "short", "market", "trade")
745 self.assertEqual("margin", order.account)
746
747 def test_pending(self):
748 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
749 D("0.1"), "BTC", "long", "market", "trade")
750 self.assertTrue(order.pending)
751 order.status = "open"
752 self.assertFalse(order.pending)
753
754 def test_open(self):
755 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
756 D("0.1"), "BTC", "long", "market", "trade")
757 self.assertFalse(order.open)
758 order.status = "open"
759 self.assertTrue(order.open)
760
761 def test_finished(self):
762 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
763 D("0.1"), "BTC", "long", "market", "trade")
764 self.assertFalse(order.finished)
765 order.status = "closed"
766 self.assertTrue(order.finished)
767 order.status = "canceled"
768 self.assertTrue(order.finished)
769 order.status = "error"
770 self.assertTrue(order.finished)
771
772 @mock.patch.object(portfolio.Order, "fetch")
773 def test_cancel(self, fetch):
774 with self.subTest(debug=True):
775 self.m.debug = True
776 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
777 D("0.1"), "BTC", "long", self.m, "trade")
778 order.status = "open"
779
780 order.cancel()
781 self.m.ccxt.cancel_order.assert_not_called()
782 self.m.report.log_debug_action.assert_called_once()
783 self.m.report.log_debug_action.reset_mock()
784 self.assertEqual("canceled", order.status)
785
786 with self.subTest(desc="Nominal case"):
787 self.m.debug = False
788 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
789 D("0.1"), "BTC", "long", self.m, "trade")
790 order.status = "open"
791 order.id = 42
792
793 order.cancel()
794 self.m.ccxt.cancel_order.assert_called_with(42)
795 fetch.assert_called_once_with()
796 self.m.report.log_debug_action.assert_not_called()
797
798 with self.subTest(exception=True):
799 self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
800 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
801 D("0.1"), "BTC", "long", self.m, "trade")
802 order.status = "open"
803 order.id = 42
804 order.cancel()
805 self.m.ccxt.cancel_order.assert_called_with(42)
806 self.m.report.log_error.assert_called_once()
807
808 self.m.reset_mock()
809 with self.subTest(id=None):
810 self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
811 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
812 D("0.1"), "BTC", "long", self.m, "trade")
813 order.status = "open"
814 order.cancel()
815 self.m.ccxt.cancel_order.assert_not_called()
816
817 self.m.reset_mock()
818 with self.subTest(open=False):
819 self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
820 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
821 D("0.1"), "BTC", "long", self.m, "trade")
822 order.status = "closed"
823 order.cancel()
824 self.m.ccxt.cancel_order.assert_not_called()
825
1902674c 826 def test_mark_dust_amount_remaining(self):
c682bdf4
IB
827 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
828 D("0.1"), "BTC", "long", self.m, "trade")
1902674c
IB
829 self.m.ccxt.is_dust_trade.return_value = False
830 order.mark_dust_amount_remaining_order()
831 self.assertEqual("pending", order.status)
c682bdf4 832
1902674c
IB
833 self.m.ccxt.is_dust_trade.return_value = True
834 order.mark_dust_amount_remaining_order()
835 self.assertEqual("closed_dust_remaining", order.status)
c682bdf4
IB
836
837 @mock.patch.object(portfolio.Order, "fetch")
838 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
839 def test_remaining_amount(self, filled_amount, fetch):
840 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
841 D("0.1"), "BTC", "long", self.m, "trade")
842
843 self.assertEqual(9, order.remaining_amount().value)
844
845 order.status = "open"
846 self.assertEqual(9, order.remaining_amount().value)
847
848 @mock.patch.object(portfolio.Order, "fetch")
849 def test_filled_amount(self, fetch):
850 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
851 D("0.1"), "BTC", "long", self.m, "trade")
852 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
853 "tradeID": 42, "type": "buy", "fee": "0.0015",
854 "date": "2017-12-30 12:00:12", "rate": "0.1",
855 "amount": "3", "total": "0.3"
856 }))
857 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
858 "tradeID": 43, "type": "buy", "fee": "0.0015",
859 "date": "2017-12-30 13:00:12", "rate": "0.2",
860 "amount": "2", "total": "0.4"
861 }))
862 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount())
863 fetch.assert_not_called()
864 order.status = "open"
865 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False))
4ae84fb7
IB
866 fetch.assert_not_called()
867 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False, refetch=True))
c682bdf4
IB
868 fetch.assert_called_once()
869 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))
870
871 def test_fetch_mouvements(self):
872 self.m.ccxt.privatePostReturnOrderTrades.return_value = [
873 {
874 "tradeID": 42, "type": "buy", "fee": "0.0015",
875 "date": "2017-12-30 13:00:12", "rate": "0.1",
876 "amount": "3", "total": "0.3"
877 },
878 {
879 "tradeID": 43, "type": "buy", "fee": "0.0015",
880 "date": "2017-12-30 12:00:12", "rate": "0.2",
881 "amount": "2", "total": "0.4"
882 }
883 ]
884 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
885 D("0.1"), "BTC", "long", self.m, "trade")
886 order.id = 12
887 order.mouvements = ["Foo", "Bar", "Baz"]
888
889 order.fetch_mouvements()
890
891 self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
892 self.assertEqual(2, len(order.mouvements))
893 self.assertEqual(43, order.mouvements[0].id)
894 self.assertEqual(42, order.mouvements[1].id)
895
896 self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
897 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
898 D("0.1"), "BTC", "long", self.m, "trade")
899 order.fetch_mouvements()
900 self.assertEqual(0, len(order.mouvements))
901
902 def test_mark_finished_order(self):
903 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
904 D("0.1"), "BTC", "short", self.m, "trade",
905 close_if_possible=True)
906 order.status = "closed"
907 self.m.debug = False
908
909 order.mark_finished_order()
910 self.m.ccxt.close_margin_position.assert_called_with("ETH", "BTC")
911 self.m.ccxt.close_margin_position.reset_mock()
912
913 order.status = "open"
914 order.mark_finished_order()
915 self.m.ccxt.close_margin_position.assert_not_called()
916
917 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
918 D("0.1"), "BTC", "short", self.m, "trade",
919 close_if_possible=False)
920 order.status = "closed"
921 order.mark_finished_order()
922 self.m.ccxt.close_margin_position.assert_not_called()
923
924 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
925 D("0.1"), "BTC", "short", self.m, "trade",
926 close_if_possible=True)
927 order.status = "closed"
928 order.mark_finished_order()
929 self.m.ccxt.close_margin_position.assert_not_called()
930
931 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
932 D("0.1"), "BTC", "long", self.m, "trade",
933 close_if_possible=True)
934 order.status = "closed"
935 order.mark_finished_order()
936 self.m.ccxt.close_margin_position.assert_not_called()
937
938 self.m.debug = True
939
940 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
941 D("0.1"), "BTC", "short", self.m, "trade",
942 close_if_possible=True)
943 order.status = "closed"
944
945 order.mark_finished_order()
946 self.m.ccxt.close_margin_position.assert_not_called()
947 self.m.report.log_debug_action.assert_called_once()
948
949 @mock.patch.object(portfolio.Order, "fetch_mouvements")
950 @mock.patch.object(portfolio.Order, "mark_disappeared_order")
951 @mock.patch.object(portfolio.Order, "mark_finished_order")
952 def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
953 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
954 D("0.1"), "BTC", "long", self.m, "trade")
955 order.id = 45
956 with self.subTest(debug=True):
957 self.m.debug = True
958 order.fetch()
959 self.m.report.log_debug_action.assert_called_once()
960 self.m.report.log_debug_action.reset_mock()
961 self.m.ccxt.fetch_order.assert_not_called()
962 mark_finished_order.assert_not_called()
963 mark_disappeared_order.assert_not_called()
964 fetch_mouvements.assert_not_called()
965
966 with self.subTest(debug=False):
967 self.m.debug = False
968 self.m.ccxt.fetch_order.return_value = {
969 "status": "foo",
970 "datetime": "timestamp"
971 }
1902674c 972 self.m.ccxt.is_dust_trade.return_value = False
c682bdf4
IB
973 order.fetch()
974
975 self.m.ccxt.fetch_order.assert_called_once_with(45)
976 fetch_mouvements.assert_called_once()
977 self.assertEqual("foo", order.status)
978 self.assertEqual("timestamp", order.timestamp)
979 self.assertEqual(1, len(order.results))
980 self.m.report.log_debug_action.assert_not_called()
981 mark_finished_order.assert_called_once()
982 mark_disappeared_order.assert_called_once()
983
984 mark_finished_order.reset_mock()
985 with self.subTest(missing_order=True):
986 self.m.ccxt.fetch_order.side_effect = [
987 portfolio.OrderNotCached,
988 ]
989 order.fetch()
990 self.assertEqual("closed_unknown", order.status)
991 mark_finished_order.assert_called_once()
992
993 def test_mark_disappeared_order(self):
994 with self.subTest("Open order"):
995 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
996 D("0.1"), "BTC", "long", self.m, "trade")
997 order.id = 45
998 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
999 "tradeID":21336541,
1000 "currencyPair":"BTC_XRP",
1001 "type":"sell",
1002 "rate":"0.00007013",
1003 "amount":"0.00000222",
1004 "total":"0.00000000",
1005 "fee":"0.00150000",
1006 "date":"2018-04-02 00:09:13"
1007 }))
1008 order.mark_disappeared_order()
1009 self.assertEqual("pending", order.status)
1010
1011 with self.subTest("Non-zero amount"):
1012 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1013 D("0.1"), "BTC", "long", self.m, "trade")
1014 order.id = 45
1015 order.status = "closed"
1016 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1017 "tradeID":21336541,
1018 "currencyPair":"BTC_XRP",
1019 "type":"sell",
1020 "rate":"0.00007013",
1021 "amount":"0.00000222",
1022 "total":"0.00000010",
1023 "fee":"0.00150000",
1024 "date":"2018-04-02 00:09:13"
1025 }))
1026 order.mark_disappeared_order()
1027 self.assertEqual("closed", order.status)
1028
1029 with self.subTest("Other mouvements"):
1030 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1031 D("0.1"), "BTC", "long", self.m, "trade")
1032 order.id = 45
1033 order.status = "closed"
1034 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1035 "tradeID":21336541,
1036 "currencyPair":"BTC_XRP",
1037 "type":"sell",
1038 "rate":"0.00007013",
1039 "amount":"0.00000222",
1040 "total":"0.00000001",
1041 "fee":"0.00150000",
1042 "date":"2018-04-02 00:09:13"
1043 }))
1044 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1045 "tradeID":21336541,
1046 "currencyPair":"BTC_XRP",
1047 "type":"sell",
1048 "rate":"0.00007013",
1049 "amount":"0.00000222",
1050 "total":"0.00000000",
1051 "fee":"0.00150000",
1052 "date":"2018-04-02 00:09:13"
1053 }))
1054 order.mark_disappeared_order()
1055 self.assertEqual("error_disappeared", order.status)
1056
1057 with self.subTest("Order disappeared"):
1058 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1059 D("0.1"), "BTC", "long", self.m, "trade")
1060 order.id = 45
1061 order.status = "closed"
1062 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1063 "tradeID":21336541,
1064 "currencyPair":"BTC_XRP",
1065 "type":"sell",
1066 "rate":"0.00007013",
1067 "amount":"0.00000222",
1068 "total":"0.00000000",
1069 "fee":"0.00150000",
1070 "date":"2018-04-02 00:09:13"
1071 }))
1072 order.mark_disappeared_order()
1073 self.assertEqual("error_disappeared", order.status)
1074
1075 @mock.patch.object(portfolio.Order, "fetch")
1076 def test_get_status(self, fetch):
1077 with self.subTest(debug=True):
1078 self.m.debug = True
1079 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1080 D("0.1"), "BTC", "long", self.m, "trade")
1081 self.assertEqual("pending", order.get_status())
1082 fetch.assert_not_called()
1083 self.m.report.log_debug_action.assert_called_once()
1084
1085 with self.subTest(debug=False, finished=False):
1086 self.m.debug = False
1087 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1088 D("0.1"), "BTC", "long", self.m, "trade")
1089 def _fetch(order):
1090 def update_status():
1091 order.status = "open"
1092 return update_status
1093 fetch.side_effect = _fetch(order)
1094 self.assertEqual("open", order.get_status())
1095 fetch.assert_called_once()
1096
1097 fetch.reset_mock()
1098 with self.subTest(debug=False, finished=True):
1099 self.m.debug = False
1100 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1101 D("0.1"), "BTC", "long", self.m, "trade")
1102 def _fetch(order):
1103 def update_status():
1104 order.status = "closed"
1105 return update_status
1106 fetch.side_effect = _fetch(order)
1107 self.assertEqual("closed", order.get_status())
1108 fetch.assert_called_once()
1109
1110 def test_run(self):
1111 self.m.ccxt.order_precision.return_value = 4
1112 with self.subTest(debug=True):
1113 self.m.debug = True
1114 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1115 D("0.1"), "BTC", "long", self.m, "trade")
1116 order.run()
1117 self.m.ccxt.create_order.assert_not_called()
1118 self.m.report.log_debug_action.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1119 self.assertEqual("open", order.status)
1120 self.assertEqual(1, len(order.results))
1121 self.assertEqual(-1, order.id)
1122
1123 self.m.ccxt.create_order.reset_mock()
1124 with self.subTest(debug=False):
1125 self.m.debug = False
1126 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1127 D("0.1"), "BTC", "long", self.m, "trade")
1128 self.m.ccxt.create_order.return_value = { "id": 123 }
1129 order.run()
1130 self.m.ccxt.create_order.assert_called_once()
1131 self.assertEqual(1, len(order.results))
1132 self.assertEqual("open", order.status)
1133
1134 self.m.ccxt.create_order.reset_mock()
1135 with self.subTest(exception=True):
1136 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1137 D("0.1"), "BTC", "long", self.m, "trade")
1138 self.m.ccxt.create_order.side_effect = Exception("bouh")
1139 order.run()
1140 self.m.ccxt.create_order.assert_called_once()
1141 self.assertEqual(0, len(order.results))
1142 self.assertEqual("error", order.status)
1143 self.m.report.log_error.assert_called_once()
1144
1145 self.m.ccxt.create_order.reset_mock()
1146 with self.subTest(dust_amount_exception=True),\
1147 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1148 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
1149 D("0.1"), "BTC", "long", self.m, "trade")
1150 self.m.ccxt.create_order.side_effect = portfolio.InvalidOrder
1151 order.run()
1152 self.m.ccxt.create_order.assert_called_once()
1153 self.assertEqual(0, len(order.results))
1154 self.assertEqual("closed", order.status)
1155 mark_finished_order.assert_called_once()
1156
1157 self.m.ccxt.order_precision.return_value = 8
1158 self.m.ccxt.create_order.reset_mock()
1159 with self.subTest(insufficient_funds=True),\
1160 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1161 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1162 D("0.1"), "BTC", "long", self.m, "trade")
1163 self.m.ccxt.create_order.side_effect = [
1164 portfolio.InsufficientFunds,
1165 portfolio.InsufficientFunds,
1166 portfolio.InsufficientFunds,
1167 { "id": 123 },
1168 ]
1169 order.run()
1170 self.m.ccxt.create_order.assert_has_calls([
1171 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1172 mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
1173 mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
1174 mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
1175 ])
1176 self.assertEqual(4, self.m.ccxt.create_order.call_count)
1177 self.assertEqual(1, len(order.results))
1178 self.assertEqual("open", order.status)
1179 self.assertEqual(4, order.tries)
1180 self.m.report.log_error.assert_called()
1181 self.assertEqual(4, self.m.report.log_error.call_count)
1182
1183 self.m.ccxt.order_precision.return_value = 8
1184 self.m.ccxt.create_order.reset_mock()
1185 self.m.report.log_error.reset_mock()
1186 with self.subTest(insufficient_funds=True),\
1187 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1188 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1189 D("0.1"), "BTC", "long", self.m, "trade")
1190 self.m.ccxt.create_order.side_effect = [
1191 portfolio.InsufficientFunds,
1192 portfolio.InsufficientFunds,
1193 portfolio.InsufficientFunds,
1194 portfolio.InsufficientFunds,
1195 portfolio.InsufficientFunds,
1196 ]
1197 order.run()
1198 self.m.ccxt.create_order.assert_has_calls([
1199 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1200 mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
1201 mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
1202 mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
1203 mock.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account='exchange', price=D('0.1')),
1204 ])
1205 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1206 self.assertEqual(0, len(order.results))
1207 self.assertEqual("error", order.status)
1208 self.assertEqual(5, order.tries)
1209 self.m.report.log_error.assert_called()
1210 self.assertEqual(5, self.m.report.log_error.call_count)
1211 self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception=mock.ANY)
1212
1213 self.m.reset_mock()
1214 with self.subTest(invalid_nonce=True):
1215 with self.subTest(retry_success=True):
1216 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1217 D("0.1"), "BTC", "long", self.m, "trade")
1218 self.m.ccxt.create_order.side_effect = [
1219 portfolio.InvalidNonce,
1220 portfolio.InvalidNonce,
1221 { "id": 123 },
1222 ]
1223 order.run()
1224 self.m.ccxt.create_order.assert_has_calls([
1225 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1226 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1227 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1228 ])
1229 self.assertEqual(3, self.m.ccxt.create_order.call_count)
1230 self.assertEqual(3, order.tries)
1231 self.m.report.log_error.assert_called()
1232 self.assertEqual(2, self.m.report.log_error.call_count)
1233 self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after invalid nonce", exception=mock.ANY)
1234 self.assertEqual(123, order.id)
1235
1236 self.m.reset_mock()
1237 with self.subTest(retry_success=False):
1238 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1239 D("0.1"), "BTC", "long", self.m, "trade")
1240 self.m.ccxt.create_order.side_effect = [
1241 portfolio.InvalidNonce,
1242 portfolio.InvalidNonce,
1243 portfolio.InvalidNonce,
1244 portfolio.InvalidNonce,
1245 portfolio.InvalidNonce,
1246 ]
1247 order.run()
1248 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1249 self.assertEqual(5, order.tries)
1250 self.m.report.log_error.assert_called()
1251 self.assertEqual(5, self.m.report.log_error.call_count)
1252 self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after invalid nonce", exception=mock.ANY)
1253 self.assertEqual("error", order.status)
1254
1255 self.m.reset_mock()
1256 with self.subTest(request_timeout=True):
1257 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1258 D("0.1"), "BTC", "long", self.m, "trade")
1259 with self.subTest(retrieved=False), \
1260 mock.patch.object(order, "retrieve_order") as retrieve:
1261 self.m.ccxt.create_order.side_effect = [
1262 portfolio.RequestTimeout,
1263 portfolio.RequestTimeout,
1264 { "id": 123 },
1265 ]
1266 retrieve.return_value = False
1267 order.run()
1268 self.m.ccxt.create_order.assert_has_calls([
1269 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1270 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1271 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1272 ])
1273 self.assertEqual(3, self.m.ccxt.create_order.call_count)
1274 self.assertEqual(3, order.tries)
1275 self.m.report.log_error.assert_called()
1276 self.assertEqual(2, self.m.report.log_error.call_count)
1277 self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after timeout", exception=mock.ANY)
1278 self.assertEqual(123, order.id)
1279
1280 self.m.reset_mock()
1281 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1282 D("0.1"), "BTC", "long", self.m, "trade")
1283 with self.subTest(retrieved=True), \
1284 mock.patch.object(order, "retrieve_order") as retrieve:
1285 self.m.ccxt.create_order.side_effect = [
1286 portfolio.RequestTimeout,
1287 ]
1288 def _retrieve():
1289 order.results.append({"id": 123})
1290 return True
1291 retrieve.side_effect = _retrieve
1292 order.run()
1293 self.m.ccxt.create_order.assert_has_calls([
1294 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1295 ])
1296 self.assertEqual(1, self.m.ccxt.create_order.call_count)
1297 self.assertEqual(1, order.tries)
1298 self.m.report.log_error.assert_called()
1299 self.assertEqual(1, self.m.report.log_error.call_count)
1300 self.m.report.log_error.assert_called_with(mock.ANY, message="Timeout, found the order")
1301 self.assertEqual(123, order.id)
1302
1303 self.m.reset_mock()
1304 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1305 D("0.1"), "BTC", "long", self.m, "trade")
1306 with self.subTest(retrieved=False), \
1307 mock.patch.object(order, "retrieve_order") as retrieve:
1308 self.m.ccxt.create_order.side_effect = [
1309 portfolio.RequestTimeout,
1310 portfolio.RequestTimeout,
1311 portfolio.RequestTimeout,
1312 portfolio.RequestTimeout,
1313 portfolio.RequestTimeout,
1314 ]
1315 retrieve.return_value = False
1316 order.run()
1317 self.m.ccxt.create_order.assert_has_calls([
1318 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1319 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1320 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1321 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1322 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1323 ])
1324 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1325 self.assertEqual(5, order.tries)
1326 self.m.report.log_error.assert_called()
1327 self.assertEqual(5, self.m.report.log_error.call_count)
1328 self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after timeouts", exception=mock.ANY)
1329 self.assertEqual("error", order.status)
1330
1331 def test_retrieve_order(self):
1332 with self.subTest(similar_open_order=True):
1333 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1334 D("0.1"), "BTC", "long", self.m, "trade")
1335 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1336
1337 self.m.ccxt.order_precision.return_value = 8
1338 self.m.ccxt.fetch_orders.return_value = [
1339 { # Wrong amount
1340 'amount': 0.002, 'cost': 0.1,
1341 'datetime': '2018-03-25T15:15:51.000Z',
1342 'fee': None, 'filled': 0.0,
1343 'id': '1',
1344 'info': {
1345 'amount': '0.002',
1346 'date': '2018-03-25 15:15:51',
1347 'margin': 0, 'orderNumber': '1',
1348 'price': '0.1', 'rate': '0.1',
1349 'side': 'buy', 'startingAmount': '0.002',
1350 'status': 'open', 'total': '0.0002',
1351 'type': 'limit'
1352 },
1353 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1354 'status': 'open', 'symbol': 'ETH/BTC',
1355 'timestamp': 1521990951000, 'trades': None,
1356 'type': 'limit'
1357 },
1358 { # Margin
1359 'amount': 0.001, 'cost': 0.1,
1360 'datetime': '2018-03-25T15:15:51.000Z',
1361 'fee': None, 'filled': 0.0,
1362 'id': '2',
1363 'info': {
1364 'amount': '0.001',
1365 'date': '2018-03-25 15:15:51',
1366 'margin': 1, 'orderNumber': '2',
1367 'price': '0.1', 'rate': '0.1',
1368 'side': 'buy', 'startingAmount': '0.001',
1369 'status': 'open', 'total': '0.0001',
1370 'type': 'limit'
1371 },
1372 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1373 'status': 'open', 'symbol': 'ETH/BTC',
1374 'timestamp': 1521990951000, 'trades': None,
1375 'type': 'limit'
1376 },
1377 { # selling
1378 'amount': 0.001, 'cost': 0.1,
1379 'datetime': '2018-03-25T15:15:51.000Z',
1380 'fee': None, 'filled': 0.0,
1381 'id': '3',
1382 'info': {
1383 'amount': '0.001',
1384 'date': '2018-03-25 15:15:51',
1385 'margin': 0, 'orderNumber': '3',
1386 'price': '0.1', 'rate': '0.1',
1387 'side': 'sell', 'startingAmount': '0.001',
1388 'status': 'open', 'total': '0.0001',
1389 'type': 'limit'
1390 },
1391 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1392 'status': 'open', 'symbol': 'ETH/BTC',
1393 'timestamp': 1521990951000, 'trades': None,
1394 'type': 'limit'
1395 },
1396 { # Wrong rate
1397 'amount': 0.001, 'cost': 0.15,
1398 'datetime': '2018-03-25T15:15:51.000Z',
1399 'fee': None, 'filled': 0.0,
1400 'id': '4',
1401 'info': {
1402 'amount': '0.001',
1403 'date': '2018-03-25 15:15:51',
1404 'margin': 0, 'orderNumber': '4',
1405 'price': '0.15', 'rate': '0.15',
1406 'side': 'buy', 'startingAmount': '0.001',
1407 'status': 'open', 'total': '0.0001',
1408 'type': 'limit'
1409 },
1410 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1411 'status': 'open', 'symbol': 'ETH/BTC',
1412 'timestamp': 1521990951000, 'trades': None,
1413 'type': 'limit'
1414 },
1415 { # All good
1416 'amount': 0.001, 'cost': 0.1,
1417 'datetime': '2018-03-25T15:15:51.000Z',
1418 'fee': None, 'filled': 0.0,
1419 'id': '5',
1420 'info': {
1421 'amount': '0.001',
1422 'date': '2018-03-25 15:15:51',
1423 'margin': 0, 'orderNumber': '1',
1424 'price': '0.1', 'rate': '0.1',
1425 'side': 'buy', 'startingAmount': '0.001',
1426 'status': 'open', 'total': '0.0001',
1427 'type': 'limit'
1428 },
1429 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1430 'status': 'open', 'symbol': 'ETH/BTC',
1431 'timestamp': 1521990951000, 'trades': None,
1432 'type': 'limit'
1433 }
1434 ]
1435 result = order.retrieve_order()
1436 self.assertTrue(result)
1437 self.assertEqual('5', order.results[0]["id"])
1438 self.m.ccxt.fetch_my_trades.assert_not_called()
1439 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
1440
1441 self.m.reset_mock()
1442 with self.subTest(similar_open_order=False, past_trades=True):
1443 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1444 D("0.1"), "BTC", "long", self.m, "trade")
1445 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1446
1447 self.m.ccxt.order_precision.return_value = 8
1448 self.m.ccxt.fetch_orders.return_value = []
1449 self.m.ccxt.fetch_my_trades.return_value = [
1450 { # Wrong timestamp 1
1451 'amount': 0.0006,
1452 'cost': 0.00006,
1453 'datetime': '2018-03-25T15:15:14.000Z',
1454 'id': '1-1',
1455 'info': {
1456 'amount': '0.0006',
1457 'category': 'exchange',
1458 'date': '2018-03-25 15:15:14',
1459 'fee': '0.00150000',
1460 'globalTradeID': 1,
1461 'orderNumber': '1',
1462 'rate': '0.1',
1463 'total': '0.00006',
1464 'tradeID': '1-1',
1465 'type': 'buy'
1466 },
1467 'order': '1',
1468 'price': 0.1,
1469 'side': 'buy',
1470 'symbol': 'ETH/BTC',
1471 'timestamp': 1521983714,
1472 'type': 'limit'
1473 },
1474 { # Wrong timestamp 2
1475 'amount': 0.0004,
1476 'cost': 0.00004,
1477 'datetime': '2018-03-25T15:16:54.000Z',
1478 'id': '1-2',
1479 'info': {
1480 'amount': '0.0004',
1481 'category': 'exchange',
1482 'date': '2018-03-25 15:16:54',
1483 'fee': '0.00150000',
1484 'globalTradeID': 2,
1485 'orderNumber': '1',
1486 'rate': '0.1',
1487 'total': '0.00004',
1488 'tradeID': '1-2',
1489 'type': 'buy'
1490 },
1491 'order': '1',
1492 'price': 0.1,
1493 'side': 'buy',
1494 'symbol': 'ETH/BTC',
1495 'timestamp': 1521983814,
1496 'type': 'limit'
1497 },
1498 { # Wrong side 1
1499 'amount': 0.0006,
1500 'cost': 0.00006,
1501 'datetime': '2018-03-25T15:15:54.000Z',
1502 'id': '2-1',
1503 'info': {
1504 'amount': '0.0006',
1505 'category': 'exchange',
1506 'date': '2018-03-25 15:15:54',
1507 'fee': '0.00150000',
1508 'globalTradeID': 1,
1509 'orderNumber': '2',
1510 'rate': '0.1',
1511 'total': '0.00006',
1512 'tradeID': '2-1',
1513 'type': 'sell'
1514 },
1515 'order': '2',
1516 'price': 0.1,
1517 'side': 'sell',
1518 'symbol': 'ETH/BTC',
1519 'timestamp': 1521983754,
1520 'type': 'limit'
1521 },
1522 { # Wrong side 2
1523 'amount': 0.0004,
1524 'cost': 0.00004,
1525 'datetime': '2018-03-25T15:16:54.000Z',
1526 'id': '2-2',
1527 'info': {
1528 'amount': '0.0004',
1529 'category': 'exchange',
1530 'date': '2018-03-25 15:16:54',
1531 'fee': '0.00150000',
1532 'globalTradeID': 2,
1533 'orderNumber': '2',
1534 'rate': '0.1',
1535 'total': '0.00004',
1536 'tradeID': '2-2',
1537 'type': 'buy'
1538 },
1539 'order': '2',
1540 'price': 0.1,
1541 'side': 'buy',
1542 'symbol': 'ETH/BTC',
1543 'timestamp': 1521983814,
1544 'type': 'limit'
1545 },
1546 { # Margin trade 1
1547 'amount': 0.0006,
1548 'cost': 0.00006,
1549 'datetime': '2018-03-25T15:15:54.000Z',
1550 'id': '3-1',
1551 'info': {
1552 'amount': '0.0006',
1553 'category': 'marginTrade',
1554 'date': '2018-03-25 15:15:54',
1555 'fee': '0.00150000',
1556 'globalTradeID': 1,
1557 'orderNumber': '3',
1558 'rate': '0.1',
1559 'total': '0.00006',
1560 'tradeID': '3-1',
1561 'type': 'buy'
1562 },
1563 'order': '3',
1564 'price': 0.1,
1565 'side': 'buy',
1566 'symbol': 'ETH/BTC',
1567 'timestamp': 1521983754,
1568 'type': 'limit'
1569 },
1570 { # Margin trade 2
1571 'amount': 0.0004,
1572 'cost': 0.00004,
1573 'datetime': '2018-03-25T15:16:54.000Z',
1574 'id': '3-2',
1575 'info': {
1576 'amount': '0.0004',
1577 'category': 'marginTrade',
1578 'date': '2018-03-25 15:16:54',
1579 'fee': '0.00150000',
1580 'globalTradeID': 2,
1581 'orderNumber': '3',
1582 'rate': '0.1',
1583 'total': '0.00004',
1584 'tradeID': '3-2',
1585 'type': 'buy'
1586 },
1587 'order': '3',
1588 'price': 0.1,
1589 'side': 'buy',
1590 'symbol': 'ETH/BTC',
1591 'timestamp': 1521983814,
1592 'type': 'limit'
1593 },
1594 { # Wrong amount 1
1595 'amount': 0.0005,
1596 'cost': 0.00005,
1597 'datetime': '2018-03-25T15:15:54.000Z',
1598 'id': '4-1',
1599 'info': {
1600 'amount': '0.0005',
1601 'category': 'exchange',
1602 'date': '2018-03-25 15:15:54',
1603 'fee': '0.00150000',
1604 'globalTradeID': 1,
1605 'orderNumber': '4',
1606 'rate': '0.1',
1607 'total': '0.00005',
1608 'tradeID': '4-1',
1609 'type': 'buy'
1610 },
1611 'order': '4',
1612 'price': 0.1,
1613 'side': 'buy',
1614 'symbol': 'ETH/BTC',
1615 'timestamp': 1521983754,
1616 'type': 'limit'
1617 },
1618 { # Wrong amount 2
1619 'amount': 0.0004,
1620 'cost': 0.00004,
1621 'datetime': '2018-03-25T15:16:54.000Z',
1622 'id': '4-2',
1623 'info': {
1624 'amount': '0.0004',
1625 'category': 'exchange',
1626 'date': '2018-03-25 15:16:54',
1627 'fee': '0.00150000',
1628 'globalTradeID': 2,
1629 'orderNumber': '4',
1630 'rate': '0.1',
1631 'total': '0.00004',
1632 'tradeID': '4-2',
1633 'type': 'buy'
1634 },
1635 'order': '4',
1636 'price': 0.1,
1637 'side': 'buy',
1638 'symbol': 'ETH/BTC',
1639 'timestamp': 1521983814,
1640 'type': 'limit'
1641 },
1642 { # Wrong price 1
1643 'amount': 0.0006,
1644 'cost': 0.000066,
1645 'datetime': '2018-03-25T15:15:54.000Z',
1646 'id': '5-1',
1647 'info': {
1648 'amount': '0.0006',
1649 'category': 'exchange',
1650 'date': '2018-03-25 15:15:54',
1651 'fee': '0.00150000',
1652 'globalTradeID': 1,
1653 'orderNumber': '5',
1654 'rate': '0.11',
1655 'total': '0.000066',
1656 'tradeID': '5-1',
1657 'type': 'buy'
1658 },
1659 'order': '5',
1660 'price': 0.11,
1661 'side': 'buy',
1662 'symbol': 'ETH/BTC',
1663 'timestamp': 1521983754,
1664 'type': 'limit'
1665 },
1666 { # Wrong price 2
1667 'amount': 0.0004,
1668 'cost': 0.00004,
1669 'datetime': '2018-03-25T15:16:54.000Z',
1670 'id': '5-2',
1671 'info': {
1672 'amount': '0.0004',
1673 'category': 'exchange',
1674 'date': '2018-03-25 15:16:54',
1675 'fee': '0.00150000',
1676 'globalTradeID': 2,
1677 'orderNumber': '5',
1678 'rate': '0.1',
1679 'total': '0.00004',
1680 'tradeID': '5-2',
1681 'type': 'buy'
1682 },
1683 'order': '5',
1684 'price': 0.1,
1685 'side': 'buy',
1686 'symbol': 'ETH/BTC',
1687 'timestamp': 1521983814,
1688 'type': 'limit'
1689 },
1690 { # All good 1
1691 'amount': 0.0006,
1692 'cost': 0.00006,
1693 'datetime': '2018-03-25T15:15:54.000Z',
1694 'id': '7-1',
1695 'info': {
1696 'amount': '0.0006',
1697 'category': 'exchange',
1698 'date': '2018-03-25 15:15:54',
1699 'fee': '0.00150000',
1700 'globalTradeID': 1,
1701 'orderNumber': '7',
1702 'rate': '0.1',
1703 'total': '0.00006',
1704 'tradeID': '7-1',
1705 'type': 'buy'
1706 },
1707 'order': '7',
1708 'price': 0.1,
1709 'side': 'buy',
1710 'symbol': 'ETH/BTC',
1711 'timestamp': 1521983754,
1712 'type': 'limit'
1713 },
1714 { # All good 2
1715 'amount': 0.0004,
1716 'cost': 0.000036,
1717 'datetime': '2018-03-25T15:16:54.000Z',
1718 'id': '7-2',
1719 'info': {
1720 'amount': '0.0004',
1721 'category': 'exchange',
1722 'date': '2018-03-25 15:16:54',
1723 'fee': '0.00150000',
1724 'globalTradeID': 2,
1725 'orderNumber': '7',
1726 'rate': '0.09',
1727 'total': '0.000036',
1728 'tradeID': '7-2',
1729 'type': 'buy'
1730 },
1731 'order': '7',
1732 'price': 0.09,
1733 'side': 'buy',
1734 'symbol': 'ETH/BTC',
1735 'timestamp': 1521983814,
1736 'type': 'limit'
1737 },
1738 ]
1739
1740 result = order.retrieve_order()
1741 self.assertTrue(result)
1742 self.assertEqual('7', order.results[0]["id"])
1743 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
1744
1745 self.m.reset_mock()
1746 with self.subTest(similar_open_order=False, past_trades=False):
1747 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1748 D("0.1"), "BTC", "long", self.m, "trade")
1749 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1750
1751 self.m.ccxt.order_precision.return_value = 8
1752 self.m.ccxt.fetch_orders.return_value = []
1753 self.m.ccxt.fetch_my_trades.return_value = []
1754 result = order.retrieve_order()
1755 self.assertFalse(result)
1756
3080f31d 1757@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
1758class MouvementTest(WebMockTestCase):
1759 def test_values(self):
1760 mouvement = portfolio.Mouvement("ETH", "BTC", {
1761 "tradeID": 42, "type": "buy", "fee": "0.0015",
1762 "date": "2017-12-30 12:00:12", "rate": "0.1",
1763 "amount": "10", "total": "1"
1764 })
1765 self.assertEqual("ETH", mouvement.currency)
1766 self.assertEqual("BTC", mouvement.base_currency)
1767 self.assertEqual(42, mouvement.id)
1768 self.assertEqual("buy", mouvement.action)
1769 self.assertEqual(D("0.0015"), mouvement.fee_rate)
e7d7c0e5 1770 self.assertEqual(portfolio.datetime.datetime(2017, 12, 30, 12, 0, 12), mouvement.date)
c682bdf4
IB
1771 self.assertEqual(D("0.1"), mouvement.rate)
1772 self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
1773 self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)
1774
1775 mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
1776 self.assertIsNone(mouvement.date)
1777 self.assertIsNone(mouvement.id)
1778 self.assertIsNone(mouvement.action)
1779 self.assertEqual(-1, mouvement.fee_rate)
1780 self.assertEqual(0, mouvement.rate)
1781 self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
1782 self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)
1783
1784 def test__repr(self):
1785 mouvement = portfolio.Mouvement("ETH", "BTC", {
1786 "tradeID": 42, "type": "buy", "fee": "0.0015",
1787 "date": "2017-12-30 12:00:12", "rate": "0.1",
1788 "amount": "10", "total": "1"
1789 })
1790 self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement))
1791
1792 mouvement = portfolio.Mouvement("ETH", "BTC", {
1793 "tradeID": 42, "type": "buy",
1794 "date": "garbage", "rate": "0.1",
1795 "amount": "10", "total": "1"
1796 })
1797 self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement))
1798
1799 def test_as_json(self):
1800 mouvement = portfolio.Mouvement("ETH", "BTC", {
1801 "tradeID": 42, "type": "buy", "fee": "0.0015",
1802 "date": "2017-12-30 12:00:12", "rate": "0.1",
1803 "amount": "10", "total": "1"
1804 })
1805 as_json = mouvement.as_json()
1806
1807 self.assertEqual(D("0.0015"), as_json["fee_rate"])
e7d7c0e5 1808 self.assertEqual(portfolio.datetime.datetime(2017, 12, 30, 12, 0, 12), as_json["date"])
c682bdf4
IB
1809 self.assertEqual("buy", as_json["action"])
1810 self.assertEqual(D("10"), as_json["total"])
1811 self.assertEqual(D("1"), as_json["total_in_base"])
1812 self.assertEqual("BTC", as_json["base_currency"])
1813 self.assertEqual("ETH", as_json["currency"])
1814
3080f31d 1815@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
1816class AmountTest(WebMockTestCase):
1817 def test_values(self):
1818 amount = portfolio.Amount("BTC", "0.65")
1819 self.assertEqual(D("0.65"), amount.value)
1820 self.assertEqual("BTC", amount.currency)
1821
1822 def test_in_currency(self):
1823 amount = portfolio.Amount("ETC", 10)
1824
1825 self.assertEqual(amount, amount.in_currency("ETC", self.m))
1826
1827 with self.subTest(desc="no ticker for currency"):
1828 self.m.get_ticker.return_value = None
1829
1830 self.assertRaises(Exception, amount.in_currency, "ETH", self.m)
1831
1832 with self.subTest(desc="nominal case"):
1833 self.m.get_ticker.return_value = {
1834 "bid": D("0.2"),
1835 "ask": D("0.4"),
1836 "average": D("0.3"),
1837 "foo": "bar",
1838 }
1839 converted_amount = amount.in_currency("ETH", self.m)
1840
1841 self.assertEqual(D("3.0"), converted_amount.value)
1842 self.assertEqual("ETH", converted_amount.currency)
1843 self.assertEqual(amount, converted_amount.linked_to)
1844 self.assertEqual("bar", converted_amount.ticker["foo"])
1845
1846 converted_amount = amount.in_currency("ETH", self.m, action="bid", compute_value="default")
1847 self.assertEqual(D("2"), converted_amount.value)
1848
1849 converted_amount = amount.in_currency("ETH", self.m, compute_value="ask")
1850 self.assertEqual(D("4"), converted_amount.value)
1851
1852 converted_amount = amount.in_currency("ETH", self.m, rate=D("0.02"))
1853 self.assertEqual(D("0.2"), converted_amount.value)
1854
1855 def test__round(self):
1856 amount = portfolio.Amount("BAR", portfolio.D("1.23456789876"))
1857 self.assertEqual(D("1.23456789"), round(amount).value)
1858 self.assertEqual(D("1.23"), round(amount, 2).value)
1859
1860 def test__abs(self):
1861 amount = portfolio.Amount("SC", -120)
1862 self.assertEqual(120, abs(amount).value)
1863 self.assertEqual("SC", abs(amount).currency)
1864
1865 amount = portfolio.Amount("SC", 10)
1866 self.assertEqual(10, abs(amount).value)
1867 self.assertEqual("SC", abs(amount).currency)
1868
1869 def test__add(self):
1870 amount1 = portfolio.Amount("XVG", "12.9")
1871 amount2 = portfolio.Amount("XVG", "13.1")
1872
1873 self.assertEqual(26, (amount1 + amount2).value)
1874 self.assertEqual("XVG", (amount1 + amount2).currency)
1875
1876 amount3 = portfolio.Amount("ETH", "1.6")
1877 with self.assertRaises(Exception):
1878 amount1 + amount3
1879
1880 amount4 = portfolio.Amount("ETH", 0.0)
1881 self.assertEqual(amount1, amount1 + amount4)
1882
1883 self.assertEqual(amount1, amount1 + 0)
1884
1885 def test__radd(self):
1886 amount = portfolio.Amount("XVG", "12.9")
1887
1888 self.assertEqual(amount, 0 + amount)
1889 with self.assertRaises(Exception):
1890 4 + amount
1891
1892 def test__sub(self):
1893 amount1 = portfolio.Amount("XVG", "13.3")
1894 amount2 = portfolio.Amount("XVG", "13.1")
1895
1896 self.assertEqual(D("0.2"), (amount1 - amount2).value)
1897 self.assertEqual("XVG", (amount1 - amount2).currency)
1898
1899 amount3 = portfolio.Amount("ETH", "1.6")
1900 with self.assertRaises(Exception):
1901 amount1 - amount3
1902
1903 amount4 = portfolio.Amount("ETH", 0.0)
1904 self.assertEqual(amount1, amount1 - amount4)
1905
1906 def test__rsub(self):
1907 amount = portfolio.Amount("ETH", "1.6")
1908 with self.assertRaises(Exception):
1909 3 - amount
1910
1911 self.assertEqual(portfolio.Amount("ETH", "-1.6"), 0-amount)
1912
1913 def test__mul(self):
1914 amount = portfolio.Amount("XEM", 11)
1915
1916 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
1917 self.assertEqual(D("33"), (amount * 3).value)
1918
1919 with self.assertRaises(Exception):
1920 amount * amount
1921
1922 def test__rmul(self):
1923 amount = portfolio.Amount("XEM", 11)
1924
1925 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
1926 self.assertEqual(D("33"), (3 * amount).value)
1927
1928 def test__floordiv(self):
1929 amount = portfolio.Amount("XEM", 11)
1930
1931 self.assertEqual(D("5.5"), (amount / 2).value)
1932 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
1933
1934 with self.assertRaises(Exception):
1935 amount / amount
1936
1937 def test__truediv(self):
1938 amount = portfolio.Amount("XEM", 11)
1939
1940 self.assertEqual(D("5.5"), (amount / 2).value)
1941 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
1942
1943 def test__lt(self):
1944 amount1 = portfolio.Amount("BTD", 11.3)
1945 amount2 = portfolio.Amount("BTD", 13.1)
1946
1947 self.assertTrue(amount1 < amount2)
1948 self.assertFalse(amount2 < amount1)
1949 self.assertFalse(amount1 < amount1)
1950
1951 amount3 = portfolio.Amount("BTC", 1.6)
1952 with self.assertRaises(Exception):
1953 amount1 < amount3
1954
1955 def test__le(self):
1956 amount1 = portfolio.Amount("BTD", 11.3)
1957 amount2 = portfolio.Amount("BTD", 13.1)
1958
1959 self.assertTrue(amount1 <= amount2)
1960 self.assertFalse(amount2 <= amount1)
1961 self.assertTrue(amount1 <= amount1)
1962
1963 amount3 = portfolio.Amount("BTC", 1.6)
1964 with self.assertRaises(Exception):
1965 amount1 <= amount3
1966
1967 def test__gt(self):
1968 amount1 = portfolio.Amount("BTD", 11.3)
1969 amount2 = portfolio.Amount("BTD", 13.1)
1970
1971 self.assertTrue(amount2 > amount1)
1972 self.assertFalse(amount1 > amount2)
1973 self.assertFalse(amount1 > amount1)
1974
1975 amount3 = portfolio.Amount("BTC", 1.6)
1976 with self.assertRaises(Exception):
1977 amount3 > amount1
1978
1979 def test__ge(self):
1980 amount1 = portfolio.Amount("BTD", 11.3)
1981 amount2 = portfolio.Amount("BTD", 13.1)
1982
1983 self.assertTrue(amount2 >= amount1)
1984 self.assertFalse(amount1 >= amount2)
1985 self.assertTrue(amount1 >= amount1)
1986
1987 amount3 = portfolio.Amount("BTC", 1.6)
1988 with self.assertRaises(Exception):
1989 amount3 >= amount1
1990
1991 def test__eq(self):
1992 amount1 = portfolio.Amount("BTD", 11.3)
1993 amount2 = portfolio.Amount("BTD", 13.1)
1994 amount3 = portfolio.Amount("BTD", 11.3)
1995
1996 self.assertFalse(amount1 == amount2)
1997 self.assertFalse(amount2 == amount1)
1998 self.assertTrue(amount1 == amount3)
1999 self.assertFalse(amount2 == 0)
2000
2001 amount4 = portfolio.Amount("BTC", 1.6)
2002 with self.assertRaises(Exception):
2003 amount1 == amount4
2004
2005 amount5 = portfolio.Amount("BTD", 0)
2006 self.assertTrue(amount5 == 0)
2007
2008 def test__ne(self):
2009 amount1 = portfolio.Amount("BTD", 11.3)
2010 amount2 = portfolio.Amount("BTD", 13.1)
2011 amount3 = portfolio.Amount("BTD", 11.3)
2012
2013 self.assertTrue(amount1 != amount2)
2014 self.assertTrue(amount2 != amount1)
2015 self.assertFalse(amount1 != amount3)
2016 self.assertTrue(amount2 != 0)
2017
2018 amount4 = portfolio.Amount("BTC", 1.6)
2019 with self.assertRaises(Exception):
2020 amount1 != amount4
2021
2022 amount5 = portfolio.Amount("BTD", 0)
2023 self.assertFalse(amount5 != 0)
2024
2025 def test__neg(self):
2026 amount1 = portfolio.Amount("BTD", "11.3")
2027
2028 self.assertEqual(portfolio.D("-11.3"), (-amount1).value)
2029
2030 def test__str(self):
2031 amount1 = portfolio.Amount("BTX", 32)
2032 self.assertEqual("32.00000000 BTX", str(amount1))
2033
2034 amount2 = portfolio.Amount("USDT", 12000)
2035 amount1.linked_to = amount2
2036 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
2037
2038 def test__repr(self):
2039 amount1 = portfolio.Amount("BTX", 32)
2040 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
2041
2042 amount2 = portfolio.Amount("USDT", 12000)
2043 amount1.linked_to = amount2
2044 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
2045
2046 amount3 = portfolio.Amount("BTC", 0.1)
2047 amount2.linked_to = amount3
2048 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
2049
2050 def test_as_json(self):
2051 amount = portfolio.Amount("BTX", 32)
2052 self.assertEqual({"currency": "BTX", "value": D("32")}, amount.as_json())
2053
2054 amount = portfolio.Amount("BTX", "1E-10")
2055 self.assertEqual({"currency": "BTX", "value": D("0")}, amount.as_json())
2056
2057 amount = portfolio.Amount("BTX", "1E-5")
2058 self.assertEqual({"currency": "BTX", "value": D("0.00001")}, amount.as_json())
2059 self.assertEqual("0.00001", str(amount.as_json()["value"]))
2060
2061