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