]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blame - tests/test_portfolio.py
Fix price imprecision due to floats
[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
145 order1.filled_amount.assert_called_with(in_base_currency=True)
146 order2.filled_amount.assert_called_with(in_base_currency=True)
147 order3.filled_amount.assert_called_with(in_base_currency=True)
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
172 order1.filled_amount.assert_called_with(in_base_currency=False)
173 order2.filled_amount.assert_called_with(in_base_currency=False)
174 order3.filled_amount.assert_called_with(in_base_currency=False)
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())
192 order1.filled_amount.assert_called_with(in_base_currency=False)
193 order2.filled_amount.assert_called_with(in_base_currency=False)
194
195 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
196 order1.filled_amount.assert_called_with(in_base_currency=False)
197 order2.filled_amount.assert_called_with(in_base_currency=False)
198
199 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
200 order1.filled_amount.assert_called_with(in_base_currency=True)
201 order2.filled_amount.assert_called_with(in_base_currency=True)
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
826 def test_dust_amount_remaining(self):
827 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
828 D("0.1"), "BTC", "long", self.m, "trade")
829 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
830 self.assertFalse(order.dust_amount_remaining())
831
832 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001")))
833 self.assertTrue(order.dust_amount_remaining())
834
835 @mock.patch.object(portfolio.Order, "fetch")
836 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
837 def test_remaining_amount(self, filled_amount, fetch):
838 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
839 D("0.1"), "BTC", "long", self.m, "trade")
840
841 self.assertEqual(9, order.remaining_amount().value)
842
843 order.status = "open"
844 self.assertEqual(9, order.remaining_amount().value)
845
846 @mock.patch.object(portfolio.Order, "fetch")
847 def test_filled_amount(self, fetch):
848 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
849 D("0.1"), "BTC", "long", self.m, "trade")
850 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
851 "tradeID": 42, "type": "buy", "fee": "0.0015",
852 "date": "2017-12-30 12:00:12", "rate": "0.1",
853 "amount": "3", "total": "0.3"
854 }))
855 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
856 "tradeID": 43, "type": "buy", "fee": "0.0015",
857 "date": "2017-12-30 13:00:12", "rate": "0.2",
858 "amount": "2", "total": "0.4"
859 }))
860 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount())
861 fetch.assert_not_called()
862 order.status = "open"
863 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False))
864 fetch.assert_called_once()
865 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))
866
867 def test_fetch_mouvements(self):
868 self.m.ccxt.privatePostReturnOrderTrades.return_value = [
869 {
870 "tradeID": 42, "type": "buy", "fee": "0.0015",
871 "date": "2017-12-30 13:00:12", "rate": "0.1",
872 "amount": "3", "total": "0.3"
873 },
874 {
875 "tradeID": 43, "type": "buy", "fee": "0.0015",
876 "date": "2017-12-30 12:00:12", "rate": "0.2",
877 "amount": "2", "total": "0.4"
878 }
879 ]
880 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
881 D("0.1"), "BTC", "long", self.m, "trade")
882 order.id = 12
883 order.mouvements = ["Foo", "Bar", "Baz"]
884
885 order.fetch_mouvements()
886
887 self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
888 self.assertEqual(2, len(order.mouvements))
889 self.assertEqual(43, order.mouvements[0].id)
890 self.assertEqual(42, order.mouvements[1].id)
891
892 self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
893 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
894 D("0.1"), "BTC", "long", self.m, "trade")
895 order.fetch_mouvements()
896 self.assertEqual(0, len(order.mouvements))
897
898 def test_mark_finished_order(self):
899 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
900 D("0.1"), "BTC", "short", self.m, "trade",
901 close_if_possible=True)
902 order.status = "closed"
903 self.m.debug = False
904
905 order.mark_finished_order()
906 self.m.ccxt.close_margin_position.assert_called_with("ETH", "BTC")
907 self.m.ccxt.close_margin_position.reset_mock()
908
909 order.status = "open"
910 order.mark_finished_order()
911 self.m.ccxt.close_margin_position.assert_not_called()
912
913 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
914 D("0.1"), "BTC", "short", self.m, "trade",
915 close_if_possible=False)
916 order.status = "closed"
917 order.mark_finished_order()
918 self.m.ccxt.close_margin_position.assert_not_called()
919
920 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
921 D("0.1"), "BTC", "short", self.m, "trade",
922 close_if_possible=True)
923 order.status = "closed"
924 order.mark_finished_order()
925 self.m.ccxt.close_margin_position.assert_not_called()
926
927 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
928 D("0.1"), "BTC", "long", self.m, "trade",
929 close_if_possible=True)
930 order.status = "closed"
931 order.mark_finished_order()
932 self.m.ccxt.close_margin_position.assert_not_called()
933
934 self.m.debug = True
935
936 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
937 D("0.1"), "BTC", "short", self.m, "trade",
938 close_if_possible=True)
939 order.status = "closed"
940
941 order.mark_finished_order()
942 self.m.ccxt.close_margin_position.assert_not_called()
943 self.m.report.log_debug_action.assert_called_once()
944
945 @mock.patch.object(portfolio.Order, "fetch_mouvements")
946 @mock.patch.object(portfolio.Order, "mark_disappeared_order")
947 @mock.patch.object(portfolio.Order, "mark_finished_order")
948 def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
949 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
950 D("0.1"), "BTC", "long", self.m, "trade")
951 order.id = 45
952 with self.subTest(debug=True):
953 self.m.debug = True
954 order.fetch()
955 self.m.report.log_debug_action.assert_called_once()
956 self.m.report.log_debug_action.reset_mock()
957 self.m.ccxt.fetch_order.assert_not_called()
958 mark_finished_order.assert_not_called()
959 mark_disappeared_order.assert_not_called()
960 fetch_mouvements.assert_not_called()
961
962 with self.subTest(debug=False):
963 self.m.debug = False
964 self.m.ccxt.fetch_order.return_value = {
965 "status": "foo",
966 "datetime": "timestamp"
967 }
968 order.fetch()
969
970 self.m.ccxt.fetch_order.assert_called_once_with(45)
971 fetch_mouvements.assert_called_once()
972 self.assertEqual("foo", order.status)
973 self.assertEqual("timestamp", order.timestamp)
974 self.assertEqual(1, len(order.results))
975 self.m.report.log_debug_action.assert_not_called()
976 mark_finished_order.assert_called_once()
977 mark_disappeared_order.assert_called_once()
978
979 mark_finished_order.reset_mock()
980 with self.subTest(missing_order=True):
981 self.m.ccxt.fetch_order.side_effect = [
982 portfolio.OrderNotCached,
983 ]
984 order.fetch()
985 self.assertEqual("closed_unknown", order.status)
986 mark_finished_order.assert_called_once()
987
988 def test_mark_disappeared_order(self):
989 with self.subTest("Open order"):
990 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
991 D("0.1"), "BTC", "long", self.m, "trade")
992 order.id = 45
993 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
994 "tradeID":21336541,
995 "currencyPair":"BTC_XRP",
996 "type":"sell",
997 "rate":"0.00007013",
998 "amount":"0.00000222",
999 "total":"0.00000000",
1000 "fee":"0.00150000",
1001 "date":"2018-04-02 00:09:13"
1002 }))
1003 order.mark_disappeared_order()
1004 self.assertEqual("pending", order.status)
1005
1006 with self.subTest("Non-zero amount"):
1007 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1008 D("0.1"), "BTC", "long", self.m, "trade")
1009 order.id = 45
1010 order.status = "closed"
1011 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1012 "tradeID":21336541,
1013 "currencyPair":"BTC_XRP",
1014 "type":"sell",
1015 "rate":"0.00007013",
1016 "amount":"0.00000222",
1017 "total":"0.00000010",
1018 "fee":"0.00150000",
1019 "date":"2018-04-02 00:09:13"
1020 }))
1021 order.mark_disappeared_order()
1022 self.assertEqual("closed", order.status)
1023
1024 with self.subTest("Other mouvements"):
1025 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1026 D("0.1"), "BTC", "long", self.m, "trade")
1027 order.id = 45
1028 order.status = "closed"
1029 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1030 "tradeID":21336541,
1031 "currencyPair":"BTC_XRP",
1032 "type":"sell",
1033 "rate":"0.00007013",
1034 "amount":"0.00000222",
1035 "total":"0.00000001",
1036 "fee":"0.00150000",
1037 "date":"2018-04-02 00:09:13"
1038 }))
1039 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1040 "tradeID":21336541,
1041 "currencyPair":"BTC_XRP",
1042 "type":"sell",
1043 "rate":"0.00007013",
1044 "amount":"0.00000222",
1045 "total":"0.00000000",
1046 "fee":"0.00150000",
1047 "date":"2018-04-02 00:09:13"
1048 }))
1049 order.mark_disappeared_order()
1050 self.assertEqual("error_disappeared", order.status)
1051
1052 with self.subTest("Order disappeared"):
1053 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1054 D("0.1"), "BTC", "long", self.m, "trade")
1055 order.id = 45
1056 order.status = "closed"
1057 order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
1058 "tradeID":21336541,
1059 "currencyPair":"BTC_XRP",
1060 "type":"sell",
1061 "rate":"0.00007013",
1062 "amount":"0.00000222",
1063 "total":"0.00000000",
1064 "fee":"0.00150000",
1065 "date":"2018-04-02 00:09:13"
1066 }))
1067 order.mark_disappeared_order()
1068 self.assertEqual("error_disappeared", order.status)
1069
1070 @mock.patch.object(portfolio.Order, "fetch")
1071 def test_get_status(self, fetch):
1072 with self.subTest(debug=True):
1073 self.m.debug = True
1074 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1075 D("0.1"), "BTC", "long", self.m, "trade")
1076 self.assertEqual("pending", order.get_status())
1077 fetch.assert_not_called()
1078 self.m.report.log_debug_action.assert_called_once()
1079
1080 with self.subTest(debug=False, finished=False):
1081 self.m.debug = False
1082 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1083 D("0.1"), "BTC", "long", self.m, "trade")
1084 def _fetch(order):
1085 def update_status():
1086 order.status = "open"
1087 return update_status
1088 fetch.side_effect = _fetch(order)
1089 self.assertEqual("open", order.get_status())
1090 fetch.assert_called_once()
1091
1092 fetch.reset_mock()
1093 with self.subTest(debug=False, finished=True):
1094 self.m.debug = False
1095 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1096 D("0.1"), "BTC", "long", self.m, "trade")
1097 def _fetch(order):
1098 def update_status():
1099 order.status = "closed"
1100 return update_status
1101 fetch.side_effect = _fetch(order)
1102 self.assertEqual("closed", order.get_status())
1103 fetch.assert_called_once()
1104
1105 def test_run(self):
1106 self.m.ccxt.order_precision.return_value = 4
1107 with self.subTest(debug=True):
1108 self.m.debug = True
1109 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1110 D("0.1"), "BTC", "long", self.m, "trade")
1111 order.run()
1112 self.m.ccxt.create_order.assert_not_called()
1113 self.m.report.log_debug_action.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1114 self.assertEqual("open", order.status)
1115 self.assertEqual(1, len(order.results))
1116 self.assertEqual(-1, order.id)
1117
1118 self.m.ccxt.create_order.reset_mock()
1119 with self.subTest(debug=False):
1120 self.m.debug = False
1121 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1122 D("0.1"), "BTC", "long", self.m, "trade")
1123 self.m.ccxt.create_order.return_value = { "id": 123 }
1124 order.run()
1125 self.m.ccxt.create_order.assert_called_once()
1126 self.assertEqual(1, len(order.results))
1127 self.assertEqual("open", order.status)
1128
1129 self.m.ccxt.create_order.reset_mock()
1130 with self.subTest(exception=True):
1131 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1132 D("0.1"), "BTC", "long", self.m, "trade")
1133 self.m.ccxt.create_order.side_effect = Exception("bouh")
1134 order.run()
1135 self.m.ccxt.create_order.assert_called_once()
1136 self.assertEqual(0, len(order.results))
1137 self.assertEqual("error", order.status)
1138 self.m.report.log_error.assert_called_once()
1139
1140 self.m.ccxt.create_order.reset_mock()
1141 with self.subTest(dust_amount_exception=True),\
1142 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1143 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
1144 D("0.1"), "BTC", "long", self.m, "trade")
1145 self.m.ccxt.create_order.side_effect = portfolio.InvalidOrder
1146 order.run()
1147 self.m.ccxt.create_order.assert_called_once()
1148 self.assertEqual(0, len(order.results))
1149 self.assertEqual("closed", order.status)
1150 mark_finished_order.assert_called_once()
1151
1152 self.m.ccxt.order_precision.return_value = 8
1153 self.m.ccxt.create_order.reset_mock()
1154 with self.subTest(insufficient_funds=True),\
1155 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1156 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1157 D("0.1"), "BTC", "long", self.m, "trade")
1158 self.m.ccxt.create_order.side_effect = [
1159 portfolio.InsufficientFunds,
1160 portfolio.InsufficientFunds,
1161 portfolio.InsufficientFunds,
1162 { "id": 123 },
1163 ]
1164 order.run()
1165 self.m.ccxt.create_order.assert_has_calls([
1166 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1167 mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
1168 mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
1169 mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
1170 ])
1171 self.assertEqual(4, self.m.ccxt.create_order.call_count)
1172 self.assertEqual(1, len(order.results))
1173 self.assertEqual("open", order.status)
1174 self.assertEqual(4, order.tries)
1175 self.m.report.log_error.assert_called()
1176 self.assertEqual(4, self.m.report.log_error.call_count)
1177
1178 self.m.ccxt.order_precision.return_value = 8
1179 self.m.ccxt.create_order.reset_mock()
1180 self.m.report.log_error.reset_mock()
1181 with self.subTest(insufficient_funds=True),\
1182 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1183 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1184 D("0.1"), "BTC", "long", self.m, "trade")
1185 self.m.ccxt.create_order.side_effect = [
1186 portfolio.InsufficientFunds,
1187 portfolio.InsufficientFunds,
1188 portfolio.InsufficientFunds,
1189 portfolio.InsufficientFunds,
1190 portfolio.InsufficientFunds,
1191 ]
1192 order.run()
1193 self.m.ccxt.create_order.assert_has_calls([
1194 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1195 mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
1196 mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
1197 mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
1198 mock.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account='exchange', price=D('0.1')),
1199 ])
1200 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1201 self.assertEqual(0, len(order.results))
1202 self.assertEqual("error", order.status)
1203 self.assertEqual(5, order.tries)
1204 self.m.report.log_error.assert_called()
1205 self.assertEqual(5, self.m.report.log_error.call_count)
1206 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)
1207
1208 self.m.reset_mock()
1209 with self.subTest(invalid_nonce=True):
1210 with self.subTest(retry_success=True):
1211 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1212 D("0.1"), "BTC", "long", self.m, "trade")
1213 self.m.ccxt.create_order.side_effect = [
1214 portfolio.InvalidNonce,
1215 portfolio.InvalidNonce,
1216 { "id": 123 },
1217 ]
1218 order.run()
1219 self.m.ccxt.create_order.assert_has_calls([
1220 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1221 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1222 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1223 ])
1224 self.assertEqual(3, self.m.ccxt.create_order.call_count)
1225 self.assertEqual(3, order.tries)
1226 self.m.report.log_error.assert_called()
1227 self.assertEqual(2, self.m.report.log_error.call_count)
1228 self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after invalid nonce", exception=mock.ANY)
1229 self.assertEqual(123, order.id)
1230
1231 self.m.reset_mock()
1232 with self.subTest(retry_success=False):
1233 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1234 D("0.1"), "BTC", "long", self.m, "trade")
1235 self.m.ccxt.create_order.side_effect = [
1236 portfolio.InvalidNonce,
1237 portfolio.InvalidNonce,
1238 portfolio.InvalidNonce,
1239 portfolio.InvalidNonce,
1240 portfolio.InvalidNonce,
1241 ]
1242 order.run()
1243 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1244 self.assertEqual(5, order.tries)
1245 self.m.report.log_error.assert_called()
1246 self.assertEqual(5, self.m.report.log_error.call_count)
1247 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)
1248 self.assertEqual("error", order.status)
1249
1250 self.m.reset_mock()
1251 with self.subTest(request_timeout=True):
1252 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1253 D("0.1"), "BTC", "long", self.m, "trade")
1254 with self.subTest(retrieved=False), \
1255 mock.patch.object(order, "retrieve_order") as retrieve:
1256 self.m.ccxt.create_order.side_effect = [
1257 portfolio.RequestTimeout,
1258 portfolio.RequestTimeout,
1259 { "id": 123 },
1260 ]
1261 retrieve.return_value = False
1262 order.run()
1263 self.m.ccxt.create_order.assert_has_calls([
1264 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1265 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1266 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1267 ])
1268 self.assertEqual(3, self.m.ccxt.create_order.call_count)
1269 self.assertEqual(3, order.tries)
1270 self.m.report.log_error.assert_called()
1271 self.assertEqual(2, self.m.report.log_error.call_count)
1272 self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after timeout", exception=mock.ANY)
1273 self.assertEqual(123, order.id)
1274
1275 self.m.reset_mock()
1276 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1277 D("0.1"), "BTC", "long", self.m, "trade")
1278 with self.subTest(retrieved=True), \
1279 mock.patch.object(order, "retrieve_order") as retrieve:
1280 self.m.ccxt.create_order.side_effect = [
1281 portfolio.RequestTimeout,
1282 ]
1283 def _retrieve():
1284 order.results.append({"id": 123})
1285 return True
1286 retrieve.side_effect = _retrieve
1287 order.run()
1288 self.m.ccxt.create_order.assert_has_calls([
1289 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1290 ])
1291 self.assertEqual(1, self.m.ccxt.create_order.call_count)
1292 self.assertEqual(1, order.tries)
1293 self.m.report.log_error.assert_called()
1294 self.assertEqual(1, self.m.report.log_error.call_count)
1295 self.m.report.log_error.assert_called_with(mock.ANY, message="Timeout, found the order")
1296 self.assertEqual(123, order.id)
1297
1298 self.m.reset_mock()
1299 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1300 D("0.1"), "BTC", "long", self.m, "trade")
1301 with self.subTest(retrieved=False), \
1302 mock.patch.object(order, "retrieve_order") as retrieve:
1303 self.m.ccxt.create_order.side_effect = [
1304 portfolio.RequestTimeout,
1305 portfolio.RequestTimeout,
1306 portfolio.RequestTimeout,
1307 portfolio.RequestTimeout,
1308 portfolio.RequestTimeout,
1309 ]
1310 retrieve.return_value = False
1311 order.run()
1312 self.m.ccxt.create_order.assert_has_calls([
1313 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1314 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1315 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1316 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1317 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
1318 ])
1319 self.assertEqual(5, self.m.ccxt.create_order.call_count)
1320 self.assertEqual(5, order.tries)
1321 self.m.report.log_error.assert_called()
1322 self.assertEqual(5, self.m.report.log_error.call_count)
1323 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)
1324 self.assertEqual("error", order.status)
1325
1326 def test_retrieve_order(self):
1327 with self.subTest(similar_open_order=True):
1328 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1329 D("0.1"), "BTC", "long", self.m, "trade")
1330 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1331
1332 self.m.ccxt.order_precision.return_value = 8
1333 self.m.ccxt.fetch_orders.return_value = [
1334 { # Wrong amount
1335 'amount': 0.002, 'cost': 0.1,
1336 'datetime': '2018-03-25T15:15:51.000Z',
1337 'fee': None, 'filled': 0.0,
1338 'id': '1',
1339 'info': {
1340 'amount': '0.002',
1341 'date': '2018-03-25 15:15:51',
1342 'margin': 0, 'orderNumber': '1',
1343 'price': '0.1', 'rate': '0.1',
1344 'side': 'buy', 'startingAmount': '0.002',
1345 'status': 'open', 'total': '0.0002',
1346 'type': 'limit'
1347 },
1348 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1349 'status': 'open', 'symbol': 'ETH/BTC',
1350 'timestamp': 1521990951000, 'trades': None,
1351 'type': 'limit'
1352 },
1353 { # Margin
1354 'amount': 0.001, 'cost': 0.1,
1355 'datetime': '2018-03-25T15:15:51.000Z',
1356 'fee': None, 'filled': 0.0,
1357 'id': '2',
1358 'info': {
1359 'amount': '0.001',
1360 'date': '2018-03-25 15:15:51',
1361 'margin': 1, 'orderNumber': '2',
1362 'price': '0.1', 'rate': '0.1',
1363 'side': 'buy', 'startingAmount': '0.001',
1364 'status': 'open', 'total': '0.0001',
1365 'type': 'limit'
1366 },
1367 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1368 'status': 'open', 'symbol': 'ETH/BTC',
1369 'timestamp': 1521990951000, 'trades': None,
1370 'type': 'limit'
1371 },
1372 { # selling
1373 'amount': 0.001, 'cost': 0.1,
1374 'datetime': '2018-03-25T15:15:51.000Z',
1375 'fee': None, 'filled': 0.0,
1376 'id': '3',
1377 'info': {
1378 'amount': '0.001',
1379 'date': '2018-03-25 15:15:51',
1380 'margin': 0, 'orderNumber': '3',
1381 'price': '0.1', 'rate': '0.1',
1382 'side': 'sell', 'startingAmount': '0.001',
1383 'status': 'open', 'total': '0.0001',
1384 'type': 'limit'
1385 },
1386 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1387 'status': 'open', 'symbol': 'ETH/BTC',
1388 'timestamp': 1521990951000, 'trades': None,
1389 'type': 'limit'
1390 },
1391 { # Wrong rate
1392 'amount': 0.001, 'cost': 0.15,
1393 'datetime': '2018-03-25T15:15:51.000Z',
1394 'fee': None, 'filled': 0.0,
1395 'id': '4',
1396 'info': {
1397 'amount': '0.001',
1398 'date': '2018-03-25 15:15:51',
1399 'margin': 0, 'orderNumber': '4',
1400 'price': '0.15', 'rate': '0.15',
1401 'side': 'buy', 'startingAmount': '0.001',
1402 'status': 'open', 'total': '0.0001',
1403 'type': 'limit'
1404 },
1405 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1406 'status': 'open', 'symbol': 'ETH/BTC',
1407 'timestamp': 1521990951000, 'trades': None,
1408 'type': 'limit'
1409 },
1410 { # All good
1411 'amount': 0.001, 'cost': 0.1,
1412 'datetime': '2018-03-25T15:15:51.000Z',
1413 'fee': None, 'filled': 0.0,
1414 'id': '5',
1415 'info': {
1416 'amount': '0.001',
1417 'date': '2018-03-25 15:15:51',
1418 'margin': 0, 'orderNumber': '1',
1419 'price': '0.1', 'rate': '0.1',
1420 'side': 'buy', 'startingAmount': '0.001',
1421 'status': 'open', 'total': '0.0001',
1422 'type': 'limit'
1423 },
1424 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1425 'status': 'open', 'symbol': 'ETH/BTC',
1426 'timestamp': 1521990951000, 'trades': None,
1427 'type': 'limit'
1428 }
1429 ]
1430 result = order.retrieve_order()
1431 self.assertTrue(result)
1432 self.assertEqual('5', order.results[0]["id"])
1433 self.m.ccxt.fetch_my_trades.assert_not_called()
1434 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
1435
1436 self.m.reset_mock()
1437 with self.subTest(similar_open_order=False, past_trades=True):
1438 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1439 D("0.1"), "BTC", "long", self.m, "trade")
1440 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1441
1442 self.m.ccxt.order_precision.return_value = 8
1443 self.m.ccxt.fetch_orders.return_value = []
1444 self.m.ccxt.fetch_my_trades.return_value = [
1445 { # Wrong timestamp 1
1446 'amount': 0.0006,
1447 'cost': 0.00006,
1448 'datetime': '2018-03-25T15:15:14.000Z',
1449 'id': '1-1',
1450 'info': {
1451 'amount': '0.0006',
1452 'category': 'exchange',
1453 'date': '2018-03-25 15:15:14',
1454 'fee': '0.00150000',
1455 'globalTradeID': 1,
1456 'orderNumber': '1',
1457 'rate': '0.1',
1458 'total': '0.00006',
1459 'tradeID': '1-1',
1460 'type': 'buy'
1461 },
1462 'order': '1',
1463 'price': 0.1,
1464 'side': 'buy',
1465 'symbol': 'ETH/BTC',
1466 'timestamp': 1521983714,
1467 'type': 'limit'
1468 },
1469 { # Wrong timestamp 2
1470 'amount': 0.0004,
1471 'cost': 0.00004,
1472 'datetime': '2018-03-25T15:16:54.000Z',
1473 'id': '1-2',
1474 'info': {
1475 'amount': '0.0004',
1476 'category': 'exchange',
1477 'date': '2018-03-25 15:16:54',
1478 'fee': '0.00150000',
1479 'globalTradeID': 2,
1480 'orderNumber': '1',
1481 'rate': '0.1',
1482 'total': '0.00004',
1483 'tradeID': '1-2',
1484 'type': 'buy'
1485 },
1486 'order': '1',
1487 'price': 0.1,
1488 'side': 'buy',
1489 'symbol': 'ETH/BTC',
1490 'timestamp': 1521983814,
1491 'type': 'limit'
1492 },
1493 { # Wrong side 1
1494 'amount': 0.0006,
1495 'cost': 0.00006,
1496 'datetime': '2018-03-25T15:15:54.000Z',
1497 'id': '2-1',
1498 'info': {
1499 'amount': '0.0006',
1500 'category': 'exchange',
1501 'date': '2018-03-25 15:15:54',
1502 'fee': '0.00150000',
1503 'globalTradeID': 1,
1504 'orderNumber': '2',
1505 'rate': '0.1',
1506 'total': '0.00006',
1507 'tradeID': '2-1',
1508 'type': 'sell'
1509 },
1510 'order': '2',
1511 'price': 0.1,
1512 'side': 'sell',
1513 'symbol': 'ETH/BTC',
1514 'timestamp': 1521983754,
1515 'type': 'limit'
1516 },
1517 { # Wrong side 2
1518 'amount': 0.0004,
1519 'cost': 0.00004,
1520 'datetime': '2018-03-25T15:16:54.000Z',
1521 'id': '2-2',
1522 'info': {
1523 'amount': '0.0004',
1524 'category': 'exchange',
1525 'date': '2018-03-25 15:16:54',
1526 'fee': '0.00150000',
1527 'globalTradeID': 2,
1528 'orderNumber': '2',
1529 'rate': '0.1',
1530 'total': '0.00004',
1531 'tradeID': '2-2',
1532 'type': 'buy'
1533 },
1534 'order': '2',
1535 'price': 0.1,
1536 'side': 'buy',
1537 'symbol': 'ETH/BTC',
1538 'timestamp': 1521983814,
1539 'type': 'limit'
1540 },
1541 { # Margin trade 1
1542 'amount': 0.0006,
1543 'cost': 0.00006,
1544 'datetime': '2018-03-25T15:15:54.000Z',
1545 'id': '3-1',
1546 'info': {
1547 'amount': '0.0006',
1548 'category': 'marginTrade',
1549 'date': '2018-03-25 15:15:54',
1550 'fee': '0.00150000',
1551 'globalTradeID': 1,
1552 'orderNumber': '3',
1553 'rate': '0.1',
1554 'total': '0.00006',
1555 'tradeID': '3-1',
1556 'type': 'buy'
1557 },
1558 'order': '3',
1559 'price': 0.1,
1560 'side': 'buy',
1561 'symbol': 'ETH/BTC',
1562 'timestamp': 1521983754,
1563 'type': 'limit'
1564 },
1565 { # Margin trade 2
1566 'amount': 0.0004,
1567 'cost': 0.00004,
1568 'datetime': '2018-03-25T15:16:54.000Z',
1569 'id': '3-2',
1570 'info': {
1571 'amount': '0.0004',
1572 'category': 'marginTrade',
1573 'date': '2018-03-25 15:16:54',
1574 'fee': '0.00150000',
1575 'globalTradeID': 2,
1576 'orderNumber': '3',
1577 'rate': '0.1',
1578 'total': '0.00004',
1579 'tradeID': '3-2',
1580 'type': 'buy'
1581 },
1582 'order': '3',
1583 'price': 0.1,
1584 'side': 'buy',
1585 'symbol': 'ETH/BTC',
1586 'timestamp': 1521983814,
1587 'type': 'limit'
1588 },
1589 { # Wrong amount 1
1590 'amount': 0.0005,
1591 'cost': 0.00005,
1592 'datetime': '2018-03-25T15:15:54.000Z',
1593 'id': '4-1',
1594 'info': {
1595 'amount': '0.0005',
1596 'category': 'exchange',
1597 'date': '2018-03-25 15:15:54',
1598 'fee': '0.00150000',
1599 'globalTradeID': 1,
1600 'orderNumber': '4',
1601 'rate': '0.1',
1602 'total': '0.00005',
1603 'tradeID': '4-1',
1604 'type': 'buy'
1605 },
1606 'order': '4',
1607 'price': 0.1,
1608 'side': 'buy',
1609 'symbol': 'ETH/BTC',
1610 'timestamp': 1521983754,
1611 'type': 'limit'
1612 },
1613 { # Wrong amount 2
1614 'amount': 0.0004,
1615 'cost': 0.00004,
1616 'datetime': '2018-03-25T15:16:54.000Z',
1617 'id': '4-2',
1618 'info': {
1619 'amount': '0.0004',
1620 'category': 'exchange',
1621 'date': '2018-03-25 15:16:54',
1622 'fee': '0.00150000',
1623 'globalTradeID': 2,
1624 'orderNumber': '4',
1625 'rate': '0.1',
1626 'total': '0.00004',
1627 'tradeID': '4-2',
1628 'type': 'buy'
1629 },
1630 'order': '4',
1631 'price': 0.1,
1632 'side': 'buy',
1633 'symbol': 'ETH/BTC',
1634 'timestamp': 1521983814,
1635 'type': 'limit'
1636 },
1637 { # Wrong price 1
1638 'amount': 0.0006,
1639 'cost': 0.000066,
1640 'datetime': '2018-03-25T15:15:54.000Z',
1641 'id': '5-1',
1642 'info': {
1643 'amount': '0.0006',
1644 'category': 'exchange',
1645 'date': '2018-03-25 15:15:54',
1646 'fee': '0.00150000',
1647 'globalTradeID': 1,
1648 'orderNumber': '5',
1649 'rate': '0.11',
1650 'total': '0.000066',
1651 'tradeID': '5-1',
1652 'type': 'buy'
1653 },
1654 'order': '5',
1655 'price': 0.11,
1656 'side': 'buy',
1657 'symbol': 'ETH/BTC',
1658 'timestamp': 1521983754,
1659 'type': 'limit'
1660 },
1661 { # Wrong price 2
1662 'amount': 0.0004,
1663 'cost': 0.00004,
1664 'datetime': '2018-03-25T15:16:54.000Z',
1665 'id': '5-2',
1666 'info': {
1667 'amount': '0.0004',
1668 'category': 'exchange',
1669 'date': '2018-03-25 15:16:54',
1670 'fee': '0.00150000',
1671 'globalTradeID': 2,
1672 'orderNumber': '5',
1673 'rate': '0.1',
1674 'total': '0.00004',
1675 'tradeID': '5-2',
1676 'type': 'buy'
1677 },
1678 'order': '5',
1679 'price': 0.1,
1680 'side': 'buy',
1681 'symbol': 'ETH/BTC',
1682 'timestamp': 1521983814,
1683 'type': 'limit'
1684 },
1685 { # All good 1
1686 'amount': 0.0006,
1687 'cost': 0.00006,
1688 'datetime': '2018-03-25T15:15:54.000Z',
1689 'id': '7-1',
1690 'info': {
1691 'amount': '0.0006',
1692 'category': 'exchange',
1693 'date': '2018-03-25 15:15:54',
1694 'fee': '0.00150000',
1695 'globalTradeID': 1,
1696 'orderNumber': '7',
1697 'rate': '0.1',
1698 'total': '0.00006',
1699 'tradeID': '7-1',
1700 'type': 'buy'
1701 },
1702 'order': '7',
1703 'price': 0.1,
1704 'side': 'buy',
1705 'symbol': 'ETH/BTC',
1706 'timestamp': 1521983754,
1707 'type': 'limit'
1708 },
1709 { # All good 2
1710 'amount': 0.0004,
1711 'cost': 0.000036,
1712 'datetime': '2018-03-25T15:16:54.000Z',
1713 'id': '7-2',
1714 'info': {
1715 'amount': '0.0004',
1716 'category': 'exchange',
1717 'date': '2018-03-25 15:16:54',
1718 'fee': '0.00150000',
1719 'globalTradeID': 2,
1720 'orderNumber': '7',
1721 'rate': '0.09',
1722 'total': '0.000036',
1723 'tradeID': '7-2',
1724 'type': 'buy'
1725 },
1726 'order': '7',
1727 'price': 0.09,
1728 'side': 'buy',
1729 'symbol': 'ETH/BTC',
1730 'timestamp': 1521983814,
1731 'type': 'limit'
1732 },
1733 ]
1734
1735 result = order.retrieve_order()
1736 self.assertTrue(result)
1737 self.assertEqual('7', order.results[0]["id"])
1738 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
1739
1740 self.m.reset_mock()
1741 with self.subTest(similar_open_order=False, past_trades=False):
1742 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
1743 D("0.1"), "BTC", "long", self.m, "trade")
1744 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
1745
1746 self.m.ccxt.order_precision.return_value = 8
1747 self.m.ccxt.fetch_orders.return_value = []
1748 self.m.ccxt.fetch_my_trades.return_value = []
1749 result = order.retrieve_order()
1750 self.assertFalse(result)
1751
3080f31d 1752@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
1753class MouvementTest(WebMockTestCase):
1754 def test_values(self):
1755 mouvement = portfolio.Mouvement("ETH", "BTC", {
1756 "tradeID": 42, "type": "buy", "fee": "0.0015",
1757 "date": "2017-12-30 12:00:12", "rate": "0.1",
1758 "amount": "10", "total": "1"
1759 })
1760 self.assertEqual("ETH", mouvement.currency)
1761 self.assertEqual("BTC", mouvement.base_currency)
1762 self.assertEqual(42, mouvement.id)
1763 self.assertEqual("buy", mouvement.action)
1764 self.assertEqual(D("0.0015"), mouvement.fee_rate)
e7d7c0e5 1765 self.assertEqual(portfolio.datetime.datetime(2017, 12, 30, 12, 0, 12), mouvement.date)
c682bdf4
IB
1766 self.assertEqual(D("0.1"), mouvement.rate)
1767 self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
1768 self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)
1769
1770 mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
1771 self.assertIsNone(mouvement.date)
1772 self.assertIsNone(mouvement.id)
1773 self.assertIsNone(mouvement.action)
1774 self.assertEqual(-1, mouvement.fee_rate)
1775 self.assertEqual(0, mouvement.rate)
1776 self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
1777 self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)
1778
1779 def test__repr(self):
1780 mouvement = portfolio.Mouvement("ETH", "BTC", {
1781 "tradeID": 42, "type": "buy", "fee": "0.0015",
1782 "date": "2017-12-30 12:00:12", "rate": "0.1",
1783 "amount": "10", "total": "1"
1784 })
1785 self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement))
1786
1787 mouvement = portfolio.Mouvement("ETH", "BTC", {
1788 "tradeID": 42, "type": "buy",
1789 "date": "garbage", "rate": "0.1",
1790 "amount": "10", "total": "1"
1791 })
1792 self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement))
1793
1794 def test_as_json(self):
1795 mouvement = portfolio.Mouvement("ETH", "BTC", {
1796 "tradeID": 42, "type": "buy", "fee": "0.0015",
1797 "date": "2017-12-30 12:00:12", "rate": "0.1",
1798 "amount": "10", "total": "1"
1799 })
1800 as_json = mouvement.as_json()
1801
1802 self.assertEqual(D("0.0015"), as_json["fee_rate"])
e7d7c0e5 1803 self.assertEqual(portfolio.datetime.datetime(2017, 12, 30, 12, 0, 12), as_json["date"])
c682bdf4
IB
1804 self.assertEqual("buy", as_json["action"])
1805 self.assertEqual(D("10"), as_json["total"])
1806 self.assertEqual(D("1"), as_json["total_in_base"])
1807 self.assertEqual("BTC", as_json["base_currency"])
1808 self.assertEqual("ETH", as_json["currency"])
1809
3080f31d 1810@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
1811class AmountTest(WebMockTestCase):
1812 def test_values(self):
1813 amount = portfolio.Amount("BTC", "0.65")
1814 self.assertEqual(D("0.65"), amount.value)
1815 self.assertEqual("BTC", amount.currency)
1816
1817 def test_in_currency(self):
1818 amount = portfolio.Amount("ETC", 10)
1819
1820 self.assertEqual(amount, amount.in_currency("ETC", self.m))
1821
1822 with self.subTest(desc="no ticker for currency"):
1823 self.m.get_ticker.return_value = None
1824
1825 self.assertRaises(Exception, amount.in_currency, "ETH", self.m)
1826
1827 with self.subTest(desc="nominal case"):
1828 self.m.get_ticker.return_value = {
1829 "bid": D("0.2"),
1830 "ask": D("0.4"),
1831 "average": D("0.3"),
1832 "foo": "bar",
1833 }
1834 converted_amount = amount.in_currency("ETH", self.m)
1835
1836 self.assertEqual(D("3.0"), converted_amount.value)
1837 self.assertEqual("ETH", converted_amount.currency)
1838 self.assertEqual(amount, converted_amount.linked_to)
1839 self.assertEqual("bar", converted_amount.ticker["foo"])
1840
1841 converted_amount = amount.in_currency("ETH", self.m, action="bid", compute_value="default")
1842 self.assertEqual(D("2"), converted_amount.value)
1843
1844 converted_amount = amount.in_currency("ETH", self.m, compute_value="ask")
1845 self.assertEqual(D("4"), converted_amount.value)
1846
1847 converted_amount = amount.in_currency("ETH", self.m, rate=D("0.02"))
1848 self.assertEqual(D("0.2"), converted_amount.value)
1849
1850 def test__round(self):
1851 amount = portfolio.Amount("BAR", portfolio.D("1.23456789876"))
1852 self.assertEqual(D("1.23456789"), round(amount).value)
1853 self.assertEqual(D("1.23"), round(amount, 2).value)
1854
1855 def test__abs(self):
1856 amount = portfolio.Amount("SC", -120)
1857 self.assertEqual(120, abs(amount).value)
1858 self.assertEqual("SC", abs(amount).currency)
1859
1860 amount = portfolio.Amount("SC", 10)
1861 self.assertEqual(10, abs(amount).value)
1862 self.assertEqual("SC", abs(amount).currency)
1863
1864 def test__add(self):
1865 amount1 = portfolio.Amount("XVG", "12.9")
1866 amount2 = portfolio.Amount("XVG", "13.1")
1867
1868 self.assertEqual(26, (amount1 + amount2).value)
1869 self.assertEqual("XVG", (amount1 + amount2).currency)
1870
1871 amount3 = portfolio.Amount("ETH", "1.6")
1872 with self.assertRaises(Exception):
1873 amount1 + amount3
1874
1875 amount4 = portfolio.Amount("ETH", 0.0)
1876 self.assertEqual(amount1, amount1 + amount4)
1877
1878 self.assertEqual(amount1, amount1 + 0)
1879
1880 def test__radd(self):
1881 amount = portfolio.Amount("XVG", "12.9")
1882
1883 self.assertEqual(amount, 0 + amount)
1884 with self.assertRaises(Exception):
1885 4 + amount
1886
1887 def test__sub(self):
1888 amount1 = portfolio.Amount("XVG", "13.3")
1889 amount2 = portfolio.Amount("XVG", "13.1")
1890
1891 self.assertEqual(D("0.2"), (amount1 - amount2).value)
1892 self.assertEqual("XVG", (amount1 - amount2).currency)
1893
1894 amount3 = portfolio.Amount("ETH", "1.6")
1895 with self.assertRaises(Exception):
1896 amount1 - amount3
1897
1898 amount4 = portfolio.Amount("ETH", 0.0)
1899 self.assertEqual(amount1, amount1 - amount4)
1900
1901 def test__rsub(self):
1902 amount = portfolio.Amount("ETH", "1.6")
1903 with self.assertRaises(Exception):
1904 3 - amount
1905
1906 self.assertEqual(portfolio.Amount("ETH", "-1.6"), 0-amount)
1907
1908 def test__mul(self):
1909 amount = portfolio.Amount("XEM", 11)
1910
1911 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
1912 self.assertEqual(D("33"), (amount * 3).value)
1913
1914 with self.assertRaises(Exception):
1915 amount * amount
1916
1917 def test__rmul(self):
1918 amount = portfolio.Amount("XEM", 11)
1919
1920 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
1921 self.assertEqual(D("33"), (3 * amount).value)
1922
1923 def test__floordiv(self):
1924 amount = portfolio.Amount("XEM", 11)
1925
1926 self.assertEqual(D("5.5"), (amount / 2).value)
1927 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
1928
1929 with self.assertRaises(Exception):
1930 amount / amount
1931
1932 def test__truediv(self):
1933 amount = portfolio.Amount("XEM", 11)
1934
1935 self.assertEqual(D("5.5"), (amount / 2).value)
1936 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
1937
1938 def test__lt(self):
1939 amount1 = portfolio.Amount("BTD", 11.3)
1940 amount2 = portfolio.Amount("BTD", 13.1)
1941
1942 self.assertTrue(amount1 < amount2)
1943 self.assertFalse(amount2 < amount1)
1944 self.assertFalse(amount1 < amount1)
1945
1946 amount3 = portfolio.Amount("BTC", 1.6)
1947 with self.assertRaises(Exception):
1948 amount1 < amount3
1949
1950 def test__le(self):
1951 amount1 = portfolio.Amount("BTD", 11.3)
1952 amount2 = portfolio.Amount("BTD", 13.1)
1953
1954 self.assertTrue(amount1 <= amount2)
1955 self.assertFalse(amount2 <= amount1)
1956 self.assertTrue(amount1 <= amount1)
1957
1958 amount3 = portfolio.Amount("BTC", 1.6)
1959 with self.assertRaises(Exception):
1960 amount1 <= amount3
1961
1962 def test__gt(self):
1963 amount1 = portfolio.Amount("BTD", 11.3)
1964 amount2 = portfolio.Amount("BTD", 13.1)
1965
1966 self.assertTrue(amount2 > amount1)
1967 self.assertFalse(amount1 > amount2)
1968 self.assertFalse(amount1 > amount1)
1969
1970 amount3 = portfolio.Amount("BTC", 1.6)
1971 with self.assertRaises(Exception):
1972 amount3 > amount1
1973
1974 def test__ge(self):
1975 amount1 = portfolio.Amount("BTD", 11.3)
1976 amount2 = portfolio.Amount("BTD", 13.1)
1977
1978 self.assertTrue(amount2 >= amount1)
1979 self.assertFalse(amount1 >= amount2)
1980 self.assertTrue(amount1 >= amount1)
1981
1982 amount3 = portfolio.Amount("BTC", 1.6)
1983 with self.assertRaises(Exception):
1984 amount3 >= amount1
1985
1986 def test__eq(self):
1987 amount1 = portfolio.Amount("BTD", 11.3)
1988 amount2 = portfolio.Amount("BTD", 13.1)
1989 amount3 = portfolio.Amount("BTD", 11.3)
1990
1991 self.assertFalse(amount1 == amount2)
1992 self.assertFalse(amount2 == amount1)
1993 self.assertTrue(amount1 == amount3)
1994 self.assertFalse(amount2 == 0)
1995
1996 amount4 = portfolio.Amount("BTC", 1.6)
1997 with self.assertRaises(Exception):
1998 amount1 == amount4
1999
2000 amount5 = portfolio.Amount("BTD", 0)
2001 self.assertTrue(amount5 == 0)
2002
2003 def test__ne(self):
2004 amount1 = portfolio.Amount("BTD", 11.3)
2005 amount2 = portfolio.Amount("BTD", 13.1)
2006 amount3 = portfolio.Amount("BTD", 11.3)
2007
2008 self.assertTrue(amount1 != amount2)
2009 self.assertTrue(amount2 != amount1)
2010 self.assertFalse(amount1 != amount3)
2011 self.assertTrue(amount2 != 0)
2012
2013 amount4 = portfolio.Amount("BTC", 1.6)
2014 with self.assertRaises(Exception):
2015 amount1 != amount4
2016
2017 amount5 = portfolio.Amount("BTD", 0)
2018 self.assertFalse(amount5 != 0)
2019
2020 def test__neg(self):
2021 amount1 = portfolio.Amount("BTD", "11.3")
2022
2023 self.assertEqual(portfolio.D("-11.3"), (-amount1).value)
2024
2025 def test__str(self):
2026 amount1 = portfolio.Amount("BTX", 32)
2027 self.assertEqual("32.00000000 BTX", str(amount1))
2028
2029 amount2 = portfolio.Amount("USDT", 12000)
2030 amount1.linked_to = amount2
2031 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
2032
2033 def test__repr(self):
2034 amount1 = portfolio.Amount("BTX", 32)
2035 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
2036
2037 amount2 = portfolio.Amount("USDT", 12000)
2038 amount1.linked_to = amount2
2039 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
2040
2041 amount3 = portfolio.Amount("BTC", 0.1)
2042 amount2.linked_to = amount3
2043 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
2044
2045 def test_as_json(self):
2046 amount = portfolio.Amount("BTX", 32)
2047 self.assertEqual({"currency": "BTX", "value": D("32")}, amount.as_json())
2048
2049 amount = portfolio.Amount("BTX", "1E-10")
2050 self.assertEqual({"currency": "BTX", "value": D("0")}, amount.as_json())
2051
2052 amount = portfolio.Amount("BTX", "1E-5")
2053 self.assertEqual({"currency": "BTX", "value": D("0.00001")}, amount.as_json())
2054 self.assertEqual("0.00001", str(amount.as_json()["value"]))
2055
2056