5 @unittest.skipUnless("unit" in limits
, "Unit skipped")
6 class ComputationTest(WebMockTestCase
):
7 def test_compute_value(self
):
9 portfolio
.Computation
.compute_value("foo", "buy", compute_value
=compute
)
10 compute
.assert_called_with("foo", "ask")
13 portfolio
.Computation
.compute_value("foo", "sell", compute_value
=compute
)
14 compute
.assert_called_with("foo", "bid")
17 portfolio
.Computation
.compute_value("foo", "ask", compute_value
=compute
)
18 compute
.assert_called_with("foo", "ask")
21 portfolio
.Computation
.compute_value("foo", "bid", compute_value
=compute
)
22 compute
.assert_called_with("foo", "bid")
25 portfolio
.Computation
.computations
["test"] = compute
26 portfolio
.Computation
.compute_value("foo", "bid", compute_value
="test")
27 compute
.assert_called_with("foo", "bid")
29 @unittest.skipUnless("unit" in limits
, "Unit skipped")
30 class TradeTest(WebMockTestCase
):
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
)
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
)
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
)
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
)
63 self
.assertIsNone(trade
.action
)
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
)
70 self
.assertIsNone(trade
.action
)
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
)
77 self
.assertEqual("acquire", trade
.action
)
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
)
84 self
.assertEqual("acquire", trade
.action
)
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
)
92 trade
.inverted
= False
93 self
.assertEqual("buy", trade
.order_action())
95 self
.assertEqual("sell", trade
.order_action())
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
)
102 trade
.inverted
= False
103 self
.assertEqual("sell", trade
.order_action())
104 trade
.inverted
= True
105 self
.assertEqual("buy", trade
.order_action())
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
)
113 self
.assertEqual("long", trade
.trade_type
)
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
)
120 self
.assertEqual("short", trade
.trade_type
)
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
)
130 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
133 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
134 trade
.orders
.append(order1
)
135 trade
.orders
.append(order2
)
137 self
.assertFalse(trade
.is_fullfiled
)
140 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
141 trade
.orders
.append(order3
)
143 self
.assertTrue(trade
.is_fullfiled
)
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)
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
157 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
160 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
161 trade
.orders
.append(order1
)
162 trade
.orders
.append(order2
)
164 self
.assertFalse(trade
.is_fullfiled
)
167 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
168 trade
.orders
.append(order3
)
170 self
.assertTrue(trade
.is_fullfiled
)
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)
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
)
184 order1
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.3")
187 order2
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.01")
188 trade
.orders
.append(order1
)
189 trade
.orders
.append(order2
)
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)
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)
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)
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"
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
)
216 trade
.prepare_order()
218 filled_amount
.assert_not_called()
219 compute_value
.assert_not_called()
220 self
.assertEqual(0, len(trade
.orders
))
221 Order
.assert_not_called()
223 self
.m
.get_ticker
.return_value
= { "inverted": False }
224 with self
.subTest(desc
="Already filled"):
225 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
226 compute_value
.return_value
= D("0.125")
228 value_from
= portfolio
.Amount("BTC", "10")
229 value_from
.rate
= D("0.1")
230 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
231 value_to
= portfolio
.Amount("BTC", "0")
232 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
234 trade
.prepare_order()
236 filled_amount
.assert_called_with(in_base_currency
=False)
237 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
238 self
.assertEqual(0, len(trade
.orders
))
239 self
.m
.report
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
240 Order
.assert_not_called()
242 with self
.subTest(action
="dispose", inverted
=False):
243 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
244 compute_value
.return_value
= D("0.125")
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", "1")
250 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
252 trade
.prepare_order()
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(1, len(trade
.orders
))
257 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
258 D("0.125"), "BTC", "long", self
.m
,
259 trade
, close_if_possible
=False)
261 with self
.subTest(action
="dispose", inverted
=False, close_if_possible
=True):
262 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
263 compute_value
.return_value
= D("0.125")
265 value_from
= portfolio
.Amount("BTC", "10")
266 value_from
.rate
= D("0.1")
267 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
268 value_to
= portfolio
.Amount("BTC", "1")
269 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
271 trade
.prepare_order(close_if_possible
=True)
273 filled_amount
.assert_called_with(in_base_currency
=False)
274 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
275 self
.assertEqual(1, len(trade
.orders
))
276 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
277 D("0.125"), "BTC", "long", self
.m
,
278 trade
, close_if_possible
=True)
280 with self
.subTest(action
="acquire", inverted
=False):
281 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
282 compute_value
.return_value
= D("0.125")
284 value_from
= portfolio
.Amount("BTC", "1")
285 value_from
.rate
= D("0.1")
286 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
287 value_to
= portfolio
.Amount("BTC", "10")
288 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
290 trade
.prepare_order()
292 filled_amount
.assert_called_with(in_base_currency
=True)
293 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "buy", compute_value
="default")
294 self
.assertEqual(1, len(trade
.orders
))
296 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
297 D("0.125"), "BTC", "long", self
.m
,
298 trade
, close_if_possible
=False)
300 with self
.subTest(close_if_possible
=True):
301 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
302 compute_value
.return_value
= D("0.125")
304 value_from
= portfolio
.Amount("BTC", "10")
305 value_from
.rate
= D("0.1")
306 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
307 value_to
= portfolio
.Amount("BTC", "0")
308 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
310 trade
.prepare_order()
312 filled_amount
.assert_called_with(in_base_currency
=False)
313 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
314 self
.assertEqual(1, len(trade
.orders
))
315 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
316 D("0.125"), "BTC", "long", self
.m
,
317 trade
, close_if_possible
=True)
319 self
.m
.get_ticker
.return_value
= { "inverted": True, "original": {}
}
320 with self
.subTest(action
="dispose", inverted
=True):
321 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
322 compute_value
.return_value
= D("125")
324 value_from
= portfolio
.Amount("BTC", "10")
325 value_from
.rate
= D("0.01")
326 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
327 value_to
= portfolio
.Amount("BTC", "1")
328 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
330 trade
.prepare_order(compute_value
="foo")
332 filled_amount
.assert_called_with(in_base_currency
=True)
333 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "buy", compute_value
="foo")
334 self
.assertEqual(1, len(trade
.orders
))
335 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
336 D("125"), "FOO", "long", self
.m
,
337 trade
, close_if_possible
=False)
339 with self
.subTest(action
="acquire", inverted
=True):
340 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
341 compute_value
.return_value
= D("125")
343 value_from
= portfolio
.Amount("BTC", "1")
344 value_from
.rate
= D("0.01")
345 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
346 value_to
= portfolio
.Amount("BTC", "10")
347 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
349 trade
.prepare_order(compute_value
="foo")
351 filled_amount
.assert_called_with(in_base_currency
=False)
352 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "sell", compute_value
="foo")
353 self
.assertEqual(1, len(trade
.orders
))
354 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
355 D("125"), "FOO", "long", self
.m
,
356 trade
, close_if_possible
=False)
358 def test_tick_actions_recreate(self
):
359 value_from
= portfolio
.Amount("BTC", "0.5")
360 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
361 value_to
= portfolio
.Amount("BTC", "1.0")
362 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
364 self
.assertEqual("average", trade
.tick_actions_recreate(0))
365 self
.assertEqual("foo", trade
.tick_actions_recreate(0, default
="foo"))
366 self
.assertEqual("average", trade
.tick_actions_recreate(1))
367 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(2))
368 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(3))
369 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(5))
370 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(6))
371 self
.assertEqual("default", trade
.tick_actions_recreate(7))
372 self
.assertEqual("default", trade
.tick_actions_recreate(8))
374 @mock.patch.object(portfolio
.Trade
, "prepare_order")
375 def test_update_order(self
, prepare_order
):
376 order_mock
= mock
.Mock()
377 new_order_mock
= mock
.Mock()
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 prepare_order
.return_value
= new_order_mock
385 for i
in [0, 1, 3, 4, 6]:
386 with self
.subTest(tick
=i
):
387 trade
.update_order(order_mock
, i
)
388 order_mock
.cancel
.assert_not_called()
389 new_order_mock
.run
.assert_not_called()
390 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
,
391 update
="waiting", compute_value
=None, new_order
=None)
393 order_mock
.reset_mock()
394 new_order_mock
.reset_mock()
396 self
.m
.report
.log_order
.reset_mock()
398 trade
.update_order(order_mock
, 2)
399 order_mock
.cancel
.assert_called()
400 new_order_mock
.run
.assert_called()
401 prepare_order
.assert_called()
402 self
.m
.report
.log_order
.assert_called()
403 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
405 mock
.call(order_mock
, 2, update
="adjusting",
406 compute_value
=mock
.ANY
,
407 new_order
=new_order_mock
),
408 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
410 self
.m
.report
.log_order
.assert_has_calls(calls
)
412 order_mock
.reset_mock()
413 new_order_mock
.reset_mock()
415 self
.m
.report
.log_order
.reset_mock()
417 trade
.update_order(order_mock
, 5)
418 order_mock
.cancel
.assert_called()
419 new_order_mock
.run
.assert_called()
420 prepare_order
.assert_called()
421 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
422 self
.m
.report
.log_order
.assert_called()
424 mock
.call(order_mock
, 5, update
="adjusting",
425 compute_value
=mock
.ANY
,
426 new_order
=new_order_mock
),
427 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
429 self
.m
.report
.log_order
.assert_has_calls(calls
)
431 order_mock
.reset_mock()
432 new_order_mock
.reset_mock()
434 self
.m
.report
.log_order
.reset_mock()
436 trade
.update_order(order_mock
, 7)
437 order_mock
.cancel
.assert_called()
438 new_order_mock
.run
.assert_called()
439 prepare_order
.assert_called_with(compute_value
="default")
440 self
.m
.report
.log_order
.assert_called()
441 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
443 mock
.call(order_mock
, 7, update
="market_fallback",
444 compute_value
='default',
445 new_order
=new_order_mock
),
446 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
448 self
.m
.report
.log_order
.assert_has_calls(calls
)
450 order_mock
.reset_mock()
451 new_order_mock
.reset_mock()
453 self
.m
.report
.log_order
.reset_mock()
455 for i
in [10, 13, 16]:
456 with self
.subTest(tick
=i
):
457 trade
.update_order(order_mock
, i
)
458 order_mock
.cancel
.assert_called()
459 new_order_mock
.run
.assert_called()
460 prepare_order
.assert_called_with(compute_value
="default")
461 self
.m
.report
.log_order
.assert_called()
462 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
464 mock
.call(order_mock
, i
, update
="market_adjust",
465 compute_value
='default',
466 new_order
=new_order_mock
),
467 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
469 self
.m
.report
.log_order
.assert_has_calls(calls
)
471 order_mock
.reset_mock()
472 new_order_mock
.reset_mock()
474 self
.m
.report
.log_order
.reset_mock()
476 for i
in [8, 9, 11, 12]:
477 with self
.subTest(tick
=i
):
478 trade
.update_order(order_mock
, i
)
479 order_mock
.cancel
.assert_not_called()
480 new_order_mock
.run
.assert_not_called()
481 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
482 compute_value
=None, new_order
=None)
484 order_mock
.reset_mock()
485 new_order_mock
.reset_mock()
487 self
.m
.report
.log_order
.reset_mock()
490 def test_print_with_order(self
):
491 value_from
= portfolio
.Amount("BTC", "0.5")
492 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
493 value_to
= portfolio
.Amount("BTC", "1.0")
494 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
496 order_mock1
= mock
.Mock()
497 order_mock1
.__repr__
= mock
.Mock()
498 order_mock1
.__repr__
.return_value
= "Mock 1"
499 order_mock2
= mock
.Mock()
500 order_mock2
.__repr__
= mock
.Mock()
501 order_mock2
.__repr__
.return_value
= "Mock 2"
502 order_mock1
.mouvements
= []
503 mouvement_mock1
= mock
.Mock()
504 mouvement_mock1
.__repr__
= mock
.Mock()
505 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
506 mouvement_mock2
= mock
.Mock()
507 mouvement_mock2
.__repr__
= mock
.Mock()
508 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
509 order_mock2
.mouvements
= [
510 mouvement_mock1
, mouvement_mock2
512 trade
.orders
.append(order_mock1
)
513 trade
.orders
.append(order_mock2
)
515 with mock
.patch
.object(trade
, "filled_amount") as filled
:
516 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
518 trade
.print_with_order()
520 self
.m
.report
.print_log
.assert_called()
521 calls
= self
.m
.report
.print_log
.mock_calls
522 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
523 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
524 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
525 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
526 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
528 self
.m
.report
.print_log
.reset_mock()
530 filled
.return_value
= portfolio
.Amount("BTC", "0.5")
531 trade
.print_with_order()
532 calls
= self
.m
.report
.print_log
.mock_calls
533 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls
[0][1][0]))
535 self
.m
.report
.print_log
.reset_mock()
537 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
539 trade
.print_with_order()
540 calls
= self
.m
.report
.print_log
.mock_calls
541 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls
[0][1][0]))
543 def test_close(self
):
544 value_from
= portfolio
.Amount("BTC", "0.5")
545 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
546 value_to
= portfolio
.Amount("BTC", "1.0")
547 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
549 trade
.orders
.append(order1
)
553 self
.assertEqual(True, trade
.closed
)
554 order1
.cancel
.assert_called_once_with()
556 def test_pending(self
):
557 value_from
= portfolio
.Amount("BTC", "0.5")
558 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
559 value_to
= portfolio
.Amount("BTC", "1.0")
560 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
563 self
.assertEqual(False, trade
.pending
)
566 self
.assertEqual(True, trade
.pending
)
569 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
570 trade
.orders
.append(order1
)
571 self
.assertEqual(False, trade
.pending
)
573 def test__repr(self
):
574 value_from
= portfolio
.Amount("BTC", "0.5")
575 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
576 value_to
= portfolio
.Amount("BTC", "1.0")
577 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
579 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
581 def test_as_json(self
):
582 value_from
= portfolio
.Amount("BTC", "0.5")
583 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
584 value_to
= portfolio
.Amount("BTC", "1.0")
585 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
587 as_json
= trade
.as_json()
588 self
.assertEqual("acquire", as_json
["action"])
589 self
.assertEqual(D("0.5"), as_json
["from"])
590 self
.assertEqual(D("1.0"), as_json
["to"])
591 self
.assertEqual("ETH", as_json
["currency"])
592 self
.assertEqual("BTC", as_json
["base_currency"])
594 @unittest.skipUnless("unit" in limits
, "Unit skipped")
595 class BalanceTest(WebMockTestCase
):
596 def test_values(self
):
597 balance
= portfolio
.Balance("BTC", {
598 "exchange_total": "0.65",
599 "exchange_free": "0.35",
600 "exchange_used": "0.30",
601 "margin_total": "-10",
602 "margin_borrowed": "10",
603 "margin_available": "0",
604 "margin_in_position": "0",
605 "margin_position_type": "short",
606 "margin_borrowed_base_currency": "USDT",
607 "margin_liquidation_price": "1.20",
608 "margin_pending_gain": "10",
609 "margin_lending_fees": "0.4",
610 "margin_borrowed_base_price": "0.15",
612 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
613 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
614 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
615 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
616 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
617 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
619 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
620 self
.assertEqual(portfolio
.D("10"), balance
.margin_borrowed
.value
)
621 self
.assertEqual(portfolio
.D("0"), balance
.margin_available
.value
)
622 self
.assertEqual("BTC", balance
.margin_total
.currency
)
623 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
624 self
.assertEqual("BTC", balance
.margin_available
.currency
)
626 self
.assertEqual("BTC", balance
.currency
)
628 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
629 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
631 def test__repr(self
):
632 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
633 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
634 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
635 "exchange_used": 1, "exchange_free": 2 })
636 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
638 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
639 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
641 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
642 "margin_in_position": 1, "margin_available": 2 })
643 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
645 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
646 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
648 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
649 "margin_borrowed_base_price": D("0.1"),
650 "margin_borrowed_base_currency": "BTC",
651 "margin_lending_fees": D("0.002") })
652 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
654 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
655 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
656 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
658 def test_as_json(self
):
659 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
660 as_json
= balance
.as_json()
661 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
662 self
.assertEqual(D(0), as_json
["total"])
663 self
.assertEqual(D(2), as_json
["exchange_total"])
664 self
.assertEqual(D(2), as_json
["exchange_free"])
665 self
.assertEqual(D(0), as_json
["exchange_used"])
666 self
.assertEqual(D(0), as_json
["margin_total"])
667 self
.assertEqual(D(0), as_json
["margin_available"])
668 self
.assertEqual(D(0), as_json
["margin_borrowed"])
670 @unittest.skipUnless("unit" in limits
, "Unit skipped")
671 class OrderTest(WebMockTestCase
):
672 def test_values(self
):
673 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
674 D("0.1"), "BTC", "long", "market", "trade")
675 self
.assertEqual("buy", order
.action
)
676 self
.assertEqual(10, order
.amount
.value
)
677 self
.assertEqual("ETH", order
.amount
.currency
)
678 self
.assertEqual(D("0.1"), order
.rate
)
679 self
.assertEqual("BTC", order
.base_currency
)
680 self
.assertEqual("market", order
.market
)
681 self
.assertEqual("long", order
.trade_type
)
682 self
.assertEqual("pending", order
.status
)
683 self
.assertEqual("trade", order
.trade
)
684 self
.assertIsNone(order
.id)
685 self
.assertFalse(order
.close_if_possible
)
687 def test__repr(self
):
688 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
689 D("0.1"), "BTC", "long", "market", "trade")
690 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
692 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
693 D("0.1"), "BTC", "long", "market", "trade",
694 close_if_possible
=True)
695 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
697 def test_as_json(self
):
698 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
699 D("0.1"), "BTC", "long", "market", "trade")
700 mouvement_mock1
= mock
.Mock()
701 mouvement_mock1
.as_json
.return_value
= 1
702 mouvement_mock2
= mock
.Mock()
703 mouvement_mock2
.as_json
.return_value
= 2
705 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
706 as_json
= order
.as_json()
707 self
.assertEqual("buy", as_json
["action"])
708 self
.assertEqual("long", as_json
["trade_type"])
709 self
.assertEqual(10, as_json
["amount"])
710 self
.assertEqual("ETH", as_json
["currency"])
711 self
.assertEqual("BTC", as_json
["base_currency"])
712 self
.assertEqual(D("0.1"), as_json
["rate"])
713 self
.assertEqual("pending", as_json
["status"])
714 self
.assertEqual(False, as_json
["close_if_possible"])
715 self
.assertIsNone(as_json
["id"])
716 self
.assertEqual([1, 2], as_json
["mouvements"])
718 def test_account(self
):
719 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
720 D("0.1"), "BTC", "long", "market", "trade")
721 self
.assertEqual("exchange", order
.account
)
723 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
724 D("0.1"), "BTC", "short", "market", "trade")
725 self
.assertEqual("margin", order
.account
)
727 def test_pending(self
):
728 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
729 D("0.1"), "BTC", "long", "market", "trade")
730 self
.assertTrue(order
.pending
)
731 order
.status
= "open"
732 self
.assertFalse(order
.pending
)
735 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
736 D("0.1"), "BTC", "long", "market", "trade")
737 self
.assertFalse(order
.open)
738 order
.status
= "open"
739 self
.assertTrue(order
.open)
741 def test_finished(self
):
742 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
743 D("0.1"), "BTC", "long", "market", "trade")
744 self
.assertFalse(order
.finished
)
745 order
.status
= "closed"
746 self
.assertTrue(order
.finished
)
747 order
.status
= "canceled"
748 self
.assertTrue(order
.finished
)
749 order
.status
= "error"
750 self
.assertTrue(order
.finished
)
752 @mock.patch.object(portfolio
.Order
, "fetch")
753 def test_cancel(self
, fetch
):
754 with self
.subTest(debug
=True):
756 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
757 D("0.1"), "BTC", "long", self
.m
, "trade")
758 order
.status
= "open"
761 self
.m
.ccxt
.cancel_order
.assert_not_called()
762 self
.m
.report
.log_debug_action
.assert_called_once()
763 self
.m
.report
.log_debug_action
.reset_mock()
764 self
.assertEqual("canceled", order
.status
)
766 with self
.subTest(desc
="Nominal case"):
768 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
769 D("0.1"), "BTC", "long", self
.m
, "trade")
770 order
.status
= "open"
774 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
775 fetch
.assert_called_once_with()
776 self
.m
.report
.log_debug_action
.assert_not_called()
778 with self
.subTest(exception
=True):
779 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
780 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
781 D("0.1"), "BTC", "long", self
.m
, "trade")
782 order
.status
= "open"
785 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
786 self
.m
.report
.log_error
.assert_called_once()
789 with self
.subTest(id=None):
790 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
791 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
792 D("0.1"), "BTC", "long", self
.m
, "trade")
793 order
.status
= "open"
795 self
.m
.ccxt
.cancel_order
.assert_not_called()
798 with self
.subTest(open=False):
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
= "closed"
804 self
.m
.ccxt
.cancel_order
.assert_not_called()
806 def test_dust_amount_remaining(self
):
807 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
808 D("0.1"), "BTC", "long", self
.m
, "trade")
809 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", 1))
810 self
.assertFalse(order
.dust_amount_remaining())
812 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", D("0.0001")))
813 self
.assertTrue(order
.dust_amount_remaining())
815 @mock.patch.object(portfolio
.Order
, "fetch")
816 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
817 def test_remaining_amount(self
, filled_amount
, fetch
):
818 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
819 D("0.1"), "BTC", "long", self
.m
, "trade")
821 self
.assertEqual(9, order
.remaining_amount().value
)
823 order
.status
= "open"
824 self
.assertEqual(9, order
.remaining_amount().value
)
826 @mock.patch.object(portfolio
.Order
, "fetch")
827 def test_filled_amount(self
, fetch
):
828 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
829 D("0.1"), "BTC", "long", self
.m
, "trade")
830 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
831 "tradeID": 42, "type": "buy", "fee": "0.0015",
832 "date": "2017-12-30 12:00:12", "rate": "0.1",
833 "amount": "3", "total": "0.3"
835 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
836 "tradeID": 43, "type": "buy", "fee": "0.0015",
837 "date": "2017-12-30 13:00:12", "rate": "0.2",
838 "amount": "2", "total": "0.4"
840 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
841 fetch
.assert_not_called()
842 order
.status
= "open"
843 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
844 fetch
.assert_called_once()
845 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
847 def test_fetch_mouvements(self
):
848 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
850 "tradeID": 42, "type": "buy", "fee": "0.0015",
851 "date": "2017-12-30 13:00:12", "rate": "0.1",
852 "amount": "3", "total": "0.3"
855 "tradeID": 43, "type": "buy", "fee": "0.0015",
856 "date": "2017-12-30 12:00:12", "rate": "0.2",
857 "amount": "2", "total": "0.4"
860 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
861 D("0.1"), "BTC", "long", self
.m
, "trade")
863 order
.mouvements
= ["Foo", "Bar", "Baz"]
865 order
.fetch_mouvements()
867 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
868 self
.assertEqual(2, len(order
.mouvements
))
869 self
.assertEqual(43, order
.mouvements
[0].id)
870 self
.assertEqual(42, order
.mouvements
[1].id)
872 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
873 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
874 D("0.1"), "BTC", "long", self
.m
, "trade")
875 order
.fetch_mouvements()
876 self
.assertEqual(0, len(order
.mouvements
))
878 def test_mark_finished_order(self
):
879 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
880 D("0.1"), "BTC", "short", self
.m
, "trade",
881 close_if_possible
=True)
882 order
.status
= "closed"
885 order
.mark_finished_order()
886 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
887 self
.m
.ccxt
.close_margin_position
.reset_mock()
889 order
.status
= "open"
890 order
.mark_finished_order()
891 self
.m
.ccxt
.close_margin_position
.assert_not_called()
893 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
894 D("0.1"), "BTC", "short", self
.m
, "trade",
895 close_if_possible
=False)
896 order
.status
= "closed"
897 order
.mark_finished_order()
898 self
.m
.ccxt
.close_margin_position
.assert_not_called()
900 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
901 D("0.1"), "BTC", "short", self
.m
, "trade",
902 close_if_possible
=True)
903 order
.status
= "closed"
904 order
.mark_finished_order()
905 self
.m
.ccxt
.close_margin_position
.assert_not_called()
907 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
908 D("0.1"), "BTC", "long", self
.m
, "trade",
909 close_if_possible
=True)
910 order
.status
= "closed"
911 order
.mark_finished_order()
912 self
.m
.ccxt
.close_margin_position
.assert_not_called()
916 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
917 D("0.1"), "BTC", "short", self
.m
, "trade",
918 close_if_possible
=True)
919 order
.status
= "closed"
921 order
.mark_finished_order()
922 self
.m
.ccxt
.close_margin_position
.assert_not_called()
923 self
.m
.report
.log_debug_action
.assert_called_once()
925 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
926 @mock.patch.object(portfolio
.Order
, "mark_disappeared_order")
927 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
928 def test_fetch(self
, mark_finished_order
, mark_disappeared_order
, fetch_mouvements
):
929 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
930 D("0.1"), "BTC", "long", self
.m
, "trade")
932 with self
.subTest(debug
=True):
935 self
.m
.report
.log_debug_action
.assert_called_once()
936 self
.m
.report
.log_debug_action
.reset_mock()
937 self
.m
.ccxt
.fetch_order
.assert_not_called()
938 mark_finished_order
.assert_not_called()
939 mark_disappeared_order
.assert_not_called()
940 fetch_mouvements
.assert_not_called()
942 with self
.subTest(debug
=False):
944 self
.m
.ccxt
.fetch_order
.return_value
= {
946 "datetime": "timestamp"
950 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
951 fetch_mouvements
.assert_called_once()
952 self
.assertEqual("foo", order
.status
)
953 self
.assertEqual("timestamp", order
.timestamp
)
954 self
.assertEqual(1, len(order
.results
))
955 self
.m
.report
.log_debug_action
.assert_not_called()
956 mark_finished_order
.assert_called_once()
957 mark_disappeared_order
.assert_called_once()
959 mark_finished_order
.reset_mock()
960 with self
.subTest(missing_order
=True):
961 self
.m
.ccxt
.fetch_order
.side_effect
= [
962 portfolio
.OrderNotCached
,
965 self
.assertEqual("closed_unknown", order
.status
)
966 mark_finished_order
.assert_called_once()
968 def test_mark_disappeared_order(self
):
969 with self
.subTest("Open order"):
970 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
971 D("0.1"), "BTC", "long", self
.m
, "trade")
973 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
975 "currencyPair":"BTC_XRP",
978 "amount":"0.00000222",
979 "total":"0.00000000",
981 "date":"2018-04-02 00:09:13"
983 order
.mark_disappeared_order()
984 self
.assertEqual("pending", order
.status
)
986 with self
.subTest("Non-zero amount"):
987 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
988 D("0.1"), "BTC", "long", self
.m
, "trade")
990 order
.status
= "closed"
991 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
993 "currencyPair":"BTC_XRP",
996 "amount":"0.00000222",
997 "total":"0.00000010",
999 "date":"2018-04-02 00:09:13"
1001 order
.mark_disappeared_order()
1002 self
.assertEqual("closed", order
.status
)
1004 with self
.subTest("Other mouvements"):
1005 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1006 D("0.1"), "BTC", "long", self
.m
, "trade")
1008 order
.status
= "closed"
1009 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1011 "currencyPair":"BTC_XRP",
1013 "rate":"0.00007013",
1014 "amount":"0.00000222",
1015 "total":"0.00000001",
1017 "date":"2018-04-02 00:09:13"
1019 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1021 "currencyPair":"BTC_XRP",
1023 "rate":"0.00007013",
1024 "amount":"0.00000222",
1025 "total":"0.00000000",
1027 "date":"2018-04-02 00:09:13"
1029 order
.mark_disappeared_order()
1030 self
.assertEqual("error_disappeared", order
.status
)
1032 with self
.subTest("Order disappeared"):
1033 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1034 D("0.1"), "BTC", "long", self
.m
, "trade")
1036 order
.status
= "closed"
1037 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1039 "currencyPair":"BTC_XRP",
1041 "rate":"0.00007013",
1042 "amount":"0.00000222",
1043 "total":"0.00000000",
1045 "date":"2018-04-02 00:09:13"
1047 order
.mark_disappeared_order()
1048 self
.assertEqual("error_disappeared", order
.status
)
1050 @mock.patch.object(portfolio
.Order
, "fetch")
1051 def test_get_status(self
, fetch
):
1052 with self
.subTest(debug
=True):
1054 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1055 D("0.1"), "BTC", "long", self
.m
, "trade")
1056 self
.assertEqual("pending", order
.get_status())
1057 fetch
.assert_not_called()
1058 self
.m
.report
.log_debug_action
.assert_called_once()
1060 with self
.subTest(debug
=False, finished
=False):
1061 self
.m
.debug
= False
1062 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1063 D("0.1"), "BTC", "long", self
.m
, "trade")
1065 def update_status():
1066 order
.status
= "open"
1067 return update_status
1068 fetch
.side_effect
= _fetch(order
)
1069 self
.assertEqual("open", order
.get_status())
1070 fetch
.assert_called_once()
1073 with self
.subTest(debug
=False, finished
=True):
1074 self
.m
.debug
= False
1075 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1076 D("0.1"), "BTC", "long", self
.m
, "trade")
1078 def update_status():
1079 order
.status
= "closed"
1080 return update_status
1081 fetch
.side_effect
= _fetch(order
)
1082 self
.assertEqual("closed", order
.get_status())
1083 fetch
.assert_called_once()
1086 self
.m
.ccxt
.order_precision
.return_value
= 4
1087 with self
.subTest(debug
=True):
1089 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1090 D("0.1"), "BTC", "long", self
.m
, "trade")
1092 self
.m
.ccxt
.create_order
.assert_not_called()
1093 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1094 self
.assertEqual("open", order
.status
)
1095 self
.assertEqual(1, len(order
.results
))
1096 self
.assertEqual(-1, order
.id)
1098 self
.m
.ccxt
.create_order
.reset_mock()
1099 with self
.subTest(debug
=False):
1100 self
.m
.debug
= False
1101 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1102 D("0.1"), "BTC", "long", self
.m
, "trade")
1103 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
1105 self
.m
.ccxt
.create_order
.assert_called_once()
1106 self
.assertEqual(1, len(order
.results
))
1107 self
.assertEqual("open", order
.status
)
1109 self
.m
.ccxt
.create_order
.reset_mock()
1110 with self
.subTest(exception
=True):
1111 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1112 D("0.1"), "BTC", "long", self
.m
, "trade")
1113 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
1115 self
.m
.ccxt
.create_order
.assert_called_once()
1116 self
.assertEqual(0, len(order
.results
))
1117 self
.assertEqual("error", order
.status
)
1118 self
.m
.report
.log_error
.assert_called_once()
1120 self
.m
.ccxt
.create_order
.reset_mock()
1121 with self
.subTest(dust_amount_exception
=True),\
1122 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1123 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
1124 D("0.1"), "BTC", "long", self
.m
, "trade")
1125 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
1127 self
.m
.ccxt
.create_order
.assert_called_once()
1128 self
.assertEqual(0, len(order
.results
))
1129 self
.assertEqual("closed", order
.status
)
1130 mark_finished_order
.assert_called_once()
1132 self
.m
.ccxt
.order_precision
.return_value
= 8
1133 self
.m
.ccxt
.create_order
.reset_mock()
1134 with self
.subTest(insufficient_funds
=True),\
1135 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1136 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1137 D("0.1"), "BTC", "long", self
.m
, "trade")
1138 self
.m
.ccxt
.create_order
.side_effect
= [
1139 portfolio
.InsufficientFunds
,
1140 portfolio
.InsufficientFunds
,
1141 portfolio
.InsufficientFunds
,
1145 self
.m
.ccxt
.create_order
.assert_has_calls([
1146 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1147 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1148 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1149 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1151 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
1152 self
.assertEqual(1, len(order
.results
))
1153 self
.assertEqual("open", order
.status
)
1154 self
.assertEqual(4, order
.tries
)
1155 self
.m
.report
.log_error
.assert_called()
1156 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
1158 self
.m
.ccxt
.order_precision
.return_value
= 8
1159 self
.m
.ccxt
.create_order
.reset_mock()
1160 self
.m
.report
.log_error
.reset_mock()
1161 with self
.subTest(insufficient_funds
=True),\
1162 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1163 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1164 D("0.1"), "BTC", "long", self
.m
, "trade")
1165 self
.m
.ccxt
.create_order
.side_effect
= [
1166 portfolio
.InsufficientFunds
,
1167 portfolio
.InsufficientFunds
,
1168 portfolio
.InsufficientFunds
,
1169 portfolio
.InsufficientFunds
,
1170 portfolio
.InsufficientFunds
,
1173 self
.m
.ccxt
.create_order
.assert_has_calls([
1174 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1175 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1176 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1177 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1178 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
1180 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1181 self
.assertEqual(0, len(order
.results
))
1182 self
.assertEqual("error", order
.status
)
1183 self
.assertEqual(5, order
.tries
)
1184 self
.m
.report
.log_error
.assert_called()
1185 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1186 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
)
1189 with self
.subTest(invalid_nonce
=True):
1190 with self
.subTest(retry_success
=True):
1191 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1192 D("0.1"), "BTC", "long", self
.m
, "trade")
1193 self
.m
.ccxt
.create_order
.side_effect
= [
1194 portfolio
.InvalidNonce
,
1195 portfolio
.InvalidNonce
,
1199 self
.m
.ccxt
.create_order
.assert_has_calls([
1200 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1201 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1202 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1204 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1205 self
.assertEqual(3, order
.tries
)
1206 self
.m
.report
.log_error
.assert_called()
1207 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1208 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after invalid nonce", exception
=mock
.ANY
)
1209 self
.assertEqual(123, order
.id)
1212 with self
.subTest(retry_success
=False):
1213 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1214 D("0.1"), "BTC", "long", self
.m
, "trade")
1215 self
.m
.ccxt
.create_order
.side_effect
= [
1216 portfolio
.InvalidNonce
,
1217 portfolio
.InvalidNonce
,
1218 portfolio
.InvalidNonce
,
1219 portfolio
.InvalidNonce
,
1220 portfolio
.InvalidNonce
,
1223 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1224 self
.assertEqual(5, order
.tries
)
1225 self
.m
.report
.log_error
.assert_called()
1226 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1227 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
)
1228 self
.assertEqual("error", order
.status
)
1231 with self
.subTest(request_timeout
=True):
1232 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1233 D("0.1"), "BTC", "long", self
.m
, "trade")
1234 with self
.subTest(retrieved
=False), \
1235 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1236 self
.m
.ccxt
.create_order
.side_effect
= [
1237 portfolio
.RequestTimeout
,
1238 portfolio
.RequestTimeout
,
1241 retrieve
.return_value
= False
1243 self
.m
.ccxt
.create_order
.assert_has_calls([
1244 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1245 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1246 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1248 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1249 self
.assertEqual(3, order
.tries
)
1250 self
.m
.report
.log_error
.assert_called()
1251 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1252 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after timeout", exception
=mock
.ANY
)
1253 self
.assertEqual(123, order
.id)
1256 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1257 D("0.1"), "BTC", "long", self
.m
, "trade")
1258 with self
.subTest(retrieved
=True), \
1259 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1260 self
.m
.ccxt
.create_order
.side_effect
= [
1261 portfolio
.RequestTimeout
,
1264 order
.results
.append({"id": 123}
)
1266 retrieve
.side_effect
= _retrieve
1268 self
.m
.ccxt
.create_order
.assert_has_calls([
1269 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1271 self
.assertEqual(1, self
.m
.ccxt
.create_order
.call_count
)
1272 self
.assertEqual(1, order
.tries
)
1273 self
.m
.report
.log_error
.assert_called()
1274 self
.assertEqual(1, self
.m
.report
.log_error
.call_count
)
1275 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Timeout, found the order")
1276 self
.assertEqual(123, order
.id)
1279 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1280 D("0.1"), "BTC", "long", self
.m
, "trade")
1281 with self
.subTest(retrieved
=False), \
1282 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1283 self
.m
.ccxt
.create_order
.side_effect
= [
1284 portfolio
.RequestTimeout
,
1285 portfolio
.RequestTimeout
,
1286 portfolio
.RequestTimeout
,
1287 portfolio
.RequestTimeout
,
1288 portfolio
.RequestTimeout
,
1290 retrieve
.return_value
= False
1292 self
.m
.ccxt
.create_order
.assert_has_calls([
1293 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1294 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1295 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1296 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1297 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1299 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1300 self
.assertEqual(5, order
.tries
)
1301 self
.m
.report
.log_error
.assert_called()
1302 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1303 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
)
1304 self
.assertEqual("error", order
.status
)
1306 def test_retrieve_order(self
):
1307 with self
.subTest(similar_open_order
=True):
1308 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1309 D("0.1"), "BTC", "long", self
.m
, "trade")
1310 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1312 self
.m
.ccxt
.order_precision
.return_value
= 8
1313 self
.m
.ccxt
.fetch_orders
.return_value
= [
1315 'amount': 0.002, 'cost': 0.1,
1316 'datetime': '2018-03-25T15:15:51.000Z',
1317 'fee': None, 'filled': 0.0,
1321 'date': '2018-03-25 15:15:51',
1322 'margin': 0, 'orderNumber': '1',
1323 'price': '0.1', 'rate': '0.1',
1324 'side': 'buy', 'startingAmount': '0.002',
1325 'status': 'open', 'total': '0.0002',
1328 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1329 'status': 'open', 'symbol': 'ETH/BTC',
1330 'timestamp': 1521990951000, 'trades': None,
1334 'amount': 0.001, 'cost': 0.1,
1335 'datetime': '2018-03-25T15:15:51.000Z',
1336 'fee': None, 'filled': 0.0,
1340 'date': '2018-03-25 15:15:51',
1341 'margin': 1, 'orderNumber': '2',
1342 'price': '0.1', 'rate': '0.1',
1343 'side': 'buy', 'startingAmount': '0.001',
1344 'status': 'open', 'total': '0.0001',
1347 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1348 'status': 'open', 'symbol': 'ETH/BTC',
1349 'timestamp': 1521990951000, 'trades': None,
1353 'amount': 0.001, 'cost': 0.1,
1354 'datetime': '2018-03-25T15:15:51.000Z',
1355 'fee': None, 'filled': 0.0,
1359 'date': '2018-03-25 15:15:51',
1360 'margin': 0, 'orderNumber': '3',
1361 'price': '0.1', 'rate': '0.1',
1362 'side': 'sell', 'startingAmount': '0.001',
1363 'status': 'open', 'total': '0.0001',
1366 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1367 'status': 'open', 'symbol': 'ETH/BTC',
1368 'timestamp': 1521990951000, 'trades': None,
1372 'amount': 0.001, 'cost': 0.15,
1373 'datetime': '2018-03-25T15:15:51.000Z',
1374 'fee': None, 'filled': 0.0,
1378 'date': '2018-03-25 15:15:51',
1379 'margin': 0, 'orderNumber': '4',
1380 'price': '0.15', 'rate': '0.15',
1381 'side': 'buy', 'startingAmount': '0.001',
1382 'status': 'open', 'total': '0.0001',
1385 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1386 'status': 'open', 'symbol': 'ETH/BTC',
1387 'timestamp': 1521990951000, 'trades': None,
1391 'amount': 0.001, 'cost': 0.1,
1392 'datetime': '2018-03-25T15:15:51.000Z',
1393 'fee': None, 'filled': 0.0,
1397 'date': '2018-03-25 15:15:51',
1398 'margin': 0, 'orderNumber': '1',
1399 'price': '0.1', 'rate': '0.1',
1400 'side': 'buy', 'startingAmount': '0.001',
1401 'status': 'open', 'total': '0.0001',
1404 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1405 'status': 'open', 'symbol': 'ETH/BTC',
1406 'timestamp': 1521990951000, 'trades': None,
1410 result
= order
.retrieve_order()
1411 self
.assertTrue(result
)
1412 self
.assertEqual('5', order
.results
[0]["id"])
1413 self
.m
.ccxt
.fetch_my_trades
.assert_not_called()
1414 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1417 with self
.subTest(similar_open_order
=False, past_trades
=True):
1418 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1419 D("0.1"), "BTC", "long", self
.m
, "trade")
1420 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1422 self
.m
.ccxt
.order_precision
.return_value
= 8
1423 self
.m
.ccxt
.fetch_orders
.return_value
= []
1424 self
.m
.ccxt
.fetch_my_trades
.return_value
= [
1425 { # Wrong timestamp 1
1428 'datetime': '2018-03-25T15:15:14.000Z',
1432 'category': 'exchange',
1433 'date': '2018-03-25 15:15:14',
1434 'fee': '0.00150000',
1445 'symbol': 'ETH/BTC',
1446 'timestamp': 1521983714,
1449 { # Wrong timestamp 2
1452 'datetime': '2018-03-25T15:16:54.000Z',
1456 'category': 'exchange',
1457 'date': '2018-03-25 15:16:54',
1458 'fee': '0.00150000',
1469 'symbol': 'ETH/BTC',
1470 'timestamp': 1521983814,
1476 'datetime': '2018-03-25T15:15:54.000Z',
1480 'category': 'exchange',
1481 'date': '2018-03-25 15:15:54',
1482 'fee': '0.00150000',
1493 'symbol': 'ETH/BTC',
1494 'timestamp': 1521983754,
1500 'datetime': '2018-03-25T15:16:54.000Z',
1504 'category': 'exchange',
1505 'date': '2018-03-25 15:16:54',
1506 'fee': '0.00150000',
1517 'symbol': 'ETH/BTC',
1518 'timestamp': 1521983814,
1524 'datetime': '2018-03-25T15:15:54.000Z',
1528 'category': 'marginTrade',
1529 'date': '2018-03-25 15:15:54',
1530 'fee': '0.00150000',
1541 'symbol': 'ETH/BTC',
1542 'timestamp': 1521983754,
1548 'datetime': '2018-03-25T15:16:54.000Z',
1552 'category': 'marginTrade',
1553 'date': '2018-03-25 15:16:54',
1554 'fee': '0.00150000',
1565 'symbol': 'ETH/BTC',
1566 'timestamp': 1521983814,
1572 'datetime': '2018-03-25T15:15:54.000Z',
1576 'category': 'exchange',
1577 'date': '2018-03-25 15:15:54',
1578 'fee': '0.00150000',
1589 'symbol': 'ETH/BTC',
1590 'timestamp': 1521983754,
1596 'datetime': '2018-03-25T15:16:54.000Z',
1600 'category': 'exchange',
1601 'date': '2018-03-25 15:16:54',
1602 'fee': '0.00150000',
1613 'symbol': 'ETH/BTC',
1614 'timestamp': 1521983814,
1620 'datetime': '2018-03-25T15:15:54.000Z',
1624 'category': 'exchange',
1625 'date': '2018-03-25 15:15:54',
1626 'fee': '0.00150000',
1630 'total': '0.000066',
1637 'symbol': 'ETH/BTC',
1638 'timestamp': 1521983754,
1644 'datetime': '2018-03-25T15:16:54.000Z',
1648 'category': 'exchange',
1649 'date': '2018-03-25 15:16:54',
1650 'fee': '0.00150000',
1661 'symbol': 'ETH/BTC',
1662 'timestamp': 1521983814,
1668 'datetime': '2018-03-25T15:15:54.000Z',
1672 'category': 'exchange',
1673 'date': '2018-03-25 15:15:54',
1674 'fee': '0.00150000',
1685 'symbol': 'ETH/BTC',
1686 'timestamp': 1521983754,
1692 'datetime': '2018-03-25T15:16:54.000Z',
1696 'category': 'exchange',
1697 'date': '2018-03-25 15:16:54',
1698 'fee': '0.00150000',
1702 'total': '0.000036',
1709 'symbol': 'ETH/BTC',
1710 'timestamp': 1521983814,
1715 result
= order
.retrieve_order()
1716 self
.assertTrue(result
)
1717 self
.assertEqual('7', order
.results
[0]["id"])
1718 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1721 with self
.subTest(similar_open_order
=False, past_trades
=False):
1722 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1723 D("0.1"), "BTC", "long", self
.m
, "trade")
1724 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1726 self
.m
.ccxt
.order_precision
.return_value
= 8
1727 self
.m
.ccxt
.fetch_orders
.return_value
= []
1728 self
.m
.ccxt
.fetch_my_trades
.return_value
= []
1729 result
= order
.retrieve_order()
1730 self
.assertFalse(result
)
1732 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1733 class MouvementTest(WebMockTestCase
):
1734 def test_values(self
):
1735 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1736 "tradeID": 42, "type": "buy", "fee": "0.0015",
1737 "date": "2017-12-30 12:00:12", "rate": "0.1",
1738 "amount": "10", "total": "1"
1740 self
.assertEqual("ETH", mouvement
.currency
)
1741 self
.assertEqual("BTC", mouvement
.base_currency
)
1742 self
.assertEqual(42, mouvement
.id)
1743 self
.assertEqual("buy", mouvement
.action
)
1744 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
1745 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
1746 self
.assertEqual(D("0.1"), mouvement
.rate
)
1747 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
1748 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
1750 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
1751 self
.assertIsNone(mouvement
.date
)
1752 self
.assertIsNone(mouvement
.id)
1753 self
.assertIsNone(mouvement
.action
)
1754 self
.assertEqual(-1, mouvement
.fee_rate
)
1755 self
.assertEqual(0, mouvement
.rate
)
1756 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
1757 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
1759 def test__repr(self
):
1760 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1761 "tradeID": 42, "type": "buy", "fee": "0.0015",
1762 "date": "2017-12-30 12:00:12", "rate": "0.1",
1763 "amount": "10", "total": "1"
1765 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
1767 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1768 "tradeID": 42, "type": "buy",
1769 "date": "garbage", "rate": "0.1",
1770 "amount": "10", "total": "1"
1772 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
1774 def test_as_json(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"
1780 as_json
= mouvement
.as_json()
1782 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
1783 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
1784 self
.assertEqual("buy", as_json
["action"])
1785 self
.assertEqual(D("10"), as_json
["total"])
1786 self
.assertEqual(D("1"), as_json
["total_in_base"])
1787 self
.assertEqual("BTC", as_json
["base_currency"])
1788 self
.assertEqual("ETH", as_json
["currency"])
1790 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1791 class AmountTest(WebMockTestCase
):
1792 def test_values(self
):
1793 amount
= portfolio
.Amount("BTC", "0.65")
1794 self
.assertEqual(D("0.65"), amount
.value
)
1795 self
.assertEqual("BTC", amount
.currency
)
1797 def test_in_currency(self
):
1798 amount
= portfolio
.Amount("ETC", 10)
1800 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
1802 with self
.subTest(desc
="no ticker for currency"):
1803 self
.m
.get_ticker
.return_value
= None
1805 self
.assertRaises(Exception, amount
.in_currency
, "ETH", self
.m
)
1807 with self
.subTest(desc
="nominal case"):
1808 self
.m
.get_ticker
.return_value
= {
1811 "average": D("0.3"),
1814 converted_amount
= amount
.in_currency("ETH", self
.m
)
1816 self
.assertEqual(D("3.0"), converted_amount
.value
)
1817 self
.assertEqual("ETH", converted_amount
.currency
)
1818 self
.assertEqual(amount
, converted_amount
.linked_to
)
1819 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
1821 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
1822 self
.assertEqual(D("2"), converted_amount
.value
)
1824 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
1825 self
.assertEqual(D("4"), converted_amount
.value
)
1827 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
1828 self
.assertEqual(D("0.2"), converted_amount
.value
)
1830 def test__round(self
):
1831 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
1832 self
.assertEqual(D("1.23456789"), round(amount
).value
)
1833 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
1835 def test__abs(self
):
1836 amount
= portfolio
.Amount("SC", -120)
1837 self
.assertEqual(120, abs(amount
).value
)
1838 self
.assertEqual("SC", abs(amount
).currency
)
1840 amount
= portfolio
.Amount("SC", 10)
1841 self
.assertEqual(10, abs(amount
).value
)
1842 self
.assertEqual("SC", abs(amount
).currency
)
1844 def test__add(self
):
1845 amount1
= portfolio
.Amount("XVG", "12.9")
1846 amount2
= portfolio
.Amount("XVG", "13.1")
1848 self
.assertEqual(26, (amount1
+ amount2
).value
)
1849 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
1851 amount3
= portfolio
.Amount("ETH", "1.6")
1852 with self
.assertRaises(Exception):
1855 amount4
= portfolio
.Amount("ETH", 0.0)
1856 self
.assertEqual(amount1
, amount1
+ amount4
)
1858 self
.assertEqual(amount1
, amount1
+ 0)
1860 def test__radd(self
):
1861 amount
= portfolio
.Amount("XVG", "12.9")
1863 self
.assertEqual(amount
, 0 + amount
)
1864 with self
.assertRaises(Exception):
1867 def test__sub(self
):
1868 amount1
= portfolio
.Amount("XVG", "13.3")
1869 amount2
= portfolio
.Amount("XVG", "13.1")
1871 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
1872 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
1874 amount3
= portfolio
.Amount("ETH", "1.6")
1875 with self
.assertRaises(Exception):
1878 amount4
= portfolio
.Amount("ETH", 0.0)
1879 self
.assertEqual(amount1
, amount1
- amount4
)
1881 def test__rsub(self
):
1882 amount
= portfolio
.Amount("ETH", "1.6")
1883 with self
.assertRaises(Exception):
1886 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
1888 def test__mul(self
):
1889 amount
= portfolio
.Amount("XEM", 11)
1891 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
1892 self
.assertEqual(D("33"), (amount
* 3).value
)
1894 with self
.assertRaises(Exception):
1897 def test__rmul(self
):
1898 amount
= portfolio
.Amount("XEM", 11)
1900 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
1901 self
.assertEqual(D("33"), (3 * amount
).value
)
1903 def test__floordiv(self
):
1904 amount
= portfolio
.Amount("XEM", 11)
1906 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1907 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1909 with self
.assertRaises(Exception):
1912 def test__truediv(self
):
1913 amount
= portfolio
.Amount("XEM", 11)
1915 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1916 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1919 amount1
= portfolio
.Amount("BTD", 11.3)
1920 amount2
= portfolio
.Amount("BTD", 13.1)
1922 self
.assertTrue(amount1
< amount2
)
1923 self
.assertFalse(amount2
< amount1
)
1924 self
.assertFalse(amount1
< amount1
)
1926 amount3
= portfolio
.Amount("BTC", 1.6)
1927 with self
.assertRaises(Exception):
1931 amount1
= portfolio
.Amount("BTD", 11.3)
1932 amount2
= portfolio
.Amount("BTD", 13.1)
1934 self
.assertTrue(amount1
<= amount2
)
1935 self
.assertFalse(amount2
<= amount1
)
1936 self
.assertTrue(amount1
<= amount1
)
1938 amount3
= portfolio
.Amount("BTC", 1.6)
1939 with self
.assertRaises(Exception):
1943 amount1
= portfolio
.Amount("BTD", 11.3)
1944 amount2
= portfolio
.Amount("BTD", 13.1)
1946 self
.assertTrue(amount2
> amount1
)
1947 self
.assertFalse(amount1
> amount2
)
1948 self
.assertFalse(amount1
> amount1
)
1950 amount3
= portfolio
.Amount("BTC", 1.6)
1951 with self
.assertRaises(Exception):
1955 amount1
= portfolio
.Amount("BTD", 11.3)
1956 amount2
= portfolio
.Amount("BTD", 13.1)
1958 self
.assertTrue(amount2
>= amount1
)
1959 self
.assertFalse(amount1
>= amount2
)
1960 self
.assertTrue(amount1
>= amount1
)
1962 amount3
= portfolio
.Amount("BTC", 1.6)
1963 with self
.assertRaises(Exception):
1967 amount1
= portfolio
.Amount("BTD", 11.3)
1968 amount2
= portfolio
.Amount("BTD", 13.1)
1969 amount3
= portfolio
.Amount("BTD", 11.3)
1971 self
.assertFalse(amount1
== amount2
)
1972 self
.assertFalse(amount2
== amount1
)
1973 self
.assertTrue(amount1
== amount3
)
1974 self
.assertFalse(amount2
== 0)
1976 amount4
= portfolio
.Amount("BTC", 1.6)
1977 with self
.assertRaises(Exception):
1980 amount5
= portfolio
.Amount("BTD", 0)
1981 self
.assertTrue(amount5
== 0)
1984 amount1
= portfolio
.Amount("BTD", 11.3)
1985 amount2
= portfolio
.Amount("BTD", 13.1)
1986 amount3
= portfolio
.Amount("BTD", 11.3)
1988 self
.assertTrue(amount1
!= amount2
)
1989 self
.assertTrue(amount2
!= amount1
)
1990 self
.assertFalse(amount1
!= amount3
)
1991 self
.assertTrue(amount2
!= 0)
1993 amount4
= portfolio
.Amount("BTC", 1.6)
1994 with self
.assertRaises(Exception):
1997 amount5
= portfolio
.Amount("BTD", 0)
1998 self
.assertFalse(amount5
!= 0)
2000 def test__neg(self
):
2001 amount1
= portfolio
.Amount("BTD", "11.3")
2003 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
2005 def test__str(self
):
2006 amount1
= portfolio
.Amount("BTX", 32)
2007 self
.assertEqual("32.00000000 BTX", str(amount1
))
2009 amount2
= portfolio
.Amount("USDT", 12000)
2010 amount1
.linked_to
= amount2
2011 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
2013 def test__repr(self
):
2014 amount1
= portfolio
.Amount("BTX", 32)
2015 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
2017 amount2
= portfolio
.Amount("USDT", 12000)
2018 amount1
.linked_to
= amount2
2019 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
2021 amount3
= portfolio
.Amount("BTC", 0.1)
2022 amount2
.linked_to
= amount3
2023 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
2025 def test_as_json(self
):
2026 amount
= portfolio
.Amount("BTX", 32)
2027 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
2029 amount
= portfolio
.Amount("BTX", "1E-10")
2030 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
2032 amount
= portfolio
.Amount("BTX", "1E-5")
2033 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
2034 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))