5 class ComputationTest(WebMockTestCase
):
6 def test_compute_value(self
):
8 portfolio
.Computation
.compute_value("foo", "buy", compute_value
=compute
)
9 compute
.assert_called_with("foo", "ask")
12 portfolio
.Computation
.compute_value("foo", "sell", compute_value
=compute
)
13 compute
.assert_called_with("foo", "bid")
16 portfolio
.Computation
.compute_value("foo", "ask", compute_value
=compute
)
17 compute
.assert_called_with("foo", "ask")
20 portfolio
.Computation
.compute_value("foo", "bid", compute_value
=compute
)
21 compute
.assert_called_with("foo", "bid")
24 portfolio
.Computation
.computations
["test"] = compute
25 portfolio
.Computation
.compute_value("foo", "bid", compute_value
="test")
26 compute
.assert_called_with("foo", "bid")
28 class TradeTest(WebMockTestCase
):
30 def test_values_assertion(self
):
31 value_from
= portfolio
.Amount("BTC", "1.0")
32 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
33 value_to
= portfolio
.Amount("BTC", "1.0")
34 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
35 self
.assertEqual("BTC", trade
.base_currency
)
36 self
.assertEqual("ETH", trade
.currency
)
37 self
.assertEqual(self
.m
, trade
.market
)
39 with self
.assertRaises(AssertionError):
40 portfolio
.Trade(value_from
, -value_to
, "ETH", self
.m
)
41 with self
.assertRaises(AssertionError):
42 portfolio
.Trade(value_from
, value_to
, "ETC", self
.m
)
43 with self
.assertRaises(AssertionError):
44 value_from
.currency
= "ETH"
45 portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
46 value_from
.currency
= "BTC"
47 with self
.assertRaises(AssertionError):
48 value_from2
= portfolio
.Amount("BTC", "1.0")
49 portfolio
.Trade(value_from2
, value_to
, "ETH", self
.m
)
51 value_from
= portfolio
.Amount("BTC", 0)
52 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
53 self
.assertEqual(0, trade
.value_from
.linked_to
)
55 def test_action(self
):
56 value_from
= portfolio
.Amount("BTC", "1.0")
57 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
58 value_to
= portfolio
.Amount("BTC", "1.0")
59 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
61 self
.assertIsNone(trade
.action
)
63 value_from
= portfolio
.Amount("BTC", "1.0")
64 value_from
.linked_to
= portfolio
.Amount("BTC", "1.0")
65 value_to
= portfolio
.Amount("BTC", "2.0")
66 trade
= portfolio
.Trade(value_from
, value_to
, "BTC", self
.m
)
68 self
.assertIsNone(trade
.action
)
70 value_from
= portfolio
.Amount("BTC", "0.5")
71 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
72 value_to
= portfolio
.Amount("BTC", "1.0")
73 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
75 self
.assertEqual("acquire", trade
.action
)
77 value_from
= portfolio
.Amount("BTC", "0")
78 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
79 value_to
= portfolio
.Amount("BTC", "-1.0")
80 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
82 self
.assertEqual("acquire", trade
.action
)
84 def test_order_action(self
):
85 value_from
= portfolio
.Amount("BTC", "0.5")
86 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
87 value_to
= portfolio
.Amount("BTC", "1.0")
88 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
90 trade
.inverted
= False
91 self
.assertEqual("buy", trade
.order_action())
93 self
.assertEqual("sell", trade
.order_action())
95 value_from
= portfolio
.Amount("BTC", "0")
96 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
97 value_to
= portfolio
.Amount("BTC", "-1.0")
98 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
100 trade
.inverted
= False
101 self
.assertEqual("sell", trade
.order_action())
102 trade
.inverted
= True
103 self
.assertEqual("buy", trade
.order_action())
105 def test_trade_type(self
):
106 value_from
= portfolio
.Amount("BTC", "0.5")
107 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
108 value_to
= portfolio
.Amount("BTC", "1.0")
109 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
111 self
.assertEqual("long", trade
.trade_type
)
113 value_from
= portfolio
.Amount("BTC", "0")
114 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
115 value_to
= portfolio
.Amount("BTC", "-1.0")
116 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
118 self
.assertEqual("short", trade
.trade_type
)
120 def test_is_fullfiled(self
):
121 with self
.subTest(inverted
=False):
122 value_from
= portfolio
.Amount("BTC", "0.5")
123 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
124 value_to
= portfolio
.Amount("BTC", "1.0")
125 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
128 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
131 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
132 trade
.orders
.append(order1
)
133 trade
.orders
.append(order2
)
135 self
.assertFalse(trade
.is_fullfiled
)
138 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
139 trade
.orders
.append(order3
)
141 self
.assertTrue(trade
.is_fullfiled
)
143 order1
.filled_amount
.assert_called_with(in_base_currency
=True)
144 order2
.filled_amount
.assert_called_with(in_base_currency
=True)
145 order3
.filled_amount
.assert_called_with(in_base_currency
=True)
147 with self
.subTest(inverted
=True):
148 value_from
= portfolio
.Amount("BTC", "0.5")
149 value_from
.linked_to
= portfolio
.Amount("USDT", "1000.0")
150 value_to
= portfolio
.Amount("BTC", "1.0")
151 trade
= portfolio
.Trade(value_from
, value_to
, "USDT", self
.m
)
152 trade
.inverted
= True
155 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
158 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
159 trade
.orders
.append(order1
)
160 trade
.orders
.append(order2
)
162 self
.assertFalse(trade
.is_fullfiled
)
165 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
166 trade
.orders
.append(order3
)
168 self
.assertTrue(trade
.is_fullfiled
)
170 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
171 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
172 order3
.filled_amount
.assert_called_with(in_base_currency
=False)
175 def test_filled_amount(self
):
176 value_from
= portfolio
.Amount("BTC", "0.5")
177 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
178 value_to
= portfolio
.Amount("BTC", "1.0")
179 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
182 order1
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.3")
185 order2
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.01")
186 trade
.orders
.append(order1
)
187 trade
.orders
.append(order2
)
189 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount())
190 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
191 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
193 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=False))
194 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
195 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
197 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=True))
198 order1
.filled_amount
.assert_called_with(in_base_currency
=True)
199 order2
.filled_amount
.assert_called_with(in_base_currency
=True)
201 @mock.patch.object(portfolio
.Computation
, "compute_value")
202 @mock.patch.object(portfolio
.Trade
, "filled_amount")
203 @mock.patch.object(portfolio
, "Order")
204 def test_prepare_order(self
, Order
, filled_amount
, compute_value
):
205 Order
.return_value
= "Order"
207 with self
.subTest(desc
="Nothing to do"):
208 value_from
= portfolio
.Amount("BTC", "10")
209 value_from
.rate
= D("0.1")
210 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
211 value_to
= portfolio
.Amount("BTC", "10")
212 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
214 trade
.prepare_order()
216 filled_amount
.assert_not_called()
217 compute_value
.assert_not_called()
218 self
.assertEqual(0, len(trade
.orders
))
219 Order
.assert_not_called()
221 self
.m
.get_ticker
.return_value
= None
222 with self
.subTest(desc
="Unknown ticker"):
223 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
224 compute_value
.return_value
= D("0.125")
226 value_from
= portfolio
.Amount("BTC", "1")
227 value_from
.rate
= D("0.1")
228 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
229 value_to
= portfolio
.Amount("BTC", "10")
230 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
232 trade
.prepare_order()
234 filled_amount
.assert_not_called()
235 compute_value
.assert_not_called()
236 self
.assertEqual(0, len(trade
.orders
))
237 Order
.assert_not_called()
238 self
.m
.report
.log_error
.assert_called_once_with('prepare_order',
239 message
='Unknown ticker FOO/BTC')
241 self
.m
.get_ticker
.return_value
= { "inverted": False }
242 with self
.subTest(desc
="Already filled"):
243 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
244 compute_value
.return_value
= D("0.125")
246 value_from
= portfolio
.Amount("BTC", "10")
247 value_from
.rate
= D("0.1")
248 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
249 value_to
= portfolio
.Amount("BTC", "0")
250 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
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(0, len(trade
.orders
))
257 self
.m
.report
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
258 Order
.assert_not_called()
260 with self
.subTest(action
="dispose", inverted
=False):
261 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
262 compute_value
.return_value
= D("0.125")
264 value_from
= portfolio
.Amount("BTC", "10")
265 value_from
.rate
= D("0.1")
266 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
267 value_to
= portfolio
.Amount("BTC", "1")
268 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
270 trade
.prepare_order()
272 filled_amount
.assert_called_with(in_base_currency
=False)
273 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
274 self
.assertEqual(1, len(trade
.orders
))
275 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
276 D("0.125"), "BTC", "long", self
.m
,
277 trade
, close_if_possible
=False)
279 with self
.subTest(action
="dispose", inverted
=False, close_if_possible
=True):
280 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
281 compute_value
.return_value
= D("0.125")
283 value_from
= portfolio
.Amount("BTC", "10")
284 value_from
.rate
= D("0.1")
285 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
286 value_to
= portfolio
.Amount("BTC", "1")
287 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
289 trade
.prepare_order(close_if_possible
=True)
291 filled_amount
.assert_called_with(in_base_currency
=False)
292 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
293 self
.assertEqual(1, len(trade
.orders
))
294 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
295 D("0.125"), "BTC", "long", self
.m
,
296 trade
, close_if_possible
=True)
298 with self
.subTest(action
="acquire", inverted
=False):
299 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
300 compute_value
.return_value
= D("0.125")
302 value_from
= portfolio
.Amount("BTC", "1")
303 value_from
.rate
= D("0.1")
304 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
305 value_to
= portfolio
.Amount("BTC", "10")
306 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
308 trade
.prepare_order()
310 filled_amount
.assert_called_with(in_base_currency
=True)
311 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "buy", compute_value
="default")
312 self
.assertEqual(1, len(trade
.orders
))
314 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
315 D("0.125"), "BTC", "long", self
.m
,
316 trade
, close_if_possible
=False)
318 with self
.subTest(close_if_possible
=True):
319 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
320 compute_value
.return_value
= D("0.125")
322 value_from
= portfolio
.Amount("BTC", "10")
323 value_from
.rate
= D("0.1")
324 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
325 value_to
= portfolio
.Amount("BTC", "0")
326 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
328 trade
.prepare_order()
330 filled_amount
.assert_called_with(in_base_currency
=False)
331 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
332 self
.assertEqual(1, len(trade
.orders
))
333 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
334 D("0.125"), "BTC", "long", self
.m
,
335 trade
, close_if_possible
=True)
337 self
.m
.get_ticker
.return_value
= { "inverted": True, "original": {}
}
338 with self
.subTest(action
="dispose", inverted
=True):
339 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
340 compute_value
.return_value
= D("125")
342 value_from
= portfolio
.Amount("BTC", "10")
343 value_from
.rate
= D("0.01")
344 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
345 value_to
= portfolio
.Amount("BTC", "1")
346 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
348 trade
.prepare_order(compute_value
="foo")
350 filled_amount
.assert_called_with(in_base_currency
=True)
351 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "buy", compute_value
="foo")
352 self
.assertEqual(1, len(trade
.orders
))
353 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
354 D("125"), "FOO", "long", self
.m
,
355 trade
, close_if_possible
=False)
357 with self
.subTest(action
="acquire", inverted
=True):
358 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
359 compute_value
.return_value
= D("125")
361 value_from
= portfolio
.Amount("BTC", "1")
362 value_from
.rate
= D("0.01")
363 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
364 value_to
= portfolio
.Amount("BTC", "10")
365 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
367 trade
.prepare_order(compute_value
="foo")
369 filled_amount
.assert_called_with(in_base_currency
=False)
370 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "sell", compute_value
="foo")
371 self
.assertEqual(1, len(trade
.orders
))
372 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
373 D("125"), "FOO", "long", self
.m
,
374 trade
, close_if_possible
=False)
376 def test_tick_actions_recreate(self
):
377 value_from
= portfolio
.Amount("BTC", "0.5")
378 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
379 value_to
= portfolio
.Amount("BTC", "1.0")
380 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
382 self
.assertEqual("average", trade
.tick_actions_recreate(0))
383 self
.assertEqual("foo", trade
.tick_actions_recreate(0, default
="foo"))
384 self
.assertEqual("average", trade
.tick_actions_recreate(1))
385 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(2))
386 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(3))
387 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(5))
388 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(6))
389 self
.assertEqual("default", trade
.tick_actions_recreate(7))
390 self
.assertEqual("default", trade
.tick_actions_recreate(8))
392 @mock.patch.object(portfolio
.Trade
, "prepare_order")
393 def test_update_order(self
, prepare_order
):
394 order_mock
= mock
.Mock()
395 new_order_mock
= mock
.Mock()
397 value_from
= portfolio
.Amount("BTC", "0.5")
398 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
399 value_to
= portfolio
.Amount("BTC", "1.0")
400 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
401 prepare_order
.return_value
= new_order_mock
403 for i
in [0, 1, 3, 4, 6]:
404 with self
.subTest(tick
=i
):
405 trade
.update_order(order_mock
, i
)
406 order_mock
.cancel
.assert_not_called()
407 new_order_mock
.run
.assert_not_called()
408 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
,
409 update
="waiting", compute_value
=None, new_order
=None)
411 order_mock
.reset_mock()
412 new_order_mock
.reset_mock()
414 self
.m
.report
.log_order
.reset_mock()
416 trade
.update_order(order_mock
, 2)
417 order_mock
.cancel
.assert_called()
418 new_order_mock
.run
.assert_called()
419 prepare_order
.assert_called()
420 self
.m
.report
.log_order
.assert_called()
421 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
423 mock
.call(order_mock
, 2, update
="adjusting",
424 compute_value
=mock
.ANY
,
425 new_order
=new_order_mock
),
426 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
428 self
.m
.report
.log_order
.assert_has_calls(calls
)
430 order_mock
.reset_mock()
431 new_order_mock
.reset_mock()
433 self
.m
.report
.log_order
.reset_mock()
435 trade
.update_order(order_mock
, 5)
436 order_mock
.cancel
.assert_called()
437 new_order_mock
.run
.assert_called()
438 prepare_order
.assert_called()
439 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
440 self
.m
.report
.log_order
.assert_called()
442 mock
.call(order_mock
, 5, update
="adjusting",
443 compute_value
=mock
.ANY
,
444 new_order
=new_order_mock
),
445 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
447 self
.m
.report
.log_order
.assert_has_calls(calls
)
449 order_mock
.reset_mock()
450 new_order_mock
.reset_mock()
452 self
.m
.report
.log_order
.reset_mock()
454 trade
.update_order(order_mock
, 7)
455 order_mock
.cancel
.assert_called()
456 new_order_mock
.run
.assert_called()
457 prepare_order
.assert_called_with(compute_value
="default")
458 self
.m
.report
.log_order
.assert_called()
459 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
461 mock
.call(order_mock
, 7, update
="market_fallback",
462 compute_value
='default',
463 new_order
=new_order_mock
),
464 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
466 self
.m
.report
.log_order
.assert_has_calls(calls
)
468 order_mock
.reset_mock()
469 new_order_mock
.reset_mock()
471 self
.m
.report
.log_order
.reset_mock()
473 for i
in [10, 13, 16]:
474 with self
.subTest(tick
=i
):
475 trade
.update_order(order_mock
, i
)
476 order_mock
.cancel
.assert_called()
477 new_order_mock
.run
.assert_called()
478 prepare_order
.assert_called_with(compute_value
="default")
479 self
.m
.report
.log_order
.assert_called()
480 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
482 mock
.call(order_mock
, i
, update
="market_adjust",
483 compute_value
='default',
484 new_order
=new_order_mock
),
485 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
487 self
.m
.report
.log_order
.assert_has_calls(calls
)
489 order_mock
.reset_mock()
490 new_order_mock
.reset_mock()
492 self
.m
.report
.log_order
.reset_mock()
494 for i
in [8, 9, 11, 12]:
495 with self
.subTest(tick
=i
):
496 trade
.update_order(order_mock
, i
)
497 order_mock
.cancel
.assert_not_called()
498 new_order_mock
.run
.assert_not_called()
499 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
500 compute_value
=None, new_order
=None)
502 order_mock
.reset_mock()
503 new_order_mock
.reset_mock()
505 self
.m
.report
.log_order
.reset_mock()
508 def test_print_with_order(self
):
509 value_from
= portfolio
.Amount("BTC", "0.5")
510 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
511 value_to
= portfolio
.Amount("BTC", "1.0")
512 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
514 order_mock1
= mock
.Mock()
515 order_mock1
.__repr__
= mock
.Mock()
516 order_mock1
.__repr__
.return_value
= "Mock 1"
517 order_mock2
= mock
.Mock()
518 order_mock2
.__repr__
= mock
.Mock()
519 order_mock2
.__repr__
.return_value
= "Mock 2"
520 order_mock1
.mouvements
= []
521 mouvement_mock1
= mock
.Mock()
522 mouvement_mock1
.__repr__
= mock
.Mock()
523 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
524 mouvement_mock2
= mock
.Mock()
525 mouvement_mock2
.__repr__
= mock
.Mock()
526 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
527 order_mock2
.mouvements
= [
528 mouvement_mock1
, mouvement_mock2
530 trade
.orders
.append(order_mock1
)
531 trade
.orders
.append(order_mock2
)
533 with mock
.patch
.object(trade
, "filled_amount") as filled
:
534 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
536 trade
.print_with_order()
538 self
.m
.report
.print_log
.assert_called()
539 calls
= self
.m
.report
.print_log
.mock_calls
540 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
541 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
542 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
543 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
544 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
546 self
.m
.report
.print_log
.reset_mock()
548 filled
.return_value
= portfolio
.Amount("BTC", "0.5")
549 trade
.print_with_order()
550 calls
= self
.m
.report
.print_log
.mock_calls
551 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls
[0][1][0]))
553 self
.m
.report
.print_log
.reset_mock()
555 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
557 trade
.print_with_order()
558 calls
= self
.m
.report
.print_log
.mock_calls
559 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls
[0][1][0]))
561 def test_close(self
):
562 value_from
= portfolio
.Amount("BTC", "0.5")
563 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
564 value_to
= portfolio
.Amount("BTC", "1.0")
565 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
567 trade
.orders
.append(order1
)
571 self
.assertEqual(True, trade
.closed
)
572 order1
.cancel
.assert_called_once_with()
574 def test_pending(self
):
575 value_from
= portfolio
.Amount("BTC", "0.5")
576 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
577 value_to
= portfolio
.Amount("BTC", "1.0")
578 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
581 self
.assertEqual(False, trade
.pending
)
584 self
.assertEqual(True, trade
.pending
)
587 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
588 trade
.orders
.append(order1
)
589 self
.assertEqual(False, trade
.pending
)
591 def test__repr(self
):
592 value_from
= portfolio
.Amount("BTC", "0.5")
593 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
594 value_to
= portfolio
.Amount("BTC", "1.0")
595 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
597 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
599 def test_as_json(self
):
600 value_from
= portfolio
.Amount("BTC", "0.5")
601 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
602 value_to
= portfolio
.Amount("BTC", "1.0")
603 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
605 as_json
= trade
.as_json()
606 self
.assertEqual("acquire", as_json
["action"])
607 self
.assertEqual(D("0.5"), as_json
["from"])
608 self
.assertEqual(D("1.0"), as_json
["to"])
609 self
.assertEqual("ETH", as_json
["currency"])
610 self
.assertEqual("BTC", as_json
["base_currency"])
612 class BalanceTest(WebMockTestCase
):
613 def test_values(self
):
614 balance
= portfolio
.Balance("BTC", {
615 "exchange_total": "0.65",
616 "exchange_free": "0.35",
617 "exchange_used": "0.30",
618 "margin_total": "-10",
619 "margin_borrowed": "10",
620 "margin_available": "0",
621 "margin_in_position": "0",
622 "margin_position_type": "short",
623 "margin_borrowed_base_currency": "USDT",
624 "margin_liquidation_price": "1.20",
625 "margin_pending_gain": "10",
626 "margin_lending_fees": "0.4",
627 "margin_borrowed_base_price": "0.15",
629 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
630 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
631 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
632 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
633 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
634 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
636 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
637 self
.assertEqual(portfolio
.D("10"), balance
.margin_borrowed
.value
)
638 self
.assertEqual(portfolio
.D("0"), balance
.margin_available
.value
)
639 self
.assertEqual("BTC", balance
.margin_total
.currency
)
640 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
641 self
.assertEqual("BTC", balance
.margin_available
.currency
)
643 self
.assertEqual("BTC", balance
.currency
)
645 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
646 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
648 def test__repr(self
):
649 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
650 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
651 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
652 "exchange_used": 1, "exchange_free": 2 })
653 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
655 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
656 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
658 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
659 "margin_in_position": 1, "margin_available": 2 })
660 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
662 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
663 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
665 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
666 "margin_borrowed_base_price": D("0.1"),
667 "margin_borrowed_base_currency": "BTC",
668 "margin_lending_fees": D("0.002") })
669 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
671 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
672 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
673 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
675 def test_as_json(self
):
676 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
677 as_json
= balance
.as_json()
678 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
679 self
.assertEqual(D(0), as_json
["total"])
680 self
.assertEqual(D(2), as_json
["exchange_total"])
681 self
.assertEqual(D(2), as_json
["exchange_free"])
682 self
.assertEqual(D(0), as_json
["exchange_used"])
683 self
.assertEqual(D(0), as_json
["margin_total"])
684 self
.assertEqual(D(0), as_json
["margin_available"])
685 self
.assertEqual(D(0), as_json
["margin_borrowed"])
687 class OrderTest(WebMockTestCase
):
688 def test_values(self
):
689 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
690 D("0.1"), "BTC", "long", "market", "trade")
691 self
.assertEqual("buy", order
.action
)
692 self
.assertEqual(10, order
.amount
.value
)
693 self
.assertEqual("ETH", order
.amount
.currency
)
694 self
.assertEqual(D("0.1"), order
.rate
)
695 self
.assertEqual("BTC", order
.base_currency
)
696 self
.assertEqual("market", order
.market
)
697 self
.assertEqual("long", order
.trade_type
)
698 self
.assertEqual("pending", order
.status
)
699 self
.assertEqual("trade", order
.trade
)
700 self
.assertIsNone(order
.id)
701 self
.assertFalse(order
.close_if_possible
)
703 def test__repr(self
):
704 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
705 D("0.1"), "BTC", "long", "market", "trade")
706 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
708 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
709 D("0.1"), "BTC", "long", "market", "trade",
710 close_if_possible
=True)
711 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
713 def test_as_json(self
):
714 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
715 D("0.1"), "BTC", "long", "market", "trade")
716 mouvement_mock1
= mock
.Mock()
717 mouvement_mock1
.as_json
.return_value
= 1
718 mouvement_mock2
= mock
.Mock()
719 mouvement_mock2
.as_json
.return_value
= 2
721 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
722 as_json
= order
.as_json()
723 self
.assertEqual("buy", as_json
["action"])
724 self
.assertEqual("long", as_json
["trade_type"])
725 self
.assertEqual(10, as_json
["amount"])
726 self
.assertEqual("ETH", as_json
["currency"])
727 self
.assertEqual("BTC", as_json
["base_currency"])
728 self
.assertEqual(D("0.1"), as_json
["rate"])
729 self
.assertEqual("pending", as_json
["status"])
730 self
.assertEqual(False, as_json
["close_if_possible"])
731 self
.assertIsNone(as_json
["id"])
732 self
.assertEqual([1, 2], as_json
["mouvements"])
734 def test_account(self
):
735 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
736 D("0.1"), "BTC", "long", "market", "trade")
737 self
.assertEqual("exchange", order
.account
)
739 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
740 D("0.1"), "BTC", "short", "market", "trade")
741 self
.assertEqual("margin", order
.account
)
743 def test_pending(self
):
744 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
745 D("0.1"), "BTC", "long", "market", "trade")
746 self
.assertTrue(order
.pending
)
747 order
.status
= "open"
748 self
.assertFalse(order
.pending
)
751 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
752 D("0.1"), "BTC", "long", "market", "trade")
753 self
.assertFalse(order
.open)
754 order
.status
= "open"
755 self
.assertTrue(order
.open)
757 def test_finished(self
):
758 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
759 D("0.1"), "BTC", "long", "market", "trade")
760 self
.assertFalse(order
.finished
)
761 order
.status
= "closed"
762 self
.assertTrue(order
.finished
)
763 order
.status
= "canceled"
764 self
.assertTrue(order
.finished
)
765 order
.status
= "error"
766 self
.assertTrue(order
.finished
)
768 @mock.patch.object(portfolio
.Order
, "fetch")
769 def test_cancel(self
, fetch
):
770 with self
.subTest(debug
=True):
772 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
773 D("0.1"), "BTC", "long", self
.m
, "trade")
774 order
.status
= "open"
777 self
.m
.ccxt
.cancel_order
.assert_not_called()
778 self
.m
.report
.log_debug_action
.assert_called_once()
779 self
.m
.report
.log_debug_action
.reset_mock()
780 self
.assertEqual("canceled", order
.status
)
782 with self
.subTest(desc
="Nominal case"):
784 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
785 D("0.1"), "BTC", "long", self
.m
, "trade")
786 order
.status
= "open"
790 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
791 fetch
.assert_called_once_with()
792 self
.m
.report
.log_debug_action
.assert_not_called()
794 with self
.subTest(exception
=True):
795 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
796 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
797 D("0.1"), "BTC", "long", self
.m
, "trade")
798 order
.status
= "open"
801 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
802 self
.m
.report
.log_error
.assert_called_once()
805 with self
.subTest(id=None):
806 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
807 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
808 D("0.1"), "BTC", "long", self
.m
, "trade")
809 order
.status
= "open"
811 self
.m
.ccxt
.cancel_order
.assert_not_called()
814 with self
.subTest(open=False):
815 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
816 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
817 D("0.1"), "BTC", "long", self
.m
, "trade")
818 order
.status
= "closed"
820 self
.m
.ccxt
.cancel_order
.assert_not_called()
822 def test_dust_amount_remaining(self
):
823 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
824 D("0.1"), "BTC", "long", self
.m
, "trade")
825 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", 1))
826 self
.assertFalse(order
.dust_amount_remaining())
828 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", D("0.0001")))
829 self
.assertTrue(order
.dust_amount_remaining())
831 @mock.patch.object(portfolio
.Order
, "fetch")
832 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
833 def test_remaining_amount(self
, filled_amount
, fetch
):
834 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
835 D("0.1"), "BTC", "long", self
.m
, "trade")
837 self
.assertEqual(9, order
.remaining_amount().value
)
839 order
.status
= "open"
840 self
.assertEqual(9, order
.remaining_amount().value
)
842 @mock.patch.object(portfolio
.Order
, "fetch")
843 def test_filled_amount(self
, fetch
):
844 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
845 D("0.1"), "BTC", "long", self
.m
, "trade")
846 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
847 "tradeID": 42, "type": "buy", "fee": "0.0015",
848 "date": "2017-12-30 12:00:12", "rate": "0.1",
849 "amount": "3", "total": "0.3"
851 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
852 "tradeID": 43, "type": "buy", "fee": "0.0015",
853 "date": "2017-12-30 13:00:12", "rate": "0.2",
854 "amount": "2", "total": "0.4"
856 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
857 fetch
.assert_not_called()
858 order
.status
= "open"
859 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
860 fetch
.assert_called_once()
861 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
863 def test_fetch_mouvements(self
):
864 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
866 "tradeID": 42, "type": "buy", "fee": "0.0015",
867 "date": "2017-12-30 13:00:12", "rate": "0.1",
868 "amount": "3", "total": "0.3"
871 "tradeID": 43, "type": "buy", "fee": "0.0015",
872 "date": "2017-12-30 12:00:12", "rate": "0.2",
873 "amount": "2", "total": "0.4"
876 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
877 D("0.1"), "BTC", "long", self
.m
, "trade")
879 order
.mouvements
= ["Foo", "Bar", "Baz"]
881 order
.fetch_mouvements()
883 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
884 self
.assertEqual(2, len(order
.mouvements
))
885 self
.assertEqual(43, order
.mouvements
[0].id)
886 self
.assertEqual(42, order
.mouvements
[1].id)
888 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
889 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
890 D("0.1"), "BTC", "long", self
.m
, "trade")
891 order
.fetch_mouvements()
892 self
.assertEqual(0, len(order
.mouvements
))
894 def test_mark_finished_order(self
):
895 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
896 D("0.1"), "BTC", "short", self
.m
, "trade",
897 close_if_possible
=True)
898 order
.status
= "closed"
901 order
.mark_finished_order()
902 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
903 self
.m
.ccxt
.close_margin_position
.reset_mock()
905 order
.status
= "open"
906 order
.mark_finished_order()
907 self
.m
.ccxt
.close_margin_position
.assert_not_called()
909 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
910 D("0.1"), "BTC", "short", self
.m
, "trade",
911 close_if_possible
=False)
912 order
.status
= "closed"
913 order
.mark_finished_order()
914 self
.m
.ccxt
.close_margin_position
.assert_not_called()
916 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
917 D("0.1"), "BTC", "short", self
.m
, "trade",
918 close_if_possible
=True)
919 order
.status
= "closed"
920 order
.mark_finished_order()
921 self
.m
.ccxt
.close_margin_position
.assert_not_called()
923 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
924 D("0.1"), "BTC", "long", self
.m
, "trade",
925 close_if_possible
=True)
926 order
.status
= "closed"
927 order
.mark_finished_order()
928 self
.m
.ccxt
.close_margin_position
.assert_not_called()
932 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
933 D("0.1"), "BTC", "short", self
.m
, "trade",
934 close_if_possible
=True)
935 order
.status
= "closed"
937 order
.mark_finished_order()
938 self
.m
.ccxt
.close_margin_position
.assert_not_called()
939 self
.m
.report
.log_debug_action
.assert_called_once()
941 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
942 @mock.patch.object(portfolio
.Order
, "mark_disappeared_order")
943 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
944 def test_fetch(self
, mark_finished_order
, mark_disappeared_order
, fetch_mouvements
):
945 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
946 D("0.1"), "BTC", "long", self
.m
, "trade")
948 with self
.subTest(debug
=True):
951 self
.m
.report
.log_debug_action
.assert_called_once()
952 self
.m
.report
.log_debug_action
.reset_mock()
953 self
.m
.ccxt
.fetch_order
.assert_not_called()
954 mark_finished_order
.assert_not_called()
955 mark_disappeared_order
.assert_not_called()
956 fetch_mouvements
.assert_not_called()
958 with self
.subTest(debug
=False):
960 self
.m
.ccxt
.fetch_order
.return_value
= {
962 "datetime": "timestamp"
966 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
967 fetch_mouvements
.assert_called_once()
968 self
.assertEqual("foo", order
.status
)
969 self
.assertEqual("timestamp", order
.timestamp
)
970 self
.assertEqual(1, len(order
.results
))
971 self
.m
.report
.log_debug_action
.assert_not_called()
972 mark_finished_order
.assert_called_once()
973 mark_disappeared_order
.assert_called_once()
975 mark_finished_order
.reset_mock()
976 with self
.subTest(missing_order
=True):
977 self
.m
.ccxt
.fetch_order
.side_effect
= [
978 portfolio
.OrderNotCached
,
981 self
.assertEqual("closed_unknown", order
.status
)
982 mark_finished_order
.assert_called_once()
984 def test_mark_disappeared_order(self
):
985 with self
.subTest("Open order"):
986 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
987 D("0.1"), "BTC", "long", self
.m
, "trade")
989 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
991 "currencyPair":"BTC_XRP",
994 "amount":"0.00000222",
995 "total":"0.00000000",
997 "date":"2018-04-02 00:09:13"
999 order
.mark_disappeared_order()
1000 self
.assertEqual("pending", order
.status
)
1002 with self
.subTest("Non-zero amount"):
1003 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1004 D("0.1"), "BTC", "long", self
.m
, "trade")
1006 order
.status
= "closed"
1007 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1009 "currencyPair":"BTC_XRP",
1011 "rate":"0.00007013",
1012 "amount":"0.00000222",
1013 "total":"0.00000010",
1015 "date":"2018-04-02 00:09:13"
1017 order
.mark_disappeared_order()
1018 self
.assertEqual("closed", order
.status
)
1020 with self
.subTest("Other mouvements"):
1021 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1022 D("0.1"), "BTC", "long", self
.m
, "trade")
1024 order
.status
= "closed"
1025 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1027 "currencyPair":"BTC_XRP",
1029 "rate":"0.00007013",
1030 "amount":"0.00000222",
1031 "total":"0.00000001",
1033 "date":"2018-04-02 00:09:13"
1035 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1037 "currencyPair":"BTC_XRP",
1039 "rate":"0.00007013",
1040 "amount":"0.00000222",
1041 "total":"0.00000000",
1043 "date":"2018-04-02 00:09:13"
1045 order
.mark_disappeared_order()
1046 self
.assertEqual("error_disappeared", order
.status
)
1048 with self
.subTest("Order disappeared"):
1049 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1050 D("0.1"), "BTC", "long", self
.m
, "trade")
1052 order
.status
= "closed"
1053 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1055 "currencyPair":"BTC_XRP",
1057 "rate":"0.00007013",
1058 "amount":"0.00000222",
1059 "total":"0.00000000",
1061 "date":"2018-04-02 00:09:13"
1063 order
.mark_disappeared_order()
1064 self
.assertEqual("error_disappeared", order
.status
)
1066 @mock.patch.object(portfolio
.Order
, "fetch")
1067 def test_get_status(self
, fetch
):
1068 with self
.subTest(debug
=True):
1070 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1071 D("0.1"), "BTC", "long", self
.m
, "trade")
1072 self
.assertEqual("pending", order
.get_status())
1073 fetch
.assert_not_called()
1074 self
.m
.report
.log_debug_action
.assert_called_once()
1076 with self
.subTest(debug
=False, finished
=False):
1077 self
.m
.debug
= False
1078 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1079 D("0.1"), "BTC", "long", self
.m
, "trade")
1081 def update_status():
1082 order
.status
= "open"
1083 return update_status
1084 fetch
.side_effect
= _fetch(order
)
1085 self
.assertEqual("open", order
.get_status())
1086 fetch
.assert_called_once()
1089 with self
.subTest(debug
=False, finished
=True):
1090 self
.m
.debug
= False
1091 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1092 D("0.1"), "BTC", "long", self
.m
, "trade")
1094 def update_status():
1095 order
.status
= "closed"
1096 return update_status
1097 fetch
.side_effect
= _fetch(order
)
1098 self
.assertEqual("closed", order
.get_status())
1099 fetch
.assert_called_once()
1102 self
.m
.ccxt
.order_precision
.return_value
= 4
1103 with self
.subTest(debug
=True):
1105 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1106 D("0.1"), "BTC", "long", self
.m
, "trade")
1108 self
.m
.ccxt
.create_order
.assert_not_called()
1109 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1110 self
.assertEqual("open", order
.status
)
1111 self
.assertEqual(1, len(order
.results
))
1112 self
.assertEqual(-1, order
.id)
1114 self
.m
.ccxt
.create_order
.reset_mock()
1115 with self
.subTest(debug
=False):
1116 self
.m
.debug
= False
1117 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1118 D("0.1"), "BTC", "long", self
.m
, "trade")
1119 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
1121 self
.m
.ccxt
.create_order
.assert_called_once()
1122 self
.assertEqual(1, len(order
.results
))
1123 self
.assertEqual("open", order
.status
)
1125 self
.m
.ccxt
.create_order
.reset_mock()
1126 with self
.subTest(exception
=True):
1127 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1128 D("0.1"), "BTC", "long", self
.m
, "trade")
1129 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
1131 self
.m
.ccxt
.create_order
.assert_called_once()
1132 self
.assertEqual(0, len(order
.results
))
1133 self
.assertEqual("error", order
.status
)
1134 self
.m
.report
.log_error
.assert_called_once()
1136 self
.m
.ccxt
.create_order
.reset_mock()
1137 with self
.subTest(dust_amount_exception
=True),\
1138 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1139 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
1140 D("0.1"), "BTC", "long", self
.m
, "trade")
1141 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
1143 self
.m
.ccxt
.create_order
.assert_called_once()
1144 self
.assertEqual(0, len(order
.results
))
1145 self
.assertEqual("closed", order
.status
)
1146 mark_finished_order
.assert_called_once()
1148 self
.m
.ccxt
.order_precision
.return_value
= 8
1149 self
.m
.ccxt
.create_order
.reset_mock()
1150 with self
.subTest(insufficient_funds
=True),\
1151 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1152 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1153 D("0.1"), "BTC", "long", self
.m
, "trade")
1154 self
.m
.ccxt
.create_order
.side_effect
= [
1155 portfolio
.InsufficientFunds
,
1156 portfolio
.InsufficientFunds
,
1157 portfolio
.InsufficientFunds
,
1161 self
.m
.ccxt
.create_order
.assert_has_calls([
1162 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1163 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1164 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1165 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1167 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
1168 self
.assertEqual(1, len(order
.results
))
1169 self
.assertEqual("open", order
.status
)
1170 self
.assertEqual(4, order
.tries
)
1171 self
.m
.report
.log_error
.assert_called()
1172 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
1174 self
.m
.ccxt
.order_precision
.return_value
= 8
1175 self
.m
.ccxt
.create_order
.reset_mock()
1176 self
.m
.report
.log_error
.reset_mock()
1177 with self
.subTest(insufficient_funds
=True),\
1178 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1179 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1180 D("0.1"), "BTC", "long", self
.m
, "trade")
1181 self
.m
.ccxt
.create_order
.side_effect
= [
1182 portfolio
.InsufficientFunds
,
1183 portfolio
.InsufficientFunds
,
1184 portfolio
.InsufficientFunds
,
1185 portfolio
.InsufficientFunds
,
1186 portfolio
.InsufficientFunds
,
1189 self
.m
.ccxt
.create_order
.assert_has_calls([
1190 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1191 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1192 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1193 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1194 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
1196 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1197 self
.assertEqual(0, len(order
.results
))
1198 self
.assertEqual("error", order
.status
)
1199 self
.assertEqual(5, order
.tries
)
1200 self
.m
.report
.log_error
.assert_called()
1201 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1202 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception
=mock
.ANY
)
1205 with self
.subTest(invalid_nonce
=True):
1206 with self
.subTest(retry_success
=True):
1207 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1208 D("0.1"), "BTC", "long", self
.m
, "trade")
1209 self
.m
.ccxt
.create_order
.side_effect
= [
1210 portfolio
.InvalidNonce
,
1211 portfolio
.InvalidNonce
,
1215 self
.m
.ccxt
.create_order
.assert_has_calls([
1216 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1217 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1218 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1220 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1221 self
.assertEqual(3, order
.tries
)
1222 self
.m
.report
.log_error
.assert_called()
1223 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1224 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after invalid nonce", exception
=mock
.ANY
)
1225 self
.assertEqual(123, order
.id)
1228 with self
.subTest(retry_success
=False):
1229 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1230 D("0.1"), "BTC", "long", self
.m
, "trade")
1231 self
.m
.ccxt
.create_order
.side_effect
= [
1232 portfolio
.InvalidNonce
,
1233 portfolio
.InvalidNonce
,
1234 portfolio
.InvalidNonce
,
1235 portfolio
.InvalidNonce
,
1236 portfolio
.InvalidNonce
,
1239 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1240 self
.assertEqual(5, order
.tries
)
1241 self
.m
.report
.log_error
.assert_called()
1242 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1243 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after invalid nonce", exception
=mock
.ANY
)
1244 self
.assertEqual("error", order
.status
)
1247 with self
.subTest(request_timeout
=True):
1248 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1249 D("0.1"), "BTC", "long", self
.m
, "trade")
1250 with self
.subTest(retrieved
=False), \
1251 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1252 self
.m
.ccxt
.create_order
.side_effect
= [
1253 portfolio
.RequestTimeout
,
1254 portfolio
.RequestTimeout
,
1257 retrieve
.return_value
= False
1259 self
.m
.ccxt
.create_order
.assert_has_calls([
1260 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1261 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1262 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1264 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1265 self
.assertEqual(3, order
.tries
)
1266 self
.m
.report
.log_error
.assert_called()
1267 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1268 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after timeout", exception
=mock
.ANY
)
1269 self
.assertEqual(123, order
.id)
1272 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1273 D("0.1"), "BTC", "long", self
.m
, "trade")
1274 with self
.subTest(retrieved
=True), \
1275 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1276 self
.m
.ccxt
.create_order
.side_effect
= [
1277 portfolio
.RequestTimeout
,
1280 order
.results
.append({"id": 123}
)
1282 retrieve
.side_effect
= _retrieve
1284 self
.m
.ccxt
.create_order
.assert_has_calls([
1285 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1287 self
.assertEqual(1, self
.m
.ccxt
.create_order
.call_count
)
1288 self
.assertEqual(1, order
.tries
)
1289 self
.m
.report
.log_error
.assert_called()
1290 self
.assertEqual(1, self
.m
.report
.log_error
.call_count
)
1291 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Timeout, found the order")
1292 self
.assertEqual(123, order
.id)
1295 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1296 D("0.1"), "BTC", "long", self
.m
, "trade")
1297 with self
.subTest(retrieved
=False), \
1298 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1299 self
.m
.ccxt
.create_order
.side_effect
= [
1300 portfolio
.RequestTimeout
,
1301 portfolio
.RequestTimeout
,
1302 portfolio
.RequestTimeout
,
1303 portfolio
.RequestTimeout
,
1304 portfolio
.RequestTimeout
,
1306 retrieve
.return_value
= False
1308 self
.m
.ccxt
.create_order
.assert_has_calls([
1309 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1310 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1311 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1312 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1313 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1315 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1316 self
.assertEqual(5, order
.tries
)
1317 self
.m
.report
.log_error
.assert_called()
1318 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1319 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after timeouts", exception
=mock
.ANY
)
1320 self
.assertEqual("error", order
.status
)
1322 def test_retrieve_order(self
):
1323 with self
.subTest(similar_open_order
=True):
1324 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1325 D("0.1"), "BTC", "long", self
.m
, "trade")
1326 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1328 self
.m
.ccxt
.order_precision
.return_value
= 8
1329 self
.m
.ccxt
.fetch_orders
.return_value
= [
1331 'amount': 0.002, 'cost': 0.1,
1332 'datetime': '2018-03-25T15:15:51.000Z',
1333 'fee': None, 'filled': 0.0,
1337 'date': '2018-03-25 15:15:51',
1338 'margin': 0, 'orderNumber': '1',
1339 'price': '0.1', 'rate': '0.1',
1340 'side': 'buy', 'startingAmount': '0.002',
1341 'status': 'open', 'total': '0.0002',
1344 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1345 'status': 'open', 'symbol': 'ETH/BTC',
1346 'timestamp': 1521990951000, 'trades': None,
1350 'amount': 0.001, 'cost': 0.1,
1351 'datetime': '2018-03-25T15:15:51.000Z',
1352 'fee': None, 'filled': 0.0,
1356 'date': '2018-03-25 15:15:51',
1357 'margin': 1, 'orderNumber': '2',
1358 'price': '0.1', 'rate': '0.1',
1359 'side': 'buy', 'startingAmount': '0.001',
1360 'status': 'open', 'total': '0.0001',
1363 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1364 'status': 'open', 'symbol': 'ETH/BTC',
1365 'timestamp': 1521990951000, 'trades': None,
1369 'amount': 0.001, 'cost': 0.1,
1370 'datetime': '2018-03-25T15:15:51.000Z',
1371 'fee': None, 'filled': 0.0,
1375 'date': '2018-03-25 15:15:51',
1376 'margin': 0, 'orderNumber': '3',
1377 'price': '0.1', 'rate': '0.1',
1378 'side': 'sell', 'startingAmount': '0.001',
1379 'status': 'open', 'total': '0.0001',
1382 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1383 'status': 'open', 'symbol': 'ETH/BTC',
1384 'timestamp': 1521990951000, 'trades': None,
1388 'amount': 0.001, 'cost': 0.15,
1389 'datetime': '2018-03-25T15:15:51.000Z',
1390 'fee': None, 'filled': 0.0,
1394 'date': '2018-03-25 15:15:51',
1395 'margin': 0, 'orderNumber': '4',
1396 'price': '0.15', 'rate': '0.15',
1397 'side': 'buy', 'startingAmount': '0.001',
1398 'status': 'open', 'total': '0.0001',
1401 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1402 'status': 'open', 'symbol': 'ETH/BTC',
1403 'timestamp': 1521990951000, 'trades': None,
1407 'amount': 0.001, 'cost': 0.1,
1408 'datetime': '2018-03-25T15:15:51.000Z',
1409 'fee': None, 'filled': 0.0,
1413 'date': '2018-03-25 15:15:51',
1414 'margin': 0, 'orderNumber': '1',
1415 'price': '0.1', 'rate': '0.1',
1416 'side': 'buy', 'startingAmount': '0.001',
1417 'status': 'open', 'total': '0.0001',
1420 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1421 'status': 'open', 'symbol': 'ETH/BTC',
1422 'timestamp': 1521990951000, 'trades': None,
1426 result
= order
.retrieve_order()
1427 self
.assertTrue(result
)
1428 self
.assertEqual('5', order
.results
[0]["id"])
1429 self
.m
.ccxt
.fetch_my_trades
.assert_not_called()
1430 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1433 with self
.subTest(similar_open_order
=False, past_trades
=True):
1434 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1435 D("0.1"), "BTC", "long", self
.m
, "trade")
1436 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1438 self
.m
.ccxt
.order_precision
.return_value
= 8
1439 self
.m
.ccxt
.fetch_orders
.return_value
= []
1440 self
.m
.ccxt
.fetch_my_trades
.return_value
= [
1441 { # Wrong timestamp 1
1444 'datetime': '2018-03-25T15:15:14.000Z',
1448 'category': 'exchange',
1449 'date': '2018-03-25 15:15:14',
1450 'fee': '0.00150000',
1461 'symbol': 'ETH/BTC',
1462 'timestamp': 1521983714,
1465 { # Wrong timestamp 2
1468 'datetime': '2018-03-25T15:16:54.000Z',
1472 'category': 'exchange',
1473 'date': '2018-03-25 15:16:54',
1474 'fee': '0.00150000',
1485 'symbol': 'ETH/BTC',
1486 'timestamp': 1521983814,
1492 'datetime': '2018-03-25T15:15:54.000Z',
1496 'category': 'exchange',
1497 'date': '2018-03-25 15:15:54',
1498 'fee': '0.00150000',
1509 'symbol': 'ETH/BTC',
1510 'timestamp': 1521983754,
1516 'datetime': '2018-03-25T15:16:54.000Z',
1520 'category': 'exchange',
1521 'date': '2018-03-25 15:16:54',
1522 'fee': '0.00150000',
1533 'symbol': 'ETH/BTC',
1534 'timestamp': 1521983814,
1540 'datetime': '2018-03-25T15:15:54.000Z',
1544 'category': 'marginTrade',
1545 'date': '2018-03-25 15:15:54',
1546 'fee': '0.00150000',
1557 'symbol': 'ETH/BTC',
1558 'timestamp': 1521983754,
1564 'datetime': '2018-03-25T15:16:54.000Z',
1568 'category': 'marginTrade',
1569 'date': '2018-03-25 15:16:54',
1570 'fee': '0.00150000',
1581 'symbol': 'ETH/BTC',
1582 'timestamp': 1521983814,
1588 'datetime': '2018-03-25T15:15:54.000Z',
1592 'category': 'exchange',
1593 'date': '2018-03-25 15:15:54',
1594 'fee': '0.00150000',
1605 'symbol': 'ETH/BTC',
1606 'timestamp': 1521983754,
1612 'datetime': '2018-03-25T15:16:54.000Z',
1616 'category': 'exchange',
1617 'date': '2018-03-25 15:16:54',
1618 'fee': '0.00150000',
1629 'symbol': 'ETH/BTC',
1630 'timestamp': 1521983814,
1636 'datetime': '2018-03-25T15:15:54.000Z',
1640 'category': 'exchange',
1641 'date': '2018-03-25 15:15:54',
1642 'fee': '0.00150000',
1646 'total': '0.000066',
1653 'symbol': 'ETH/BTC',
1654 'timestamp': 1521983754,
1660 'datetime': '2018-03-25T15:16:54.000Z',
1664 'category': 'exchange',
1665 'date': '2018-03-25 15:16:54',
1666 'fee': '0.00150000',
1677 'symbol': 'ETH/BTC',
1678 'timestamp': 1521983814,
1684 'datetime': '2018-03-25T15:15:54.000Z',
1688 'category': 'exchange',
1689 'date': '2018-03-25 15:15:54',
1690 'fee': '0.00150000',
1701 'symbol': 'ETH/BTC',
1702 'timestamp': 1521983754,
1708 'datetime': '2018-03-25T15:16:54.000Z',
1712 'category': 'exchange',
1713 'date': '2018-03-25 15:16:54',
1714 'fee': '0.00150000',
1718 'total': '0.000036',
1725 'symbol': 'ETH/BTC',
1726 'timestamp': 1521983814,
1731 result
= order
.retrieve_order()
1732 self
.assertTrue(result
)
1733 self
.assertEqual('7', order
.results
[0]["id"])
1734 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1737 with self
.subTest(similar_open_order
=False, past_trades
=False):
1738 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1739 D("0.1"), "BTC", "long", self
.m
, "trade")
1740 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1742 self
.m
.ccxt
.order_precision
.return_value
= 8
1743 self
.m
.ccxt
.fetch_orders
.return_value
= []
1744 self
.m
.ccxt
.fetch_my_trades
.return_value
= []
1745 result
= order
.retrieve_order()
1746 self
.assertFalse(result
)
1748 class MouvementTest(WebMockTestCase
):
1749 def test_values(self
):
1750 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1751 "tradeID": 42, "type": "buy", "fee": "0.0015",
1752 "date": "2017-12-30 12:00:12", "rate": "0.1",
1753 "amount": "10", "total": "1"
1755 self
.assertEqual("ETH", mouvement
.currency
)
1756 self
.assertEqual("BTC", mouvement
.base_currency
)
1757 self
.assertEqual(42, mouvement
.id)
1758 self
.assertEqual("buy", mouvement
.action
)
1759 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
1760 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
1761 self
.assertEqual(D("0.1"), mouvement
.rate
)
1762 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
1763 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
1765 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
1766 self
.assertIsNone(mouvement
.date
)
1767 self
.assertIsNone(mouvement
.id)
1768 self
.assertIsNone(mouvement
.action
)
1769 self
.assertEqual(-1, mouvement
.fee_rate
)
1770 self
.assertEqual(0, mouvement
.rate
)
1771 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
1772 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
1774 def test__repr(self
):
1775 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1776 "tradeID": 42, "type": "buy", "fee": "0.0015",
1777 "date": "2017-12-30 12:00:12", "rate": "0.1",
1778 "amount": "10", "total": "1"
1780 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
1782 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1783 "tradeID": 42, "type": "buy",
1784 "date": "garbage", "rate": "0.1",
1785 "amount": "10", "total": "1"
1787 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
1789 def test_as_json(self
):
1790 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1791 "tradeID": 42, "type": "buy", "fee": "0.0015",
1792 "date": "2017-12-30 12:00:12", "rate": "0.1",
1793 "amount": "10", "total": "1"
1795 as_json
= mouvement
.as_json()
1797 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
1798 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
1799 self
.assertEqual("buy", as_json
["action"])
1800 self
.assertEqual(D("10"), as_json
["total"])
1801 self
.assertEqual(D("1"), as_json
["total_in_base"])
1802 self
.assertEqual("BTC", as_json
["base_currency"])
1803 self
.assertEqual("ETH", as_json
["currency"])
1805 class AmountTest(WebMockTestCase
):
1806 def test_values(self
):
1807 amount
= portfolio
.Amount("BTC", "0.65")
1808 self
.assertEqual(D("0.65"), amount
.value
)
1809 self
.assertEqual("BTC", amount
.currency
)
1811 def test_in_currency(self
):
1812 amount
= portfolio
.Amount("ETC", 10)
1814 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
1816 with self
.subTest(desc
="no ticker for currency"):
1817 self
.m
.get_ticker
.return_value
= None
1819 self
.assertRaises(Exception, amount
.in_currency
, "ETH", self
.m
)
1821 with self
.subTest(desc
="nominal case"):
1822 self
.m
.get_ticker
.return_value
= {
1825 "average": D("0.3"),
1828 converted_amount
= amount
.in_currency("ETH", self
.m
)
1830 self
.assertEqual(D("3.0"), converted_amount
.value
)
1831 self
.assertEqual("ETH", converted_amount
.currency
)
1832 self
.assertEqual(amount
, converted_amount
.linked_to
)
1833 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
1835 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
1836 self
.assertEqual(D("2"), converted_amount
.value
)
1838 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
1839 self
.assertEqual(D("4"), converted_amount
.value
)
1841 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
1842 self
.assertEqual(D("0.2"), converted_amount
.value
)
1844 def test__round(self
):
1845 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
1846 self
.assertEqual(D("1.23456789"), round(amount
).value
)
1847 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
1849 def test__abs(self
):
1850 amount
= portfolio
.Amount("SC", -120)
1851 self
.assertEqual(120, abs(amount
).value
)
1852 self
.assertEqual("SC", abs(amount
).currency
)
1854 amount
= portfolio
.Amount("SC", 10)
1855 self
.assertEqual(10, abs(amount
).value
)
1856 self
.assertEqual("SC", abs(amount
).currency
)
1858 def test__add(self
):
1859 amount1
= portfolio
.Amount("XVG", "12.9")
1860 amount2
= portfolio
.Amount("XVG", "13.1")
1862 self
.assertEqual(26, (amount1
+ amount2
).value
)
1863 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
1865 amount3
= portfolio
.Amount("ETH", "1.6")
1866 with self
.assertRaises(Exception):
1869 amount4
= portfolio
.Amount("ETH", 0.0)
1870 self
.assertEqual(amount1
, amount1
+ amount4
)
1872 self
.assertEqual(amount1
, amount1
+ 0)
1874 def test__radd(self
):
1875 amount
= portfolio
.Amount("XVG", "12.9")
1877 self
.assertEqual(amount
, 0 + amount
)
1878 with self
.assertRaises(Exception):
1881 def test__sub(self
):
1882 amount1
= portfolio
.Amount("XVG", "13.3")
1883 amount2
= portfolio
.Amount("XVG", "13.1")
1885 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
1886 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
1888 amount3
= portfolio
.Amount("ETH", "1.6")
1889 with self
.assertRaises(Exception):
1892 amount4
= portfolio
.Amount("ETH", 0.0)
1893 self
.assertEqual(amount1
, amount1
- amount4
)
1895 def test__rsub(self
):
1896 amount
= portfolio
.Amount("ETH", "1.6")
1897 with self
.assertRaises(Exception):
1900 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
1902 def test__mul(self
):
1903 amount
= portfolio
.Amount("XEM", 11)
1905 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
1906 self
.assertEqual(D("33"), (amount
* 3).value
)
1908 with self
.assertRaises(Exception):
1911 def test__rmul(self
):
1912 amount
= portfolio
.Amount("XEM", 11)
1914 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
1915 self
.assertEqual(D("33"), (3 * amount
).value
)
1917 def test__floordiv(self
):
1918 amount
= portfolio
.Amount("XEM", 11)
1920 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1921 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1923 with self
.assertRaises(Exception):
1926 def test__truediv(self
):
1927 amount
= portfolio
.Amount("XEM", 11)
1929 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1930 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1933 amount1
= portfolio
.Amount("BTD", 11.3)
1934 amount2
= portfolio
.Amount("BTD", 13.1)
1936 self
.assertTrue(amount1
< amount2
)
1937 self
.assertFalse(amount2
< amount1
)
1938 self
.assertFalse(amount1
< amount1
)
1940 amount3
= portfolio
.Amount("BTC", 1.6)
1941 with self
.assertRaises(Exception):
1945 amount1
= portfolio
.Amount("BTD", 11.3)
1946 amount2
= portfolio
.Amount("BTD", 13.1)
1948 self
.assertTrue(amount1
<= amount2
)
1949 self
.assertFalse(amount2
<= amount1
)
1950 self
.assertTrue(amount1
<= amount1
)
1952 amount3
= portfolio
.Amount("BTC", 1.6)
1953 with self
.assertRaises(Exception):
1957 amount1
= portfolio
.Amount("BTD", 11.3)
1958 amount2
= portfolio
.Amount("BTD", 13.1)
1960 self
.assertTrue(amount2
> amount1
)
1961 self
.assertFalse(amount1
> amount2
)
1962 self
.assertFalse(amount1
> amount1
)
1964 amount3
= portfolio
.Amount("BTC", 1.6)
1965 with self
.assertRaises(Exception):
1969 amount1
= portfolio
.Amount("BTD", 11.3)
1970 amount2
= portfolio
.Amount("BTD", 13.1)
1972 self
.assertTrue(amount2
>= amount1
)
1973 self
.assertFalse(amount1
>= amount2
)
1974 self
.assertTrue(amount1
>= amount1
)
1976 amount3
= portfolio
.Amount("BTC", 1.6)
1977 with self
.assertRaises(Exception):
1981 amount1
= portfolio
.Amount("BTD", 11.3)
1982 amount2
= portfolio
.Amount("BTD", 13.1)
1983 amount3
= portfolio
.Amount("BTD", 11.3)
1985 self
.assertFalse(amount1
== amount2
)
1986 self
.assertFalse(amount2
== amount1
)
1987 self
.assertTrue(amount1
== amount3
)
1988 self
.assertFalse(amount2
== 0)
1990 amount4
= portfolio
.Amount("BTC", 1.6)
1991 with self
.assertRaises(Exception):
1994 amount5
= portfolio
.Amount("BTD", 0)
1995 self
.assertTrue(amount5
== 0)
1998 amount1
= portfolio
.Amount("BTD", 11.3)
1999 amount2
= portfolio
.Amount("BTD", 13.1)
2000 amount3
= portfolio
.Amount("BTD", 11.3)
2002 self
.assertTrue(amount1
!= amount2
)
2003 self
.assertTrue(amount2
!= amount1
)
2004 self
.assertFalse(amount1
!= amount3
)
2005 self
.assertTrue(amount2
!= 0)
2007 amount4
= portfolio
.Amount("BTC", 1.6)
2008 with self
.assertRaises(Exception):
2011 amount5
= portfolio
.Amount("BTD", 0)
2012 self
.assertFalse(amount5
!= 0)
2014 def test__neg(self
):
2015 amount1
= portfolio
.Amount("BTD", "11.3")
2017 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
2019 def test__str(self
):
2020 amount1
= portfolio
.Amount("BTX", 32)
2021 self
.assertEqual("32.00000000 BTX", str(amount1
))
2023 amount2
= portfolio
.Amount("USDT", 12000)
2024 amount1
.linked_to
= amount2
2025 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
2027 def test__repr(self
):
2028 amount1
= portfolio
.Amount("BTX", 32)
2029 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
2031 amount2
= portfolio
.Amount("USDT", 12000)
2032 amount1
.linked_to
= amount2
2033 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
2035 amount3
= portfolio
.Amount("BTC", 0.1)
2036 amount2
.linked_to
= amount3
2037 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
2039 def test_as_json(self
):
2040 amount
= portfolio
.Amount("BTX", 32)
2041 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
2043 amount
= portfolio
.Amount("BTX", "1E-10")
2044 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
2046 amount
= portfolio
.Amount("BTX", "1E-5")
2047 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
2048 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))