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
= { "inverted": False }
222 with self
.subTest(desc
="Already filled"):
223 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
224 compute_value
.return_value
= D("0.125")
226 value_from
= portfolio
.Amount("BTC", "10")
227 value_from
.rate
= D("0.1")
228 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
229 value_to
= portfolio
.Amount("BTC", "0")
230 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
232 trade
.prepare_order()
234 filled_amount
.assert_called_with(in_base_currency
=False)
235 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
236 self
.assertEqual(0, len(trade
.orders
))
237 self
.m
.report
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
238 Order
.assert_not_called()
240 with self
.subTest(action
="dispose", inverted
=False):
241 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
242 compute_value
.return_value
= D("0.125")
244 value_from
= portfolio
.Amount("BTC", "10")
245 value_from
.rate
= D("0.1")
246 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
247 value_to
= portfolio
.Amount("BTC", "1")
248 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
250 trade
.prepare_order()
252 filled_amount
.assert_called_with(in_base_currency
=False)
253 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
254 self
.assertEqual(1, len(trade
.orders
))
255 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
256 D("0.125"), "BTC", "long", self
.m
,
257 trade
, close_if_possible
=False)
259 with self
.subTest(action
="dispose", inverted
=False, close_if_possible
=True):
260 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
261 compute_value
.return_value
= D("0.125")
263 value_from
= portfolio
.Amount("BTC", "10")
264 value_from
.rate
= D("0.1")
265 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
266 value_to
= portfolio
.Amount("BTC", "1")
267 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
269 trade
.prepare_order(close_if_possible
=True)
271 filled_amount
.assert_called_with(in_base_currency
=False)
272 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
273 self
.assertEqual(1, len(trade
.orders
))
274 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
275 D("0.125"), "BTC", "long", self
.m
,
276 trade
, close_if_possible
=True)
278 with self
.subTest(action
="acquire", inverted
=False):
279 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
280 compute_value
.return_value
= D("0.125")
282 value_from
= portfolio
.Amount("BTC", "1")
283 value_from
.rate
= D("0.1")
284 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
285 value_to
= portfolio
.Amount("BTC", "10")
286 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
288 trade
.prepare_order()
290 filled_amount
.assert_called_with(in_base_currency
=True)
291 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "buy", compute_value
="default")
292 self
.assertEqual(1, len(trade
.orders
))
294 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
295 D("0.125"), "BTC", "long", self
.m
,
296 trade
, close_if_possible
=False)
298 with self
.subTest(close_if_possible
=True):
299 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
300 compute_value
.return_value
= D("0.125")
302 value_from
= portfolio
.Amount("BTC", "10")
303 value_from
.rate
= D("0.1")
304 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
305 value_to
= portfolio
.Amount("BTC", "0")
306 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
308 trade
.prepare_order()
310 filled_amount
.assert_called_with(in_base_currency
=False)
311 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
312 self
.assertEqual(1, len(trade
.orders
))
313 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
314 D("0.125"), "BTC", "long", self
.m
,
315 trade
, close_if_possible
=True)
317 self
.m
.get_ticker
.return_value
= { "inverted": True, "original": {}
}
318 with self
.subTest(action
="dispose", inverted
=True):
319 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
320 compute_value
.return_value
= D("125")
322 value_from
= portfolio
.Amount("BTC", "10")
323 value_from
.rate
= D("0.01")
324 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
325 value_to
= portfolio
.Amount("BTC", "1")
326 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
328 trade
.prepare_order(compute_value
="foo")
330 filled_amount
.assert_called_with(in_base_currency
=True)
331 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "buy", compute_value
="foo")
332 self
.assertEqual(1, len(trade
.orders
))
333 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
334 D("125"), "FOO", "long", self
.m
,
335 trade
, close_if_possible
=False)
337 with self
.subTest(action
="acquire", inverted
=True):
338 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
339 compute_value
.return_value
= D("125")
341 value_from
= portfolio
.Amount("BTC", "1")
342 value_from
.rate
= D("0.01")
343 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
344 value_to
= portfolio
.Amount("BTC", "10")
345 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
347 trade
.prepare_order(compute_value
="foo")
349 filled_amount
.assert_called_with(in_base_currency
=False)
350 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "sell", compute_value
="foo")
351 self
.assertEqual(1, len(trade
.orders
))
352 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
353 D("125"), "FOO", "long", self
.m
,
354 trade
, close_if_possible
=False)
356 def test_tick_actions_recreate(self
):
357 value_from
= portfolio
.Amount("BTC", "0.5")
358 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
359 value_to
= portfolio
.Amount("BTC", "1.0")
360 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
362 self
.assertEqual("average", trade
.tick_actions_recreate(0))
363 self
.assertEqual("foo", trade
.tick_actions_recreate(0, default
="foo"))
364 self
.assertEqual("average", trade
.tick_actions_recreate(1))
365 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(2))
366 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(3))
367 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(5))
368 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(6))
369 self
.assertEqual("default", trade
.tick_actions_recreate(7))
370 self
.assertEqual("default", trade
.tick_actions_recreate(8))
372 @mock.patch.object(portfolio
.Trade
, "prepare_order")
373 def test_update_order(self
, prepare_order
):
374 order_mock
= mock
.Mock()
375 new_order_mock
= mock
.Mock()
377 value_from
= portfolio
.Amount("BTC", "0.5")
378 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
379 value_to
= portfolio
.Amount("BTC", "1.0")
380 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
381 prepare_order
.return_value
= new_order_mock
383 for i
in [0, 1, 3, 4, 6]:
384 with self
.subTest(tick
=i
):
385 trade
.update_order(order_mock
, i
)
386 order_mock
.cancel
.assert_not_called()
387 new_order_mock
.run
.assert_not_called()
388 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
,
389 update
="waiting", compute_value
=None, new_order
=None)
391 order_mock
.reset_mock()
392 new_order_mock
.reset_mock()
394 self
.m
.report
.log_order
.reset_mock()
396 trade
.update_order(order_mock
, 2)
397 order_mock
.cancel
.assert_called()
398 new_order_mock
.run
.assert_called()
399 prepare_order
.assert_called()
400 self
.m
.report
.log_order
.assert_called()
401 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
403 mock
.call(order_mock
, 2, update
="adjusting",
404 compute_value
=mock
.ANY
,
405 new_order
=new_order_mock
),
406 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
408 self
.m
.report
.log_order
.assert_has_calls(calls
)
410 order_mock
.reset_mock()
411 new_order_mock
.reset_mock()
413 self
.m
.report
.log_order
.reset_mock()
415 trade
.update_order(order_mock
, 5)
416 order_mock
.cancel
.assert_called()
417 new_order_mock
.run
.assert_called()
418 prepare_order
.assert_called()
419 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
420 self
.m
.report
.log_order
.assert_called()
422 mock
.call(order_mock
, 5, update
="adjusting",
423 compute_value
=mock
.ANY
,
424 new_order
=new_order_mock
),
425 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
427 self
.m
.report
.log_order
.assert_has_calls(calls
)
429 order_mock
.reset_mock()
430 new_order_mock
.reset_mock()
432 self
.m
.report
.log_order
.reset_mock()
434 trade
.update_order(order_mock
, 7)
435 order_mock
.cancel
.assert_called()
436 new_order_mock
.run
.assert_called()
437 prepare_order
.assert_called_with(compute_value
="default")
438 self
.m
.report
.log_order
.assert_called()
439 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
441 mock
.call(order_mock
, 7, update
="market_fallback",
442 compute_value
='default',
443 new_order
=new_order_mock
),
444 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
446 self
.m
.report
.log_order
.assert_has_calls(calls
)
448 order_mock
.reset_mock()
449 new_order_mock
.reset_mock()
451 self
.m
.report
.log_order
.reset_mock()
453 for i
in [10, 13, 16]:
454 with self
.subTest(tick
=i
):
455 trade
.update_order(order_mock
, i
)
456 order_mock
.cancel
.assert_called()
457 new_order_mock
.run
.assert_called()
458 prepare_order
.assert_called_with(compute_value
="default")
459 self
.m
.report
.log_order
.assert_called()
460 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
462 mock
.call(order_mock
, i
, update
="market_adjust",
463 compute_value
='default',
464 new_order
=new_order_mock
),
465 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
467 self
.m
.report
.log_order
.assert_has_calls(calls
)
469 order_mock
.reset_mock()
470 new_order_mock
.reset_mock()
472 self
.m
.report
.log_order
.reset_mock()
474 for i
in [8, 9, 11, 12]:
475 with self
.subTest(tick
=i
):
476 trade
.update_order(order_mock
, i
)
477 order_mock
.cancel
.assert_not_called()
478 new_order_mock
.run
.assert_not_called()
479 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
480 compute_value
=None, new_order
=None)
482 order_mock
.reset_mock()
483 new_order_mock
.reset_mock()
485 self
.m
.report
.log_order
.reset_mock()
488 def test_print_with_order(self
):
489 value_from
= portfolio
.Amount("BTC", "0.5")
490 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
491 value_to
= portfolio
.Amount("BTC", "1.0")
492 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
494 order_mock1
= mock
.Mock()
495 order_mock1
.__repr__
= mock
.Mock()
496 order_mock1
.__repr__
.return_value
= "Mock 1"
497 order_mock2
= mock
.Mock()
498 order_mock2
.__repr__
= mock
.Mock()
499 order_mock2
.__repr__
.return_value
= "Mock 2"
500 order_mock1
.mouvements
= []
501 mouvement_mock1
= mock
.Mock()
502 mouvement_mock1
.__repr__
= mock
.Mock()
503 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
504 mouvement_mock2
= mock
.Mock()
505 mouvement_mock2
.__repr__
= mock
.Mock()
506 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
507 order_mock2
.mouvements
= [
508 mouvement_mock1
, mouvement_mock2
510 trade
.orders
.append(order_mock1
)
511 trade
.orders
.append(order_mock2
)
513 with mock
.patch
.object(trade
, "filled_amount") as filled
:
514 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
516 trade
.print_with_order()
518 self
.m
.report
.print_log
.assert_called()
519 calls
= self
.m
.report
.print_log
.mock_calls
520 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
521 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
522 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
523 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
524 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
526 self
.m
.report
.print_log
.reset_mock()
528 filled
.return_value
= portfolio
.Amount("BTC", "0.5")
529 trade
.print_with_order()
530 calls
= self
.m
.report
.print_log
.mock_calls
531 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls
[0][1][0]))
533 self
.m
.report
.print_log
.reset_mock()
535 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
537 trade
.print_with_order()
538 calls
= self
.m
.report
.print_log
.mock_calls
539 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls
[0][1][0]))
541 def test_close(self
):
542 value_from
= portfolio
.Amount("BTC", "0.5")
543 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
544 value_to
= portfolio
.Amount("BTC", "1.0")
545 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
547 trade
.orders
.append(order1
)
551 self
.assertEqual(True, trade
.closed
)
552 order1
.cancel
.assert_called_once_with()
554 def test_pending(self
):
555 value_from
= portfolio
.Amount("BTC", "0.5")
556 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
557 value_to
= portfolio
.Amount("BTC", "1.0")
558 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
561 self
.assertEqual(False, trade
.pending
)
564 self
.assertEqual(True, trade
.pending
)
567 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
568 trade
.orders
.append(order1
)
569 self
.assertEqual(False, trade
.pending
)
571 def test__repr(self
):
572 value_from
= portfolio
.Amount("BTC", "0.5")
573 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
574 value_to
= portfolio
.Amount("BTC", "1.0")
575 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
577 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
579 def test_as_json(self
):
580 value_from
= portfolio
.Amount("BTC", "0.5")
581 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
582 value_to
= portfolio
.Amount("BTC", "1.0")
583 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
585 as_json
= trade
.as_json()
586 self
.assertEqual("acquire", as_json
["action"])
587 self
.assertEqual(D("0.5"), as_json
["from"])
588 self
.assertEqual(D("1.0"), as_json
["to"])
589 self
.assertEqual("ETH", as_json
["currency"])
590 self
.assertEqual("BTC", as_json
["base_currency"])
592 class BalanceTest(WebMockTestCase
):
593 def test_values(self
):
594 balance
= portfolio
.Balance("BTC", {
595 "exchange_total": "0.65",
596 "exchange_free": "0.35",
597 "exchange_used": "0.30",
598 "margin_total": "-10",
599 "margin_borrowed": "10",
600 "margin_available": "0",
601 "margin_in_position": "0",
602 "margin_position_type": "short",
603 "margin_borrowed_base_currency": "USDT",
604 "margin_liquidation_price": "1.20",
605 "margin_pending_gain": "10",
606 "margin_lending_fees": "0.4",
607 "margin_borrowed_base_price": "0.15",
609 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
610 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
611 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
612 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
613 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
614 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
616 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
617 self
.assertEqual(portfolio
.D("10"), balance
.margin_borrowed
.value
)
618 self
.assertEqual(portfolio
.D("0"), balance
.margin_available
.value
)
619 self
.assertEqual("BTC", balance
.margin_total
.currency
)
620 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
621 self
.assertEqual("BTC", balance
.margin_available
.currency
)
623 self
.assertEqual("BTC", balance
.currency
)
625 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
626 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
628 def test__repr(self
):
629 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
630 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
631 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
632 "exchange_used": 1, "exchange_free": 2 })
633 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
635 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
636 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
638 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
639 "margin_in_position": 1, "margin_available": 2 })
640 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
642 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
643 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
645 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
646 "margin_borrowed_base_price": D("0.1"),
647 "margin_borrowed_base_currency": "BTC",
648 "margin_lending_fees": D("0.002") })
649 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
651 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
652 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
653 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
655 def test_as_json(self
):
656 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
657 as_json
= balance
.as_json()
658 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
659 self
.assertEqual(D(0), as_json
["total"])
660 self
.assertEqual(D(2), as_json
["exchange_total"])
661 self
.assertEqual(D(2), as_json
["exchange_free"])
662 self
.assertEqual(D(0), as_json
["exchange_used"])
663 self
.assertEqual(D(0), as_json
["margin_total"])
664 self
.assertEqual(D(0), as_json
["margin_available"])
665 self
.assertEqual(D(0), as_json
["margin_borrowed"])
667 class OrderTest(WebMockTestCase
):
668 def test_values(self
):
669 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
670 D("0.1"), "BTC", "long", "market", "trade")
671 self
.assertEqual("buy", order
.action
)
672 self
.assertEqual(10, order
.amount
.value
)
673 self
.assertEqual("ETH", order
.amount
.currency
)
674 self
.assertEqual(D("0.1"), order
.rate
)
675 self
.assertEqual("BTC", order
.base_currency
)
676 self
.assertEqual("market", order
.market
)
677 self
.assertEqual("long", order
.trade_type
)
678 self
.assertEqual("pending", order
.status
)
679 self
.assertEqual("trade", order
.trade
)
680 self
.assertIsNone(order
.id)
681 self
.assertFalse(order
.close_if_possible
)
683 def test__repr(self
):
684 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
685 D("0.1"), "BTC", "long", "market", "trade")
686 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
688 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
689 D("0.1"), "BTC", "long", "market", "trade",
690 close_if_possible
=True)
691 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
693 def test_as_json(self
):
694 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
695 D("0.1"), "BTC", "long", "market", "trade")
696 mouvement_mock1
= mock
.Mock()
697 mouvement_mock1
.as_json
.return_value
= 1
698 mouvement_mock2
= mock
.Mock()
699 mouvement_mock2
.as_json
.return_value
= 2
701 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
702 as_json
= order
.as_json()
703 self
.assertEqual("buy", as_json
["action"])
704 self
.assertEqual("long", as_json
["trade_type"])
705 self
.assertEqual(10, as_json
["amount"])
706 self
.assertEqual("ETH", as_json
["currency"])
707 self
.assertEqual("BTC", as_json
["base_currency"])
708 self
.assertEqual(D("0.1"), as_json
["rate"])
709 self
.assertEqual("pending", as_json
["status"])
710 self
.assertEqual(False, as_json
["close_if_possible"])
711 self
.assertIsNone(as_json
["id"])
712 self
.assertEqual([1, 2], as_json
["mouvements"])
714 def test_account(self
):
715 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
716 D("0.1"), "BTC", "long", "market", "trade")
717 self
.assertEqual("exchange", order
.account
)
719 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
720 D("0.1"), "BTC", "short", "market", "trade")
721 self
.assertEqual("margin", order
.account
)
723 def test_pending(self
):
724 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
725 D("0.1"), "BTC", "long", "market", "trade")
726 self
.assertTrue(order
.pending
)
727 order
.status
= "open"
728 self
.assertFalse(order
.pending
)
731 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
732 D("0.1"), "BTC", "long", "market", "trade")
733 self
.assertFalse(order
.open)
734 order
.status
= "open"
735 self
.assertTrue(order
.open)
737 def test_finished(self
):
738 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
739 D("0.1"), "BTC", "long", "market", "trade")
740 self
.assertFalse(order
.finished
)
741 order
.status
= "closed"
742 self
.assertTrue(order
.finished
)
743 order
.status
= "canceled"
744 self
.assertTrue(order
.finished
)
745 order
.status
= "error"
746 self
.assertTrue(order
.finished
)
748 @mock.patch.object(portfolio
.Order
, "fetch")
749 def test_cancel(self
, fetch
):
750 with self
.subTest(debug
=True):
752 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
753 D("0.1"), "BTC", "long", self
.m
, "trade")
754 order
.status
= "open"
757 self
.m
.ccxt
.cancel_order
.assert_not_called()
758 self
.m
.report
.log_debug_action
.assert_called_once()
759 self
.m
.report
.log_debug_action
.reset_mock()
760 self
.assertEqual("canceled", order
.status
)
762 with self
.subTest(desc
="Nominal case"):
764 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
765 D("0.1"), "BTC", "long", self
.m
, "trade")
766 order
.status
= "open"
770 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
771 fetch
.assert_called_once_with()
772 self
.m
.report
.log_debug_action
.assert_not_called()
774 with self
.subTest(exception
=True):
775 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
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_called_with(42)
782 self
.m
.report
.log_error
.assert_called_once()
785 with self
.subTest(id=None):
786 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
787 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
788 D("0.1"), "BTC", "long", self
.m
, "trade")
789 order
.status
= "open"
791 self
.m
.ccxt
.cancel_order
.assert_not_called()
794 with self
.subTest(open=False):
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
= "closed"
800 self
.m
.ccxt
.cancel_order
.assert_not_called()
802 def test_dust_amount_remaining(self
):
803 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
804 D("0.1"), "BTC", "long", self
.m
, "trade")
805 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", 1))
806 self
.assertFalse(order
.dust_amount_remaining())
808 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", D("0.0001")))
809 self
.assertTrue(order
.dust_amount_remaining())
811 @mock.patch.object(portfolio
.Order
, "fetch")
812 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
813 def test_remaining_amount(self
, filled_amount
, fetch
):
814 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
815 D("0.1"), "BTC", "long", self
.m
, "trade")
817 self
.assertEqual(9, order
.remaining_amount().value
)
819 order
.status
= "open"
820 self
.assertEqual(9, order
.remaining_amount().value
)
822 @mock.patch.object(portfolio
.Order
, "fetch")
823 def test_filled_amount(self
, fetch
):
824 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
825 D("0.1"), "BTC", "long", self
.m
, "trade")
826 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
827 "tradeID": 42, "type": "buy", "fee": "0.0015",
828 "date": "2017-12-30 12:00:12", "rate": "0.1",
829 "amount": "3", "total": "0.3"
831 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
832 "tradeID": 43, "type": "buy", "fee": "0.0015",
833 "date": "2017-12-30 13:00:12", "rate": "0.2",
834 "amount": "2", "total": "0.4"
836 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
837 fetch
.assert_not_called()
838 order
.status
= "open"
839 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
840 fetch
.assert_called_once()
841 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
843 def test_fetch_mouvements(self
):
844 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
846 "tradeID": 42, "type": "buy", "fee": "0.0015",
847 "date": "2017-12-30 13:00:12", "rate": "0.1",
848 "amount": "3", "total": "0.3"
851 "tradeID": 43, "type": "buy", "fee": "0.0015",
852 "date": "2017-12-30 12:00:12", "rate": "0.2",
853 "amount": "2", "total": "0.4"
856 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
857 D("0.1"), "BTC", "long", self
.m
, "trade")
859 order
.mouvements
= ["Foo", "Bar", "Baz"]
861 order
.fetch_mouvements()
863 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
864 self
.assertEqual(2, len(order
.mouvements
))
865 self
.assertEqual(43, order
.mouvements
[0].id)
866 self
.assertEqual(42, order
.mouvements
[1].id)
868 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
869 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
870 D("0.1"), "BTC", "long", self
.m
, "trade")
871 order
.fetch_mouvements()
872 self
.assertEqual(0, len(order
.mouvements
))
874 def test_mark_finished_order(self
):
875 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
876 D("0.1"), "BTC", "short", self
.m
, "trade",
877 close_if_possible
=True)
878 order
.status
= "closed"
881 order
.mark_finished_order()
882 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
883 self
.m
.ccxt
.close_margin_position
.reset_mock()
885 order
.status
= "open"
886 order
.mark_finished_order()
887 self
.m
.ccxt
.close_margin_position
.assert_not_called()
889 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
890 D("0.1"), "BTC", "short", self
.m
, "trade",
891 close_if_possible
=False)
892 order
.status
= "closed"
893 order
.mark_finished_order()
894 self
.m
.ccxt
.close_margin_position
.assert_not_called()
896 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
897 D("0.1"), "BTC", "short", self
.m
, "trade",
898 close_if_possible
=True)
899 order
.status
= "closed"
900 order
.mark_finished_order()
901 self
.m
.ccxt
.close_margin_position
.assert_not_called()
903 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
904 D("0.1"), "BTC", "long", self
.m
, "trade",
905 close_if_possible
=True)
906 order
.status
= "closed"
907 order
.mark_finished_order()
908 self
.m
.ccxt
.close_margin_position
.assert_not_called()
912 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
913 D("0.1"), "BTC", "short", self
.m
, "trade",
914 close_if_possible
=True)
915 order
.status
= "closed"
917 order
.mark_finished_order()
918 self
.m
.ccxt
.close_margin_position
.assert_not_called()
919 self
.m
.report
.log_debug_action
.assert_called_once()
921 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
922 @mock.patch.object(portfolio
.Order
, "mark_disappeared_order")
923 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
924 def test_fetch(self
, mark_finished_order
, mark_disappeared_order
, fetch_mouvements
):
925 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
926 D("0.1"), "BTC", "long", self
.m
, "trade")
928 with self
.subTest(debug
=True):
931 self
.m
.report
.log_debug_action
.assert_called_once()
932 self
.m
.report
.log_debug_action
.reset_mock()
933 self
.m
.ccxt
.fetch_order
.assert_not_called()
934 mark_finished_order
.assert_not_called()
935 mark_disappeared_order
.assert_not_called()
936 fetch_mouvements
.assert_not_called()
938 with self
.subTest(debug
=False):
940 self
.m
.ccxt
.fetch_order
.return_value
= {
942 "datetime": "timestamp"
946 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
947 fetch_mouvements
.assert_called_once()
948 self
.assertEqual("foo", order
.status
)
949 self
.assertEqual("timestamp", order
.timestamp
)
950 self
.assertEqual(1, len(order
.results
))
951 self
.m
.report
.log_debug_action
.assert_not_called()
952 mark_finished_order
.assert_called_once()
953 mark_disappeared_order
.assert_called_once()
955 mark_finished_order
.reset_mock()
956 with self
.subTest(missing_order
=True):
957 self
.m
.ccxt
.fetch_order
.side_effect
= [
958 portfolio
.OrderNotCached
,
961 self
.assertEqual("closed_unknown", order
.status
)
962 mark_finished_order
.assert_called_once()
964 def test_mark_disappeared_order(self
):
965 with self
.subTest("Open order"):
966 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
967 D("0.1"), "BTC", "long", self
.m
, "trade")
969 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
971 "currencyPair":"BTC_XRP",
974 "amount":"0.00000222",
975 "total":"0.00000000",
977 "date":"2018-04-02 00:09:13"
979 order
.mark_disappeared_order()
980 self
.assertEqual("pending", order
.status
)
982 with self
.subTest("Non-zero amount"):
983 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
984 D("0.1"), "BTC", "long", self
.m
, "trade")
986 order
.status
= "closed"
987 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
989 "currencyPair":"BTC_XRP",
992 "amount":"0.00000222",
993 "total":"0.00000010",
995 "date":"2018-04-02 00:09:13"
997 order
.mark_disappeared_order()
998 self
.assertEqual("closed", order
.status
)
1000 with self
.subTest("Other mouvements"):
1001 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1002 D("0.1"), "BTC", "long", self
.m
, "trade")
1004 order
.status
= "closed"
1005 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1007 "currencyPair":"BTC_XRP",
1009 "rate":"0.00007013",
1010 "amount":"0.00000222",
1011 "total":"0.00000001",
1013 "date":"2018-04-02 00:09:13"
1015 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1017 "currencyPair":"BTC_XRP",
1019 "rate":"0.00007013",
1020 "amount":"0.00000222",
1021 "total":"0.00000000",
1023 "date":"2018-04-02 00:09:13"
1025 order
.mark_disappeared_order()
1026 self
.assertEqual("error_disappeared", order
.status
)
1028 with self
.subTest("Order disappeared"):
1029 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1030 D("0.1"), "BTC", "long", self
.m
, "trade")
1032 order
.status
= "closed"
1033 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1035 "currencyPair":"BTC_XRP",
1037 "rate":"0.00007013",
1038 "amount":"0.00000222",
1039 "total":"0.00000000",
1041 "date":"2018-04-02 00:09:13"
1043 order
.mark_disappeared_order()
1044 self
.assertEqual("error_disappeared", order
.status
)
1046 @mock.patch.object(portfolio
.Order
, "fetch")
1047 def test_get_status(self
, fetch
):
1048 with self
.subTest(debug
=True):
1050 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1051 D("0.1"), "BTC", "long", self
.m
, "trade")
1052 self
.assertEqual("pending", order
.get_status())
1053 fetch
.assert_not_called()
1054 self
.m
.report
.log_debug_action
.assert_called_once()
1056 with self
.subTest(debug
=False, finished
=False):
1057 self
.m
.debug
= False
1058 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1059 D("0.1"), "BTC", "long", self
.m
, "trade")
1061 def update_status():
1062 order
.status
= "open"
1063 return update_status
1064 fetch
.side_effect
= _fetch(order
)
1065 self
.assertEqual("open", order
.get_status())
1066 fetch
.assert_called_once()
1069 with self
.subTest(debug
=False, finished
=True):
1070 self
.m
.debug
= False
1071 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1072 D("0.1"), "BTC", "long", self
.m
, "trade")
1074 def update_status():
1075 order
.status
= "closed"
1076 return update_status
1077 fetch
.side_effect
= _fetch(order
)
1078 self
.assertEqual("closed", order
.get_status())
1079 fetch
.assert_called_once()
1082 self
.m
.ccxt
.order_precision
.return_value
= 4
1083 with self
.subTest(debug
=True):
1085 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1086 D("0.1"), "BTC", "long", self
.m
, "trade")
1088 self
.m
.ccxt
.create_order
.assert_not_called()
1089 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1090 self
.assertEqual("open", order
.status
)
1091 self
.assertEqual(1, len(order
.results
))
1092 self
.assertEqual(-1, order
.id)
1094 self
.m
.ccxt
.create_order
.reset_mock()
1095 with self
.subTest(debug
=False):
1096 self
.m
.debug
= False
1097 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1098 D("0.1"), "BTC", "long", self
.m
, "trade")
1099 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
1101 self
.m
.ccxt
.create_order
.assert_called_once()
1102 self
.assertEqual(1, len(order
.results
))
1103 self
.assertEqual("open", order
.status
)
1105 self
.m
.ccxt
.create_order
.reset_mock()
1106 with self
.subTest(exception
=True):
1107 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1108 D("0.1"), "BTC", "long", self
.m
, "trade")
1109 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
1111 self
.m
.ccxt
.create_order
.assert_called_once()
1112 self
.assertEqual(0, len(order
.results
))
1113 self
.assertEqual("error", order
.status
)
1114 self
.m
.report
.log_error
.assert_called_once()
1116 self
.m
.ccxt
.create_order
.reset_mock()
1117 with self
.subTest(dust_amount_exception
=True),\
1118 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1119 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
1120 D("0.1"), "BTC", "long", self
.m
, "trade")
1121 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
1123 self
.m
.ccxt
.create_order
.assert_called_once()
1124 self
.assertEqual(0, len(order
.results
))
1125 self
.assertEqual("closed", order
.status
)
1126 mark_finished_order
.assert_called_once()
1128 self
.m
.ccxt
.order_precision
.return_value
= 8
1129 self
.m
.ccxt
.create_order
.reset_mock()
1130 with self
.subTest(insufficient_funds
=True),\
1131 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1132 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1133 D("0.1"), "BTC", "long", self
.m
, "trade")
1134 self
.m
.ccxt
.create_order
.side_effect
= [
1135 portfolio
.InsufficientFunds
,
1136 portfolio
.InsufficientFunds
,
1137 portfolio
.InsufficientFunds
,
1141 self
.m
.ccxt
.create_order
.assert_has_calls([
1142 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1143 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1144 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1145 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1147 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
1148 self
.assertEqual(1, len(order
.results
))
1149 self
.assertEqual("open", order
.status
)
1150 self
.assertEqual(4, order
.tries
)
1151 self
.m
.report
.log_error
.assert_called()
1152 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
1154 self
.m
.ccxt
.order_precision
.return_value
= 8
1155 self
.m
.ccxt
.create_order
.reset_mock()
1156 self
.m
.report
.log_error
.reset_mock()
1157 with self
.subTest(insufficient_funds
=True),\
1158 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1159 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1160 D("0.1"), "BTC", "long", self
.m
, "trade")
1161 self
.m
.ccxt
.create_order
.side_effect
= [
1162 portfolio
.InsufficientFunds
,
1163 portfolio
.InsufficientFunds
,
1164 portfolio
.InsufficientFunds
,
1165 portfolio
.InsufficientFunds
,
1166 portfolio
.InsufficientFunds
,
1169 self
.m
.ccxt
.create_order
.assert_has_calls([
1170 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1171 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1172 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1173 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1174 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
1176 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1177 self
.assertEqual(0, len(order
.results
))
1178 self
.assertEqual("error", order
.status
)
1179 self
.assertEqual(5, order
.tries
)
1180 self
.m
.report
.log_error
.assert_called()
1181 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1182 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
)
1185 with self
.subTest(invalid_nonce
=True):
1186 with self
.subTest(retry_success
=True):
1187 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1188 D("0.1"), "BTC", "long", self
.m
, "trade")
1189 self
.m
.ccxt
.create_order
.side_effect
= [
1190 portfolio
.InvalidNonce
,
1191 portfolio
.InvalidNonce
,
1195 self
.m
.ccxt
.create_order
.assert_has_calls([
1196 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1197 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1198 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1200 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1201 self
.assertEqual(3, order
.tries
)
1202 self
.m
.report
.log_error
.assert_called()
1203 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1204 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after invalid nonce", exception
=mock
.ANY
)
1205 self
.assertEqual(123, order
.id)
1208 with self
.subTest(retry_success
=False):
1209 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1210 D("0.1"), "BTC", "long", self
.m
, "trade")
1211 self
.m
.ccxt
.create_order
.side_effect
= [
1212 portfolio
.InvalidNonce
,
1213 portfolio
.InvalidNonce
,
1214 portfolio
.InvalidNonce
,
1215 portfolio
.InvalidNonce
,
1216 portfolio
.InvalidNonce
,
1219 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1220 self
.assertEqual(5, order
.tries
)
1221 self
.m
.report
.log_error
.assert_called()
1222 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1223 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
)
1224 self
.assertEqual("error", order
.status
)
1227 with self
.subTest(request_timeout
=True):
1228 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1229 D("0.1"), "BTC", "long", self
.m
, "trade")
1230 with self
.subTest(retrieved
=False), \
1231 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1232 self
.m
.ccxt
.create_order
.side_effect
= [
1233 portfolio
.RequestTimeout
,
1234 portfolio
.RequestTimeout
,
1237 retrieve
.return_value
= False
1239 self
.m
.ccxt
.create_order
.assert_has_calls([
1240 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1241 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1242 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1244 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1245 self
.assertEqual(3, order
.tries
)
1246 self
.m
.report
.log_error
.assert_called()
1247 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1248 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after timeout", exception
=mock
.ANY
)
1249 self
.assertEqual(123, order
.id)
1252 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1253 D("0.1"), "BTC", "long", self
.m
, "trade")
1254 with self
.subTest(retrieved
=True), \
1255 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1256 self
.m
.ccxt
.create_order
.side_effect
= [
1257 portfolio
.RequestTimeout
,
1260 order
.results
.append({"id": 123}
)
1262 retrieve
.side_effect
= _retrieve
1264 self
.m
.ccxt
.create_order
.assert_has_calls([
1265 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1267 self
.assertEqual(1, self
.m
.ccxt
.create_order
.call_count
)
1268 self
.assertEqual(1, order
.tries
)
1269 self
.m
.report
.log_error
.assert_called()
1270 self
.assertEqual(1, self
.m
.report
.log_error
.call_count
)
1271 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Timeout, found the order")
1272 self
.assertEqual(123, order
.id)
1275 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1276 D("0.1"), "BTC", "long", self
.m
, "trade")
1277 with self
.subTest(retrieved
=False), \
1278 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1279 self
.m
.ccxt
.create_order
.side_effect
= [
1280 portfolio
.RequestTimeout
,
1281 portfolio
.RequestTimeout
,
1282 portfolio
.RequestTimeout
,
1283 portfolio
.RequestTimeout
,
1284 portfolio
.RequestTimeout
,
1286 retrieve
.return_value
= False
1288 self
.m
.ccxt
.create_order
.assert_has_calls([
1289 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1290 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1291 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1292 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1293 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1295 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1296 self
.assertEqual(5, order
.tries
)
1297 self
.m
.report
.log_error
.assert_called()
1298 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1299 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
)
1300 self
.assertEqual("error", order
.status
)
1302 def test_retrieve_order(self
):
1303 with self
.subTest(similar_open_order
=True):
1304 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1305 D("0.1"), "BTC", "long", self
.m
, "trade")
1306 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1308 self
.m
.ccxt
.order_precision
.return_value
= 8
1309 self
.m
.ccxt
.fetch_orders
.return_value
= [
1311 'amount': 0.002, 'cost': 0.1,
1312 'datetime': '2018-03-25T15:15:51.000Z',
1313 'fee': None, 'filled': 0.0,
1317 'date': '2018-03-25 15:15:51',
1318 'margin': 0, 'orderNumber': '1',
1319 'price': '0.1', 'rate': '0.1',
1320 'side': 'buy', 'startingAmount': '0.002',
1321 'status': 'open', 'total': '0.0002',
1324 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1325 'status': 'open', 'symbol': 'ETH/BTC',
1326 'timestamp': 1521990951000, 'trades': None,
1330 'amount': 0.001, 'cost': 0.1,
1331 'datetime': '2018-03-25T15:15:51.000Z',
1332 'fee': None, 'filled': 0.0,
1336 'date': '2018-03-25 15:15:51',
1337 'margin': 1, 'orderNumber': '2',
1338 'price': '0.1', 'rate': '0.1',
1339 'side': 'buy', 'startingAmount': '0.001',
1340 'status': 'open', 'total': '0.0001',
1343 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1344 'status': 'open', 'symbol': 'ETH/BTC',
1345 'timestamp': 1521990951000, 'trades': None,
1349 'amount': 0.001, 'cost': 0.1,
1350 'datetime': '2018-03-25T15:15:51.000Z',
1351 'fee': None, 'filled': 0.0,
1355 'date': '2018-03-25 15:15:51',
1356 'margin': 0, 'orderNumber': '3',
1357 'price': '0.1', 'rate': '0.1',
1358 'side': 'sell', 'startingAmount': '0.001',
1359 'status': 'open', 'total': '0.0001',
1362 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1363 'status': 'open', 'symbol': 'ETH/BTC',
1364 'timestamp': 1521990951000, 'trades': None,
1368 'amount': 0.001, 'cost': 0.15,
1369 'datetime': '2018-03-25T15:15:51.000Z',
1370 'fee': None, 'filled': 0.0,
1374 'date': '2018-03-25 15:15:51',
1375 'margin': 0, 'orderNumber': '4',
1376 'price': '0.15', 'rate': '0.15',
1377 'side': 'buy', 'startingAmount': '0.001',
1378 'status': 'open', 'total': '0.0001',
1381 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1382 'status': 'open', 'symbol': 'ETH/BTC',
1383 'timestamp': 1521990951000, 'trades': None,
1387 'amount': 0.001, 'cost': 0.1,
1388 'datetime': '2018-03-25T15:15:51.000Z',
1389 'fee': None, 'filled': 0.0,
1393 'date': '2018-03-25 15:15:51',
1394 'margin': 0, 'orderNumber': '1',
1395 'price': '0.1', 'rate': '0.1',
1396 'side': 'buy', 'startingAmount': '0.001',
1397 'status': 'open', 'total': '0.0001',
1400 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1401 'status': 'open', 'symbol': 'ETH/BTC',
1402 'timestamp': 1521990951000, 'trades': None,
1406 result
= order
.retrieve_order()
1407 self
.assertTrue(result
)
1408 self
.assertEqual('5', order
.results
[0]["id"])
1409 self
.m
.ccxt
.fetch_my_trades
.assert_not_called()
1410 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1413 with self
.subTest(similar_open_order
=False, past_trades
=True):
1414 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1415 D("0.1"), "BTC", "long", self
.m
, "trade")
1416 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1418 self
.m
.ccxt
.order_precision
.return_value
= 8
1419 self
.m
.ccxt
.fetch_orders
.return_value
= []
1420 self
.m
.ccxt
.fetch_my_trades
.return_value
= [
1421 { # Wrong timestamp 1
1424 'datetime': '2018-03-25T15:15:14.000Z',
1428 'category': 'exchange',
1429 'date': '2018-03-25 15:15:14',
1430 'fee': '0.00150000',
1441 'symbol': 'ETH/BTC',
1442 'timestamp': 1521983714,
1445 { # Wrong timestamp 2
1448 'datetime': '2018-03-25T15:16:54.000Z',
1452 'category': 'exchange',
1453 'date': '2018-03-25 15:16:54',
1454 'fee': '0.00150000',
1465 'symbol': 'ETH/BTC',
1466 'timestamp': 1521983814,
1472 'datetime': '2018-03-25T15:15:54.000Z',
1476 'category': 'exchange',
1477 'date': '2018-03-25 15:15:54',
1478 'fee': '0.00150000',
1489 'symbol': 'ETH/BTC',
1490 'timestamp': 1521983754,
1496 'datetime': '2018-03-25T15:16:54.000Z',
1500 'category': 'exchange',
1501 'date': '2018-03-25 15:16:54',
1502 'fee': '0.00150000',
1513 'symbol': 'ETH/BTC',
1514 'timestamp': 1521983814,
1520 'datetime': '2018-03-25T15:15:54.000Z',
1524 'category': 'marginTrade',
1525 'date': '2018-03-25 15:15:54',
1526 'fee': '0.00150000',
1537 'symbol': 'ETH/BTC',
1538 'timestamp': 1521983754,
1544 'datetime': '2018-03-25T15:16:54.000Z',
1548 'category': 'marginTrade',
1549 'date': '2018-03-25 15:16:54',
1550 'fee': '0.00150000',
1561 'symbol': 'ETH/BTC',
1562 'timestamp': 1521983814,
1568 'datetime': '2018-03-25T15:15:54.000Z',
1572 'category': 'exchange',
1573 'date': '2018-03-25 15:15:54',
1574 'fee': '0.00150000',
1585 'symbol': 'ETH/BTC',
1586 'timestamp': 1521983754,
1592 'datetime': '2018-03-25T15:16:54.000Z',
1596 'category': 'exchange',
1597 'date': '2018-03-25 15:16:54',
1598 'fee': '0.00150000',
1609 'symbol': 'ETH/BTC',
1610 'timestamp': 1521983814,
1616 'datetime': '2018-03-25T15:15:54.000Z',
1620 'category': 'exchange',
1621 'date': '2018-03-25 15:15:54',
1622 'fee': '0.00150000',
1626 'total': '0.000066',
1633 'symbol': 'ETH/BTC',
1634 'timestamp': 1521983754,
1640 'datetime': '2018-03-25T15:16:54.000Z',
1644 'category': 'exchange',
1645 'date': '2018-03-25 15:16:54',
1646 'fee': '0.00150000',
1657 'symbol': 'ETH/BTC',
1658 'timestamp': 1521983814,
1664 'datetime': '2018-03-25T15:15:54.000Z',
1668 'category': 'exchange',
1669 'date': '2018-03-25 15:15:54',
1670 'fee': '0.00150000',
1681 'symbol': 'ETH/BTC',
1682 'timestamp': 1521983754,
1688 'datetime': '2018-03-25T15:16:54.000Z',
1692 'category': 'exchange',
1693 'date': '2018-03-25 15:16:54',
1694 'fee': '0.00150000',
1698 'total': '0.000036',
1705 'symbol': 'ETH/BTC',
1706 'timestamp': 1521983814,
1711 result
= order
.retrieve_order()
1712 self
.assertTrue(result
)
1713 self
.assertEqual('7', order
.results
[0]["id"])
1714 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1717 with self
.subTest(similar_open_order
=False, past_trades
=False):
1718 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1719 D("0.1"), "BTC", "long", self
.m
, "trade")
1720 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1722 self
.m
.ccxt
.order_precision
.return_value
= 8
1723 self
.m
.ccxt
.fetch_orders
.return_value
= []
1724 self
.m
.ccxt
.fetch_my_trades
.return_value
= []
1725 result
= order
.retrieve_order()
1726 self
.assertFalse(result
)
1728 class MouvementTest(WebMockTestCase
):
1729 def test_values(self
):
1730 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1731 "tradeID": 42, "type": "buy", "fee": "0.0015",
1732 "date": "2017-12-30 12:00:12", "rate": "0.1",
1733 "amount": "10", "total": "1"
1735 self
.assertEqual("ETH", mouvement
.currency
)
1736 self
.assertEqual("BTC", mouvement
.base_currency
)
1737 self
.assertEqual(42, mouvement
.id)
1738 self
.assertEqual("buy", mouvement
.action
)
1739 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
1740 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
1741 self
.assertEqual(D("0.1"), mouvement
.rate
)
1742 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
1743 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
1745 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
1746 self
.assertIsNone(mouvement
.date
)
1747 self
.assertIsNone(mouvement
.id)
1748 self
.assertIsNone(mouvement
.action
)
1749 self
.assertEqual(-1, mouvement
.fee_rate
)
1750 self
.assertEqual(0, mouvement
.rate
)
1751 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
1752 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
1754 def test__repr(self
):
1755 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1756 "tradeID": 42, "type": "buy", "fee": "0.0015",
1757 "date": "2017-12-30 12:00:12", "rate": "0.1",
1758 "amount": "10", "total": "1"
1760 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
1762 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1763 "tradeID": 42, "type": "buy",
1764 "date": "garbage", "rate": "0.1",
1765 "amount": "10", "total": "1"
1767 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
1769 def test_as_json(self
):
1770 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1771 "tradeID": 42, "type": "buy", "fee": "0.0015",
1772 "date": "2017-12-30 12:00:12", "rate": "0.1",
1773 "amount": "10", "total": "1"
1775 as_json
= mouvement
.as_json()
1777 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
1778 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
1779 self
.assertEqual("buy", as_json
["action"])
1780 self
.assertEqual(D("10"), as_json
["total"])
1781 self
.assertEqual(D("1"), as_json
["total_in_base"])
1782 self
.assertEqual("BTC", as_json
["base_currency"])
1783 self
.assertEqual("ETH", as_json
["currency"])
1785 class AmountTest(WebMockTestCase
):
1786 def test_values(self
):
1787 amount
= portfolio
.Amount("BTC", "0.65")
1788 self
.assertEqual(D("0.65"), amount
.value
)
1789 self
.assertEqual("BTC", amount
.currency
)
1791 def test_in_currency(self
):
1792 amount
= portfolio
.Amount("ETC", 10)
1794 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
1796 with self
.subTest(desc
="no ticker for currency"):
1797 self
.m
.get_ticker
.return_value
= None
1799 self
.assertRaises(Exception, amount
.in_currency
, "ETH", self
.m
)
1801 with self
.subTest(desc
="nominal case"):
1802 self
.m
.get_ticker
.return_value
= {
1805 "average": D("0.3"),
1808 converted_amount
= amount
.in_currency("ETH", self
.m
)
1810 self
.assertEqual(D("3.0"), converted_amount
.value
)
1811 self
.assertEqual("ETH", converted_amount
.currency
)
1812 self
.assertEqual(amount
, converted_amount
.linked_to
)
1813 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
1815 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
1816 self
.assertEqual(D("2"), converted_amount
.value
)
1818 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
1819 self
.assertEqual(D("4"), converted_amount
.value
)
1821 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
1822 self
.assertEqual(D("0.2"), converted_amount
.value
)
1824 def test__round(self
):
1825 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
1826 self
.assertEqual(D("1.23456789"), round(amount
).value
)
1827 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
1829 def test__abs(self
):
1830 amount
= portfolio
.Amount("SC", -120)
1831 self
.assertEqual(120, abs(amount
).value
)
1832 self
.assertEqual("SC", abs(amount
).currency
)
1834 amount
= portfolio
.Amount("SC", 10)
1835 self
.assertEqual(10, abs(amount
).value
)
1836 self
.assertEqual("SC", abs(amount
).currency
)
1838 def test__add(self
):
1839 amount1
= portfolio
.Amount("XVG", "12.9")
1840 amount2
= portfolio
.Amount("XVG", "13.1")
1842 self
.assertEqual(26, (amount1
+ amount2
).value
)
1843 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
1845 amount3
= portfolio
.Amount("ETH", "1.6")
1846 with self
.assertRaises(Exception):
1849 amount4
= portfolio
.Amount("ETH", 0.0)
1850 self
.assertEqual(amount1
, amount1
+ amount4
)
1852 self
.assertEqual(amount1
, amount1
+ 0)
1854 def test__radd(self
):
1855 amount
= portfolio
.Amount("XVG", "12.9")
1857 self
.assertEqual(amount
, 0 + amount
)
1858 with self
.assertRaises(Exception):
1861 def test__sub(self
):
1862 amount1
= portfolio
.Amount("XVG", "13.3")
1863 amount2
= portfolio
.Amount("XVG", "13.1")
1865 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
1866 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
1868 amount3
= portfolio
.Amount("ETH", "1.6")
1869 with self
.assertRaises(Exception):
1872 amount4
= portfolio
.Amount("ETH", 0.0)
1873 self
.assertEqual(amount1
, amount1
- amount4
)
1875 def test__rsub(self
):
1876 amount
= portfolio
.Amount("ETH", "1.6")
1877 with self
.assertRaises(Exception):
1880 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
1882 def test__mul(self
):
1883 amount
= portfolio
.Amount("XEM", 11)
1885 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
1886 self
.assertEqual(D("33"), (amount
* 3).value
)
1888 with self
.assertRaises(Exception):
1891 def test__rmul(self
):
1892 amount
= portfolio
.Amount("XEM", 11)
1894 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
1895 self
.assertEqual(D("33"), (3 * amount
).value
)
1897 def test__floordiv(self
):
1898 amount
= portfolio
.Amount("XEM", 11)
1900 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1901 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1903 with self
.assertRaises(Exception):
1906 def test__truediv(self
):
1907 amount
= portfolio
.Amount("XEM", 11)
1909 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1910 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1913 amount1
= portfolio
.Amount("BTD", 11.3)
1914 amount2
= portfolio
.Amount("BTD", 13.1)
1916 self
.assertTrue(amount1
< amount2
)
1917 self
.assertFalse(amount2
< amount1
)
1918 self
.assertFalse(amount1
< amount1
)
1920 amount3
= portfolio
.Amount("BTC", 1.6)
1921 with self
.assertRaises(Exception):
1925 amount1
= portfolio
.Amount("BTD", 11.3)
1926 amount2
= portfolio
.Amount("BTD", 13.1)
1928 self
.assertTrue(amount1
<= amount2
)
1929 self
.assertFalse(amount2
<= amount1
)
1930 self
.assertTrue(amount1
<= amount1
)
1932 amount3
= portfolio
.Amount("BTC", 1.6)
1933 with self
.assertRaises(Exception):
1937 amount1
= portfolio
.Amount("BTD", 11.3)
1938 amount2
= portfolio
.Amount("BTD", 13.1)
1940 self
.assertTrue(amount2
> amount1
)
1941 self
.assertFalse(amount1
> amount2
)
1942 self
.assertFalse(amount1
> amount1
)
1944 amount3
= portfolio
.Amount("BTC", 1.6)
1945 with self
.assertRaises(Exception):
1949 amount1
= portfolio
.Amount("BTD", 11.3)
1950 amount2
= portfolio
.Amount("BTD", 13.1)
1952 self
.assertTrue(amount2
>= amount1
)
1953 self
.assertFalse(amount1
>= amount2
)
1954 self
.assertTrue(amount1
>= amount1
)
1956 amount3
= portfolio
.Amount("BTC", 1.6)
1957 with self
.assertRaises(Exception):
1961 amount1
= portfolio
.Amount("BTD", 11.3)
1962 amount2
= portfolio
.Amount("BTD", 13.1)
1963 amount3
= portfolio
.Amount("BTD", 11.3)
1965 self
.assertFalse(amount1
== amount2
)
1966 self
.assertFalse(amount2
== amount1
)
1967 self
.assertTrue(amount1
== amount3
)
1968 self
.assertFalse(amount2
== 0)
1970 amount4
= portfolio
.Amount("BTC", 1.6)
1971 with self
.assertRaises(Exception):
1974 amount5
= portfolio
.Amount("BTD", 0)
1975 self
.assertTrue(amount5
== 0)
1978 amount1
= portfolio
.Amount("BTD", 11.3)
1979 amount2
= portfolio
.Amount("BTD", 13.1)
1980 amount3
= portfolio
.Amount("BTD", 11.3)
1982 self
.assertTrue(amount1
!= amount2
)
1983 self
.assertTrue(amount2
!= amount1
)
1984 self
.assertFalse(amount1
!= amount3
)
1985 self
.assertTrue(amount2
!= 0)
1987 amount4
= portfolio
.Amount("BTC", 1.6)
1988 with self
.assertRaises(Exception):
1991 amount5
= portfolio
.Amount("BTD", 0)
1992 self
.assertFalse(amount5
!= 0)
1994 def test__neg(self
):
1995 amount1
= portfolio
.Amount("BTD", "11.3")
1997 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
1999 def test__str(self
):
2000 amount1
= portfolio
.Amount("BTX", 32)
2001 self
.assertEqual("32.00000000 BTX", str(amount1
))
2003 amount2
= portfolio
.Amount("USDT", 12000)
2004 amount1
.linked_to
= amount2
2005 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
2007 def test__repr(self
):
2008 amount1
= portfolio
.Amount("BTX", 32)
2009 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
2011 amount2
= portfolio
.Amount("USDT", 12000)
2012 amount1
.linked_to
= amount2
2013 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
2015 amount3
= portfolio
.Amount("BTC", 0.1)
2016 amount2
.linked_to
= amount3
2017 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
2019 def test_as_json(self
):
2020 amount
= portfolio
.Amount("BTX", 32)
2021 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
2023 amount
= portfolio
.Amount("BTX", "1E-10")
2024 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
2026 amount
= portfolio
.Amount("BTX", "1E-5")
2027 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
2028 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))