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, refetch
=True)
146 order2
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=True)
147 order3
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=True)
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, refetch
=True)
173 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=True)
174 order3
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=True)
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, refetch
=False)
193 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=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, refetch
=False)
197 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=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, refetch
=False)
201 order2
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=False)
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
= 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")
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
)
234 trade
.prepare_order()
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')
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")
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
)
254 trade
.prepare_order()
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()
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")
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
)
272 trade
.prepare_order()
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)
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")
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
)
291 trade
.prepare_order(close_if_possible
=True)
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)
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")
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
)
310 trade
.prepare_order()
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
))
316 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
317 D("0.125"), "BTC", "long", self
.m
,
318 trade
, close_if_possible
=False)
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")
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
)
330 trade
.prepare_order()
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)
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")
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
)
350 trade
.prepare_order(compute_value
="foo")
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)
359 with self
.subTest(action
="acquire", inverted
=True):
360 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
361 compute_value
.return_value
= D("125")
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
)
369 trade
.prepare_order(compute_value
="foo")
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)
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
)
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))
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()
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
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)
413 order_mock
.reset_mock()
414 new_order_mock
.reset_mock()
416 self
.m
.report
.log_order
.reset_mock()
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
)
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
),
430 self
.m
.report
.log_order
.assert_has_calls(calls
)
432 order_mock
.reset_mock()
433 new_order_mock
.reset_mock()
435 self
.m
.report
.log_order
.reset_mock()
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()
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
),
449 self
.m
.report
.log_order
.assert_has_calls(calls
)
451 order_mock
.reset_mock()
452 new_order_mock
.reset_mock()
454 self
.m
.report
.log_order
.reset_mock()
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
)
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
),
468 self
.m
.report
.log_order
.assert_has_calls(calls
)
470 order_mock
.reset_mock()
471 new_order_mock
.reset_mock()
473 self
.m
.report
.log_order
.reset_mock()
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
)
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
),
489 self
.m
.report
.log_order
.assert_has_calls(calls
)
491 order_mock
.reset_mock()
492 new_order_mock
.reset_mock()
494 self
.m
.report
.log_order
.reset_mock()
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)
504 order_mock
.reset_mock()
505 new_order_mock
.reset_mock()
507 self
.m
.report
.log_order
.reset_mock()
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
)
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
532 trade
.orders
.append(order_mock1
)
533 trade
.orders
.append(order_mock2
)
535 with mock
.patch
.object(trade
, "filled_amount") as filled
:
536 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
538 trade
.print_with_order()
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]))
548 self
.m
.report
.print_log
.reset_mock()
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]))
555 self
.m
.report
.print_log
.reset_mock()
557 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
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]))
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
)
569 trade
.orders
.append(order1
)
573 self
.assertEqual(True, trade
.closed
)
574 order1
.cancel
.assert_called_once_with()
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
)
583 self
.assertEqual(False, trade
.pending
)
586 self
.assertEqual(True, trade
.pending
)
589 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
590 trade
.orders
.append(order1
)
591 self
.assertEqual(False, trade
.pending
)
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
)
599 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
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
)
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"])
614 @unittest.skipUnless("unit" in limits
, "Unit skipped")
615 class 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",
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
)
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
)
646 self
.assertEqual("BTC", balance
.currency
)
648 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
649 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
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
))
658 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
659 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
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
))
665 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
666 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
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
))
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
))
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"])
690 @unittest.skipUnless("unit" in limits
, "Unit skipped")
691 class 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
)
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
))
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
))
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
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"])
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
)
743 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
744 D("0.1"), "BTC", "short", "market", "trade")
745 self
.assertEqual("margin", order
.account
)
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
)
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)
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
)
772 @mock.patch.object(portfolio
.Order
, "fetch")
773 def test_cancel(self
, fetch
):
774 with self
.subTest(debug
=True):
776 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
777 D("0.1"), "BTC", "long", self
.m
, "trade")
778 order
.status
= "open"
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
)
786 with self
.subTest(desc
="Nominal case"):
788 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
789 D("0.1"), "BTC", "long", self
.m
, "trade")
790 order
.status
= "open"
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()
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"
805 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
806 self
.m
.report
.log_error
.assert_called_once()
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"
815 self
.m
.ccxt
.cancel_order
.assert_not_called()
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"
824 self
.m
.ccxt
.cancel_order
.assert_not_called()
826 def test_mark_dust_amount_remaining(self
):
827 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
828 D("0.1"), "BTC", "long", self
.m
, "trade")
829 self
.m
.ccxt
.is_dust_trade
.return_value
= False
830 order
.mark_dust_amount_remaining_order()
831 self
.assertEqual("pending", order
.status
)
833 self
.m
.ccxt
.is_dust_trade
.return_value
= True
834 order
.mark_dust_amount_remaining_order()
835 self
.assertEqual("pending", order
.status
)
837 order
.status
= "open"
838 order
.mark_dust_amount_remaining_order()
839 self
.assertEqual("closed_dust_remaining", order
.status
)
841 @mock.patch.object(portfolio
.Order
, "fetch")
842 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
843 def test_remaining_amount(self
, filled_amount
, fetch
):
844 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
845 D("0.1"), "BTC", "long", self
.m
, "trade")
847 self
.assertEqual(9, order
.remaining_amount().value
)
849 order
.status
= "open"
850 self
.assertEqual(9, order
.remaining_amount().value
)
852 @mock.patch.object(portfolio
.Order
, "fetch")
853 def test_filled_amount(self
, fetch
):
854 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
855 D("0.1"), "BTC", "long", self
.m
, "trade")
856 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
857 "tradeID": 42, "type": "buy", "fee": "0.0015",
858 "date": "2017-12-30 12:00:12", "rate": "0.1",
859 "amount": "3", "total": "0.3"
861 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
862 "tradeID": 43, "type": "buy", "fee": "0.0015",
863 "date": "2017-12-30 13:00:12", "rate": "0.2",
864 "amount": "2", "total": "0.4"
866 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
867 fetch
.assert_not_called()
868 order
.status
= "open"
869 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
870 fetch
.assert_not_called()
871 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False, refetch
=True))
872 fetch
.assert_called_once()
873 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
875 def test_fetch_mouvements(self
):
876 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
878 "tradeID": 42, "type": "buy", "fee": "0.0015",
879 "date": "2017-12-30 13:00:12", "rate": "0.1",
880 "amount": "3", "total": "0.3"
883 "tradeID": 43, "type": "buy", "fee": "0.0015",
884 "date": "2017-12-30 12:00:12", "rate": "0.2",
885 "amount": "2", "total": "0.4"
888 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
889 D("0.1"), "BTC", "long", self
.m
, "trade")
891 order
.mouvements
= ["Foo", "Bar", "Baz"]
893 order
.fetch_mouvements()
895 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
896 self
.assertEqual(2, len(order
.mouvements
))
897 self
.assertEqual(43, order
.mouvements
[0].id)
898 self
.assertEqual(42, order
.mouvements
[1].id)
900 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
901 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
902 D("0.1"), "BTC", "long", self
.m
, "trade")
903 order
.fetch_mouvements()
904 self
.assertEqual(0, len(order
.mouvements
))
906 def test_mark_finished_order(self
):
907 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
908 D("0.1"), "BTC", "short", self
.m
, "trade",
909 close_if_possible
=True)
910 order
.status
= "closed"
913 order
.mark_finished_order()
914 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
915 self
.m
.ccxt
.close_margin_position
.reset_mock()
917 order
.status
= "open"
918 order
.mark_finished_order()
919 self
.m
.ccxt
.close_margin_position
.assert_not_called()
921 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
922 D("0.1"), "BTC", "short", self
.m
, "trade",
923 close_if_possible
=False)
924 order
.status
= "closed"
925 order
.mark_finished_order()
926 self
.m
.ccxt
.close_margin_position
.assert_not_called()
928 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
929 D("0.1"), "BTC", "short", self
.m
, "trade",
930 close_if_possible
=True)
931 order
.status
= "closed"
932 order
.mark_finished_order()
933 self
.m
.ccxt
.close_margin_position
.assert_not_called()
935 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
936 D("0.1"), "BTC", "long", self
.m
, "trade",
937 close_if_possible
=True)
938 order
.status
= "closed"
939 order
.mark_finished_order()
940 self
.m
.ccxt
.close_margin_position
.assert_not_called()
944 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
945 D("0.1"), "BTC", "short", self
.m
, "trade",
946 close_if_possible
=True)
947 order
.status
= "closed"
949 order
.mark_finished_order()
950 self
.m
.ccxt
.close_margin_position
.assert_not_called()
951 self
.m
.report
.log_debug_action
.assert_called_once()
953 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
954 @mock.patch.object(portfolio
.Order
, "mark_disappeared_order")
955 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
956 def test_fetch(self
, mark_finished_order
, mark_disappeared_order
, fetch_mouvements
):
957 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
958 D("0.1"), "BTC", "long", self
.m
, "trade")
960 with self
.subTest(debug
=True):
963 self
.m
.report
.log_debug_action
.assert_called_once()
964 self
.m
.report
.log_debug_action
.reset_mock()
965 self
.m
.ccxt
.fetch_order
.assert_not_called()
966 mark_finished_order
.assert_not_called()
967 mark_disappeared_order
.assert_not_called()
968 fetch_mouvements
.assert_not_called()
970 with self
.subTest(debug
=False):
972 self
.m
.ccxt
.fetch_order
.return_value
= {
974 "datetime": "timestamp"
976 self
.m
.ccxt
.is_dust_trade
.return_value
= False
979 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
980 fetch_mouvements
.assert_called_once()
981 self
.assertEqual("foo", order
.status
)
982 self
.assertEqual("timestamp", order
.timestamp
)
983 self
.assertEqual(1, len(order
.results
))
984 self
.m
.report
.log_debug_action
.assert_not_called()
985 mark_finished_order
.assert_called_once()
986 mark_disappeared_order
.assert_called_once()
988 mark_finished_order
.reset_mock()
989 with self
.subTest(missing_order
=True):
990 self
.m
.ccxt
.fetch_order
.side_effect
= [
991 portfolio
.OrderNotCached
,
994 self
.assertEqual("closed_unknown", order
.status
)
995 mark_finished_order
.assert_called_once()
997 def test_mark_disappeared_order(self
):
998 with self
.subTest("Open order"):
999 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1000 D("0.1"), "BTC", "long", self
.m
, "trade")
1002 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1004 "currencyPair":"BTC_XRP",
1006 "rate":"0.00007013",
1007 "amount":"0.00000222",
1008 "total":"0.00000000",
1010 "date":"2018-04-02 00:09:13"
1012 order
.mark_disappeared_order()
1013 self
.assertEqual("pending", order
.status
)
1015 with self
.subTest("Non-zero amount"):
1016 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1017 D("0.1"), "BTC", "long", self
.m
, "trade")
1019 order
.status
= "closed"
1020 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1022 "currencyPair":"BTC_XRP",
1024 "rate":"0.00007013",
1025 "amount":"0.00000222",
1026 "total":"0.00000010",
1028 "date":"2018-04-02 00:09:13"
1030 order
.mark_disappeared_order()
1031 self
.assertEqual("closed", order
.status
)
1033 with self
.subTest("Other mouvements"):
1034 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1035 D("0.1"), "BTC", "long", self
.m
, "trade")
1037 order
.status
= "closed"
1038 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1040 "currencyPair":"BTC_XRP",
1042 "rate":"0.00007013",
1043 "amount":"0.00000222",
1044 "total":"0.00000001",
1046 "date":"2018-04-02 00:09:13"
1048 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1050 "currencyPair":"BTC_XRP",
1052 "rate":"0.00007013",
1053 "amount":"0.00000222",
1054 "total":"0.00000000",
1056 "date":"2018-04-02 00:09:13"
1058 order
.mark_disappeared_order()
1059 self
.assertEqual("error_disappeared", order
.status
)
1061 with self
.subTest("Order disappeared"):
1062 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1063 D("0.1"), "BTC", "long", self
.m
, "trade")
1065 order
.status
= "closed"
1066 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1068 "currencyPair":"BTC_XRP",
1070 "rate":"0.00007013",
1071 "amount":"0.00000222",
1072 "total":"0.00000000",
1074 "date":"2018-04-02 00:09:13"
1076 order
.mark_disappeared_order()
1077 self
.assertEqual("error_disappeared", order
.status
)
1079 @mock.patch.object(portfolio
.Order
, "fetch")
1080 def test_get_status(self
, fetch
):
1081 with self
.subTest(debug
=True):
1083 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1084 D("0.1"), "BTC", "long", self
.m
, "trade")
1085 self
.assertEqual("pending", order
.get_status())
1086 fetch
.assert_not_called()
1087 self
.m
.report
.log_debug_action
.assert_called_once()
1089 with self
.subTest(debug
=False, finished
=False):
1090 self
.m
.debug
= False
1091 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1092 D("0.1"), "BTC", "long", self
.m
, "trade")
1094 def update_status():
1095 order
.status
= "open"
1096 return update_status
1097 fetch
.side_effect
= _fetch(order
)
1098 self
.assertEqual("open", order
.get_status())
1099 fetch
.assert_called_once()
1102 with self
.subTest(debug
=False, finished
=True):
1103 self
.m
.debug
= False
1104 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1105 D("0.1"), "BTC", "long", self
.m
, "trade")
1107 def update_status():
1108 order
.status
= "closed"
1109 return update_status
1110 fetch
.side_effect
= _fetch(order
)
1111 self
.assertEqual("closed", order
.get_status())
1112 fetch
.assert_called_once()
1115 self
.m
.ccxt
.order_precision
.return_value
= 4
1116 with self
.subTest(debug
=True):
1118 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1119 D("0.1"), "BTC", "long", self
.m
, "trade")
1121 self
.m
.ccxt
.create_order
.assert_not_called()
1122 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1123 self
.assertEqual("open", order
.status
)
1124 self
.assertEqual(1, len(order
.results
))
1125 self
.assertEqual(-1, order
.id)
1127 self
.m
.ccxt
.create_order
.reset_mock()
1128 with self
.subTest(debug
=False):
1129 self
.m
.debug
= False
1130 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1131 D("0.1"), "BTC", "long", self
.m
, "trade")
1132 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
1134 self
.m
.ccxt
.create_order
.assert_called_once()
1135 self
.assertEqual(1, len(order
.results
))
1136 self
.assertEqual("open", order
.status
)
1138 self
.m
.ccxt
.create_order
.reset_mock()
1139 with self
.subTest(exception
=True):
1140 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1141 D("0.1"), "BTC", "long", self
.m
, "trade")
1142 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
1144 self
.m
.ccxt
.create_order
.assert_called_once()
1145 self
.assertEqual(0, len(order
.results
))
1146 self
.assertEqual("error", order
.status
)
1147 self
.m
.report
.log_error
.assert_called_once()
1149 self
.m
.ccxt
.create_order
.reset_mock()
1150 with self
.subTest(dust_amount_exception
=True),\
1151 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1152 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
1153 D("0.1"), "BTC", "long", self
.m
, "trade")
1154 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
1156 self
.m
.ccxt
.create_order
.assert_called_once()
1157 self
.assertEqual(0, len(order
.results
))
1158 self
.assertEqual("closed", order
.status
)
1159 mark_finished_order
.assert_called_once()
1161 self
.m
.ccxt
.order_precision
.return_value
= 8
1162 self
.m
.ccxt
.create_order
.reset_mock()
1163 with self
.subTest(insufficient_funds
=True),\
1164 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1165 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1166 D("0.1"), "BTC", "long", self
.m
, "trade")
1167 self
.m
.ccxt
.create_order
.side_effect
= [
1168 portfolio
.InsufficientFunds
,
1169 portfolio
.InsufficientFunds
,
1170 portfolio
.InsufficientFunds
,
1174 self
.m
.ccxt
.create_order
.assert_has_calls([
1175 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1176 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1177 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1178 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1180 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
1181 self
.assertEqual(1, len(order
.results
))
1182 self
.assertEqual("open", order
.status
)
1183 self
.assertEqual(4, order
.tries
)
1184 self
.m
.report
.log_error
.assert_called()
1185 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
1187 self
.m
.ccxt
.order_precision
.return_value
= 8
1188 self
.m
.ccxt
.create_order
.reset_mock()
1189 self
.m
.report
.log_error
.reset_mock()
1190 with self
.subTest(insufficient_funds
=True),\
1191 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1192 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1193 D("0.1"), "BTC", "long", self
.m
, "trade")
1194 self
.m
.ccxt
.create_order
.side_effect
= [
1195 portfolio
.InsufficientFunds
,
1196 portfolio
.InsufficientFunds
,
1197 portfolio
.InsufficientFunds
,
1198 portfolio
.InsufficientFunds
,
1199 portfolio
.InsufficientFunds
,
1202 self
.m
.ccxt
.create_order
.assert_has_calls([
1203 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1204 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1205 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1206 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1207 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
1209 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1210 self
.assertEqual(0, len(order
.results
))
1211 self
.assertEqual("error", order
.status
)
1212 self
.assertEqual(5, order
.tries
)
1213 self
.m
.report
.log_error
.assert_called()
1214 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1215 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
)
1218 with self
.subTest(invalid_nonce
=True):
1219 with self
.subTest(retry_success
=True):
1220 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1221 D("0.1"), "BTC", "long", self
.m
, "trade")
1222 self
.m
.ccxt
.create_order
.side_effect
= [
1223 portfolio
.InvalidNonce
,
1224 portfolio
.InvalidNonce
,
1228 self
.m
.ccxt
.create_order
.assert_has_calls([
1229 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1230 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1231 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1233 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1234 self
.assertEqual(3, order
.tries
)
1235 self
.m
.report
.log_error
.assert_called()
1236 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1237 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after invalid nonce", exception
=mock
.ANY
)
1238 self
.assertEqual(123, order
.id)
1241 with self
.subTest(retry_success
=False):
1242 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1243 D("0.1"), "BTC", "long", self
.m
, "trade")
1244 self
.m
.ccxt
.create_order
.side_effect
= [
1245 portfolio
.InvalidNonce
,
1246 portfolio
.InvalidNonce
,
1247 portfolio
.InvalidNonce
,
1248 portfolio
.InvalidNonce
,
1249 portfolio
.InvalidNonce
,
1252 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1253 self
.assertEqual(5, order
.tries
)
1254 self
.m
.report
.log_error
.assert_called()
1255 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1256 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
)
1257 self
.assertEqual("error", order
.status
)
1260 with self
.subTest(request_timeout
=True):
1261 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1262 D("0.1"), "BTC", "long", self
.m
, "trade")
1263 with self
.subTest(retrieved
=False), \
1264 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1265 self
.m
.ccxt
.create_order
.side_effect
= [
1266 portfolio
.RequestTimeout
,
1267 portfolio
.RequestTimeout
,
1270 retrieve
.return_value
= False
1272 self
.m
.ccxt
.create_order
.assert_has_calls([
1273 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1274 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1275 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1277 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1278 self
.assertEqual(3, order
.tries
)
1279 self
.m
.report
.log_error
.assert_called()
1280 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1281 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after timeout", exception
=mock
.ANY
)
1282 self
.assertEqual(123, order
.id)
1285 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1286 D("0.1"), "BTC", "long", self
.m
, "trade")
1287 with self
.subTest(retrieved
=True), \
1288 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1289 self
.m
.ccxt
.create_order
.side_effect
= [
1290 portfolio
.RequestTimeout
,
1293 order
.results
.append({"id": 123}
)
1295 retrieve
.side_effect
= _retrieve
1297 self
.m
.ccxt
.create_order
.assert_has_calls([
1298 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1300 self
.assertEqual(1, self
.m
.ccxt
.create_order
.call_count
)
1301 self
.assertEqual(1, order
.tries
)
1302 self
.m
.report
.log_error
.assert_called()
1303 self
.assertEqual(1, self
.m
.report
.log_error
.call_count
)
1304 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Timeout, found the order")
1305 self
.assertEqual(123, order
.id)
1308 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1309 D("0.1"), "BTC", "long", self
.m
, "trade")
1310 with self
.subTest(retrieved
=False), \
1311 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1312 self
.m
.ccxt
.create_order
.side_effect
= [
1313 portfolio
.RequestTimeout
,
1314 portfolio
.RequestTimeout
,
1315 portfolio
.RequestTimeout
,
1316 portfolio
.RequestTimeout
,
1317 portfolio
.RequestTimeout
,
1319 retrieve
.return_value
= False
1321 self
.m
.ccxt
.create_order
.assert_has_calls([
1322 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1323 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1324 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1325 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1326 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1328 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1329 self
.assertEqual(5, order
.tries
)
1330 self
.m
.report
.log_error
.assert_called()
1331 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1332 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
)
1333 self
.assertEqual("error", order
.status
)
1335 def test_retrieve_order(self
):
1336 with self
.subTest(similar_open_order
=True):
1337 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1338 D("0.1"), "BTC", "long", self
.m
, "trade")
1339 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1341 self
.m
.ccxt
.order_precision
.return_value
= 8
1342 self
.m
.ccxt
.fetch_orders
.return_value
= [
1344 'amount': 0.002, 'cost': 0.1,
1345 'datetime': '2018-03-25T15:15:51.000Z',
1346 'fee': None, 'filled': 0.0,
1350 'date': '2018-03-25 15:15:51',
1351 'margin': 0, 'orderNumber': '1',
1352 'price': '0.1', 'rate': '0.1',
1353 'side': 'buy', 'startingAmount': '0.002',
1354 'status': 'open', 'total': '0.0002',
1357 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1358 'status': 'open', 'symbol': 'ETH/BTC',
1359 'timestamp': 1521990951000, 'trades': None,
1363 'amount': 0.001, 'cost': 0.1,
1364 'datetime': '2018-03-25T15:15:51.000Z',
1365 'fee': None, 'filled': 0.0,
1369 'date': '2018-03-25 15:15:51',
1370 'margin': 1, 'orderNumber': '2',
1371 'price': '0.1', 'rate': '0.1',
1372 'side': 'buy', 'startingAmount': '0.001',
1373 'status': 'open', 'total': '0.0001',
1376 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1377 'status': 'open', 'symbol': 'ETH/BTC',
1378 'timestamp': 1521990951000, 'trades': None,
1382 'amount': 0.001, 'cost': 0.1,
1383 'datetime': '2018-03-25T15:15:51.000Z',
1384 'fee': None, 'filled': 0.0,
1388 'date': '2018-03-25 15:15:51',
1389 'margin': 0, 'orderNumber': '3',
1390 'price': '0.1', 'rate': '0.1',
1391 'side': 'sell', 'startingAmount': '0.001',
1392 'status': 'open', 'total': '0.0001',
1395 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1396 'status': 'open', 'symbol': 'ETH/BTC',
1397 'timestamp': 1521990951000, 'trades': None,
1401 'amount': 0.001, 'cost': 0.15,
1402 'datetime': '2018-03-25T15:15:51.000Z',
1403 'fee': None, 'filled': 0.0,
1407 'date': '2018-03-25 15:15:51',
1408 'margin': 0, 'orderNumber': '4',
1409 'price': '0.15', 'rate': '0.15',
1410 'side': 'buy', 'startingAmount': '0.001',
1411 'status': 'open', 'total': '0.0001',
1414 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1415 'status': 'open', 'symbol': 'ETH/BTC',
1416 'timestamp': 1521990951000, 'trades': None,
1420 'amount': 0.001, 'cost': 0.1,
1421 'datetime': '2018-03-25T15:15:51.000Z',
1422 'fee': None, 'filled': 0.0,
1426 'date': '2018-03-25 15:15:51',
1427 'margin': 0, 'orderNumber': '1',
1428 'price': '0.1', 'rate': '0.1',
1429 'side': 'buy', 'startingAmount': '0.001',
1430 'status': 'open', 'total': '0.0001',
1433 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1434 'status': 'open', 'symbol': 'ETH/BTC',
1435 'timestamp': 1521990951000, 'trades': None,
1439 result
= order
.retrieve_order()
1440 self
.assertTrue(result
)
1441 self
.assertEqual('5', order
.results
[0]["id"])
1442 self
.m
.ccxt
.fetch_my_trades
.assert_not_called()
1443 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1446 with self
.subTest(similar_open_order
=False, past_trades
=True):
1447 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1448 D("0.1"), "BTC", "long", self
.m
, "trade")
1449 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1451 self
.m
.ccxt
.order_precision
.return_value
= 8
1452 self
.m
.ccxt
.fetch_orders
.return_value
= []
1453 self
.m
.ccxt
.fetch_my_trades
.return_value
= [
1454 { # Wrong timestamp 1
1457 'datetime': '2018-03-25T15:15:14.000Z',
1461 'category': 'exchange',
1462 'date': '2018-03-25 15:15:14',
1463 'fee': '0.00150000',
1474 'symbol': 'ETH/BTC',
1475 'timestamp': 1521983714,
1478 { # Wrong timestamp 2
1481 'datetime': '2018-03-25T15:16:54.000Z',
1485 'category': 'exchange',
1486 'date': '2018-03-25 15:16:54',
1487 'fee': '0.00150000',
1498 'symbol': 'ETH/BTC',
1499 'timestamp': 1521983814,
1505 'datetime': '2018-03-25T15:15:54.000Z',
1509 'category': 'exchange',
1510 'date': '2018-03-25 15:15:54',
1511 'fee': '0.00150000',
1522 'symbol': 'ETH/BTC',
1523 'timestamp': 1521983754,
1529 'datetime': '2018-03-25T15:16:54.000Z',
1533 'category': 'exchange',
1534 'date': '2018-03-25 15:16:54',
1535 'fee': '0.00150000',
1546 'symbol': 'ETH/BTC',
1547 'timestamp': 1521983814,
1553 'datetime': '2018-03-25T15:15:54.000Z',
1557 'category': 'marginTrade',
1558 'date': '2018-03-25 15:15:54',
1559 'fee': '0.00150000',
1570 'symbol': 'ETH/BTC',
1571 'timestamp': 1521983754,
1577 'datetime': '2018-03-25T15:16:54.000Z',
1581 'category': 'marginTrade',
1582 'date': '2018-03-25 15:16:54',
1583 'fee': '0.00150000',
1594 'symbol': 'ETH/BTC',
1595 'timestamp': 1521983814,
1601 'datetime': '2018-03-25T15:15:54.000Z',
1605 'category': 'exchange',
1606 'date': '2018-03-25 15:15:54',
1607 'fee': '0.00150000',
1618 'symbol': 'ETH/BTC',
1619 'timestamp': 1521983754,
1625 'datetime': '2018-03-25T15:16:54.000Z',
1629 'category': 'exchange',
1630 'date': '2018-03-25 15:16:54',
1631 'fee': '0.00150000',
1642 'symbol': 'ETH/BTC',
1643 'timestamp': 1521983814,
1649 'datetime': '2018-03-25T15:15:54.000Z',
1653 'category': 'exchange',
1654 'date': '2018-03-25 15:15:54',
1655 'fee': '0.00150000',
1659 'total': '0.000066',
1666 'symbol': 'ETH/BTC',
1667 'timestamp': 1521983754,
1673 'datetime': '2018-03-25T15:16:54.000Z',
1677 'category': 'exchange',
1678 'date': '2018-03-25 15:16:54',
1679 'fee': '0.00150000',
1690 'symbol': 'ETH/BTC',
1691 'timestamp': 1521983814,
1697 'datetime': '2018-03-25T15:15:54.000Z',
1701 'category': 'exchange',
1702 'date': '2018-03-25 15:15:54',
1703 'fee': '0.00150000',
1714 'symbol': 'ETH/BTC',
1715 'timestamp': 1521983754,
1721 'datetime': '2018-03-25T15:16:54.000Z',
1725 'category': 'exchange',
1726 'date': '2018-03-25 15:16:54',
1727 'fee': '0.00150000',
1731 'total': '0.000036',
1738 'symbol': 'ETH/BTC',
1739 'timestamp': 1521983814,
1744 result
= order
.retrieve_order()
1745 self
.assertTrue(result
)
1746 self
.assertEqual('7', order
.results
[0]["id"])
1747 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1750 with self
.subTest(similar_open_order
=False, past_trades
=False):
1751 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1752 D("0.1"), "BTC", "long", self
.m
, "trade")
1753 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1755 self
.m
.ccxt
.order_precision
.return_value
= 8
1756 self
.m
.ccxt
.fetch_orders
.return_value
= []
1757 self
.m
.ccxt
.fetch_my_trades
.return_value
= []
1758 result
= order
.retrieve_order()
1759 self
.assertFalse(result
)
1761 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1762 class MouvementTest(WebMockTestCase
):
1763 def test_values(self
):
1764 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1765 "tradeID": 42, "type": "buy", "fee": "0.0015",
1766 "date": "2017-12-30 12:00:12", "rate": "0.1",
1767 "amount": "10", "total": "1"
1769 self
.assertEqual("ETH", mouvement
.currency
)
1770 self
.assertEqual("BTC", mouvement
.base_currency
)
1771 self
.assertEqual(42, mouvement
.id)
1772 self
.assertEqual("buy", mouvement
.action
)
1773 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
1774 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
1775 self
.assertEqual(D("0.1"), mouvement
.rate
)
1776 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
1777 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
1779 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
1780 self
.assertIsNone(mouvement
.date
)
1781 self
.assertIsNone(mouvement
.id)
1782 self
.assertIsNone(mouvement
.action
)
1783 self
.assertEqual(-1, mouvement
.fee_rate
)
1784 self
.assertEqual(0, mouvement
.rate
)
1785 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
1786 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
1788 def test__repr(self
):
1789 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1790 "tradeID": 42, "type": "buy", "fee": "0.0015",
1791 "date": "2017-12-30 12:00:12", "rate": "0.1",
1792 "amount": "10", "total": "1"
1794 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
1796 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1797 "tradeID": 42, "type": "buy",
1798 "date": "garbage", "rate": "0.1",
1799 "amount": "10", "total": "1"
1801 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
1803 def test_as_json(self
):
1804 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1805 "tradeID": 42, "type": "buy", "fee": "0.0015",
1806 "date": "2017-12-30 12:00:12", "rate": "0.1",
1807 "amount": "10", "total": "1"
1809 as_json
= mouvement
.as_json()
1811 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
1812 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
1813 self
.assertEqual("buy", as_json
["action"])
1814 self
.assertEqual(D("10"), as_json
["total"])
1815 self
.assertEqual(D("1"), as_json
["total_in_base"])
1816 self
.assertEqual("BTC", as_json
["base_currency"])
1817 self
.assertEqual("ETH", as_json
["currency"])
1819 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1820 class AmountTest(WebMockTestCase
):
1821 def test_values(self
):
1822 amount
= portfolio
.Amount("BTC", "0.65")
1823 self
.assertEqual(D("0.65"), amount
.value
)
1824 self
.assertEqual("BTC", amount
.currency
)
1826 def test_in_currency(self
):
1827 amount
= portfolio
.Amount("ETC", 10)
1829 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
1831 with self
.subTest(desc
="no ticker for currency"):
1832 self
.m
.get_ticker
.return_value
= None
1834 self
.assertRaises(Exception, amount
.in_currency
, "ETH", self
.m
)
1836 with self
.subTest(desc
="nominal case"):
1837 self
.m
.get_ticker
.return_value
= {
1840 "average": D("0.3"),
1843 converted_amount
= amount
.in_currency("ETH", self
.m
)
1845 self
.assertEqual(D("3.0"), converted_amount
.value
)
1846 self
.assertEqual("ETH", converted_amount
.currency
)
1847 self
.assertEqual(amount
, converted_amount
.linked_to
)
1848 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
1850 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
1851 self
.assertEqual(D("2"), converted_amount
.value
)
1853 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
1854 self
.assertEqual(D("4"), converted_amount
.value
)
1856 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
1857 self
.assertEqual(D("0.2"), converted_amount
.value
)
1859 def test__round(self
):
1860 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
1861 self
.assertEqual(D("1.23456789"), round(amount
).value
)
1862 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
1864 def test__abs(self
):
1865 amount
= portfolio
.Amount("SC", -120)
1866 self
.assertEqual(120, abs(amount
).value
)
1867 self
.assertEqual("SC", abs(amount
).currency
)
1869 amount
= portfolio
.Amount("SC", 10)
1870 self
.assertEqual(10, abs(amount
).value
)
1871 self
.assertEqual("SC", abs(amount
).currency
)
1873 def test__add(self
):
1874 amount1
= portfolio
.Amount("XVG", "12.9")
1875 amount2
= portfolio
.Amount("XVG", "13.1")
1877 self
.assertEqual(26, (amount1
+ amount2
).value
)
1878 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
1880 amount3
= portfolio
.Amount("ETH", "1.6")
1881 with self
.assertRaises(Exception):
1884 amount4
= portfolio
.Amount("ETH", 0.0)
1885 self
.assertEqual(amount1
, amount1
+ amount4
)
1887 self
.assertEqual(amount1
, amount1
+ 0)
1889 def test__radd(self
):
1890 amount
= portfolio
.Amount("XVG", "12.9")
1892 self
.assertEqual(amount
, 0 + amount
)
1893 with self
.assertRaises(Exception):
1896 def test__sub(self
):
1897 amount1
= portfolio
.Amount("XVG", "13.3")
1898 amount2
= portfolio
.Amount("XVG", "13.1")
1900 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
1901 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
1903 amount3
= portfolio
.Amount("ETH", "1.6")
1904 with self
.assertRaises(Exception):
1907 amount4
= portfolio
.Amount("ETH", 0.0)
1908 self
.assertEqual(amount1
, amount1
- amount4
)
1910 def test__rsub(self
):
1911 amount
= portfolio
.Amount("ETH", "1.6")
1912 with self
.assertRaises(Exception):
1915 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
1917 def test__mul(self
):
1918 amount
= portfolio
.Amount("XEM", 11)
1920 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
1921 self
.assertEqual(D("33"), (amount
* 3).value
)
1923 with self
.assertRaises(Exception):
1926 def test__rmul(self
):
1927 amount
= portfolio
.Amount("XEM", 11)
1929 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
1930 self
.assertEqual(D("33"), (3 * amount
).value
)
1932 def test__floordiv(self
):
1933 amount
= portfolio
.Amount("XEM", 11)
1935 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1936 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1938 with self
.assertRaises(Exception):
1941 def test__truediv(self
):
1942 amount
= portfolio
.Amount("XEM", 11)
1944 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1945 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1948 amount1
= portfolio
.Amount("BTD", 11.3)
1949 amount2
= portfolio
.Amount("BTD", 13.1)
1951 self
.assertTrue(amount1
< amount2
)
1952 self
.assertFalse(amount2
< amount1
)
1953 self
.assertFalse(amount1
< amount1
)
1955 amount3
= portfolio
.Amount("BTC", 1.6)
1956 with self
.assertRaises(Exception):
1960 amount1
= portfolio
.Amount("BTD", 11.3)
1961 amount2
= portfolio
.Amount("BTD", 13.1)
1963 self
.assertTrue(amount1
<= amount2
)
1964 self
.assertFalse(amount2
<= amount1
)
1965 self
.assertTrue(amount1
<= amount1
)
1967 amount3
= portfolio
.Amount("BTC", 1.6)
1968 with self
.assertRaises(Exception):
1972 amount1
= portfolio
.Amount("BTD", 11.3)
1973 amount2
= portfolio
.Amount("BTD", 13.1)
1975 self
.assertTrue(amount2
> amount1
)
1976 self
.assertFalse(amount1
> amount2
)
1977 self
.assertFalse(amount1
> amount1
)
1979 amount3
= portfolio
.Amount("BTC", 1.6)
1980 with self
.assertRaises(Exception):
1984 amount1
= portfolio
.Amount("BTD", 11.3)
1985 amount2
= portfolio
.Amount("BTD", 13.1)
1987 self
.assertTrue(amount2
>= amount1
)
1988 self
.assertFalse(amount1
>= amount2
)
1989 self
.assertTrue(amount1
>= amount1
)
1991 amount3
= portfolio
.Amount("BTC", 1.6)
1992 with self
.assertRaises(Exception):
1996 amount1
= portfolio
.Amount("BTD", 11.3)
1997 amount2
= portfolio
.Amount("BTD", 13.1)
1998 amount3
= portfolio
.Amount("BTD", 11.3)
2000 self
.assertFalse(amount1
== amount2
)
2001 self
.assertFalse(amount2
== amount1
)
2002 self
.assertTrue(amount1
== amount3
)
2003 self
.assertFalse(amount2
== 0)
2005 amount4
= portfolio
.Amount("BTC", 1.6)
2006 with self
.assertRaises(Exception):
2009 amount5
= portfolio
.Amount("BTD", 0)
2010 self
.assertTrue(amount5
== 0)
2013 amount1
= portfolio
.Amount("BTD", 11.3)
2014 amount2
= portfolio
.Amount("BTD", 13.1)
2015 amount3
= portfolio
.Amount("BTD", 11.3)
2017 self
.assertTrue(amount1
!= amount2
)
2018 self
.assertTrue(amount2
!= amount1
)
2019 self
.assertFalse(amount1
!= amount3
)
2020 self
.assertTrue(amount2
!= 0)
2022 amount4
= portfolio
.Amount("BTC", 1.6)
2023 with self
.assertRaises(Exception):
2026 amount5
= portfolio
.Amount("BTD", 0)
2027 self
.assertFalse(amount5
!= 0)
2029 def test__neg(self
):
2030 amount1
= portfolio
.Amount("BTD", "11.3")
2032 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
2034 def test__str(self
):
2035 amount1
= portfolio
.Amount("BTX", 32)
2036 self
.assertEqual("32.00000000 BTX", str(amount1
))
2038 amount2
= portfolio
.Amount("USDT", 12000)
2039 amount1
.linked_to
= amount2
2040 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
2042 def test__repr(self
):
2043 amount1
= portfolio
.Amount("BTX", 32)
2044 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
2046 amount2
= portfolio
.Amount("USDT", 12000)
2047 amount1
.linked_to
= amount2
2048 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
2050 amount3
= portfolio
.Amount("BTC", 0.1)
2051 amount2
.linked_to
= amount3
2052 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
2054 def test_as_json(self
):
2055 amount
= portfolio
.Amount("BTX", 32)
2056 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
2058 amount
= portfolio
.Amount("BTX", "1E-10")
2059 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
2061 amount
= portfolio
.Amount("BTX", "1E-5")
2062 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
2063 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))