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