5 @unittest.skipUnless("unit" in limits
, "Unit skipped")
6 class ComputationTest(WebMockTestCase
):
7 def test_compute_value(self
):
9 portfolio
.Computation
.compute_value("foo", "buy", compute_value
=compute
)
10 compute
.assert_called_with("foo", "ask")
13 portfolio
.Computation
.compute_value("foo", "sell", compute_value
=compute
)
14 compute
.assert_called_with("foo", "bid")
17 portfolio
.Computation
.compute_value("foo", "ask", compute_value
=compute
)
18 compute
.assert_called_with("foo", "ask")
21 portfolio
.Computation
.compute_value("foo", "bid", compute_value
=compute
)
22 compute
.assert_called_with("foo", "bid")
25 portfolio
.Computation
.computations
["test"] = compute
26 portfolio
.Computation
.compute_value("foo", "bid", compute_value
="test")
27 compute
.assert_called_with("foo", "bid")
29 def test_eat_several(self
):
30 self
.m
.ccxt
.fetch_nth_order_book
.return_value
= D("0.00001275")
32 self
.assertEqual(D("0.00001275"), portfolio
.Computation
.eat_several(self
.m
)({ "symbol": "BTC/HUC" }
, "ask"))
35 @unittest.skipUnless("unit" in limits
, "Unit skipped")
36 class TradeTest(WebMockTestCase
):
38 def test_values_assertion(self
):
39 value_from
= portfolio
.Amount("BTC", "1.0")
40 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
41 value_to
= portfolio
.Amount("BTC", "1.0")
42 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
43 self
.assertEqual("BTC", trade
.base_currency
)
44 self
.assertEqual("ETH", trade
.currency
)
45 self
.assertEqual(self
.m
, trade
.market
)
47 with self
.assertRaises(AssertionError):
48 portfolio
.Trade(value_from
, -value_to
, "ETH", self
.m
)
49 with self
.assertRaises(AssertionError):
50 portfolio
.Trade(value_from
, value_to
, "ETC", self
.m
)
51 with self
.assertRaises(AssertionError):
52 value_from
.currency
= "ETH"
53 portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
54 value_from
.currency
= "BTC"
55 with self
.assertRaises(AssertionError):
56 value_from2
= portfolio
.Amount("BTC", "1.0")
57 portfolio
.Trade(value_from2
, value_to
, "ETH", self
.m
)
59 value_from
= portfolio
.Amount("BTC", 0)
60 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
61 self
.assertEqual(0, trade
.value_from
.linked_to
)
63 def test_action(self
):
64 value_from
= portfolio
.Amount("BTC", "1.0")
65 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
66 value_to
= portfolio
.Amount("BTC", "1.0")
67 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
69 self
.assertIsNone(trade
.action
)
71 value_from
= portfolio
.Amount("BTC", "1.0")
72 value_from
.linked_to
= portfolio
.Amount("BTC", "1.0")
73 value_to
= portfolio
.Amount("BTC", "2.0")
74 trade
= portfolio
.Trade(value_from
, value_to
, "BTC", self
.m
)
76 self
.assertIsNone(trade
.action
)
78 value_from
= portfolio
.Amount("BTC", "0.5")
79 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
80 value_to
= portfolio
.Amount("BTC", "1.0")
81 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
83 self
.assertEqual("acquire", trade
.action
)
85 value_from
= portfolio
.Amount("BTC", "0")
86 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
87 value_to
= portfolio
.Amount("BTC", "-1.0")
88 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
90 self
.assertEqual("acquire", trade
.action
)
92 def test_order_action(self
):
93 value_from
= portfolio
.Amount("BTC", "0.5")
94 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
95 value_to
= portfolio
.Amount("BTC", "1.0")
96 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
98 trade
.inverted
= False
99 self
.assertEqual("buy", trade
.order_action())
100 trade
.inverted
= True
101 self
.assertEqual("sell", trade
.order_action())
103 value_from
= portfolio
.Amount("BTC", "0")
104 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
105 value_to
= portfolio
.Amount("BTC", "-1.0")
106 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
108 trade
.inverted
= False
109 self
.assertEqual("sell", trade
.order_action())
110 trade
.inverted
= True
111 self
.assertEqual("buy", trade
.order_action())
113 def test_trade_type(self
):
114 value_from
= portfolio
.Amount("BTC", "0.5")
115 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
116 value_to
= portfolio
.Amount("BTC", "1.0")
117 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
119 self
.assertEqual("long", trade
.trade_type
)
121 value_from
= portfolio
.Amount("BTC", "0")
122 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
123 value_to
= portfolio
.Amount("BTC", "-1.0")
124 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
126 self
.assertEqual("short", trade
.trade_type
)
128 def test_is_fullfiled(self
):
129 with self
.subTest(inverted
=False):
130 value_from
= portfolio
.Amount("BTC", "0.5")
131 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
132 value_to
= portfolio
.Amount("BTC", "1.0")
133 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
136 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
139 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
140 trade
.orders
.append(order1
)
141 trade
.orders
.append(order2
)
143 self
.assertFalse(trade
.is_fullfiled
)
146 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
147 trade
.orders
.append(order3
)
149 self
.assertTrue(trade
.is_fullfiled
)
151 order1
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=True)
152 order2
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=True)
153 order3
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=True)
155 with self
.subTest(inverted
=True):
156 value_from
= portfolio
.Amount("BTC", "0.5")
157 value_from
.linked_to
= portfolio
.Amount("USDT", "1000.0")
158 value_to
= portfolio
.Amount("BTC", "1.0")
159 trade
= portfolio
.Trade(value_from
, value_to
, "USDT", self
.m
)
160 trade
.inverted
= True
163 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
166 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
167 trade
.orders
.append(order1
)
168 trade
.orders
.append(order2
)
170 self
.assertFalse(trade
.is_fullfiled
)
173 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
174 trade
.orders
.append(order3
)
176 self
.assertTrue(trade
.is_fullfiled
)
178 order1
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=True)
179 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=True)
180 order3
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=True)
183 def test_filled_amount(self
):
184 value_from
= portfolio
.Amount("BTC", "0.5")
185 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
186 value_to
= portfolio
.Amount("BTC", "1.0")
187 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
190 order1
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.3")
193 order2
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.01")
194 trade
.orders
.append(order1
)
195 trade
.orders
.append(order2
)
197 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount())
198 order1
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=False)
199 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=False)
201 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=False))
202 order1
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=False)
203 order2
.filled_amount
.assert_called_with(in_base_currency
=False, refetch
=False)
205 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=True))
206 order1
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=False)
207 order2
.filled_amount
.assert_called_with(in_base_currency
=True, refetch
=False)
209 @mock.patch.object(portfolio
.Computation
, "compute_value")
210 @mock.patch.object(portfolio
.Trade
, "filled_amount")
211 @mock.patch.object(portfolio
, "Order")
212 def test_prepare_order(self
, Order
, filled_amount
, compute_value
):
213 Order
.return_value
= "Order"
215 with self
.subTest(desc
="Nothing to do"):
216 value_from
= portfolio
.Amount("BTC", "10")
217 value_from
.rate
= D("0.1")
218 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
219 value_to
= portfolio
.Amount("BTC", "10")
220 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
222 trade
.prepare_order()
224 filled_amount
.assert_not_called()
225 compute_value
.assert_not_called()
226 self
.assertEqual(0, len(trade
.orders
))
227 Order
.assert_not_called()
229 self
.m
.get_ticker
.return_value
= None
230 with self
.subTest(desc
="Unknown ticker"):
231 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
232 compute_value
.return_value
= D("0.125")
234 value_from
= portfolio
.Amount("BTC", "1")
235 value_from
.rate
= D("0.1")
236 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
237 value_to
= portfolio
.Amount("BTC", "10")
238 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
240 trade
.prepare_order()
242 filled_amount
.assert_not_called()
243 compute_value
.assert_not_called()
244 self
.assertEqual(0, len(trade
.orders
))
245 Order
.assert_not_called()
246 self
.m
.report
.log_error
.assert_called_once_with('prepare_order',
247 message
='Unknown ticker FOO/BTC')
249 self
.m
.get_ticker
.return_value
= { "inverted": False }
250 with self
.subTest(desc
="Already filled"):
251 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
252 compute_value
.return_value
= D("0.125")
254 value_from
= portfolio
.Amount("BTC", "10")
255 value_from
.rate
= D("0.1")
256 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
257 value_to
= portfolio
.Amount("BTC", "0")
258 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
260 trade
.prepare_order()
262 filled_amount
.assert_called_with(in_base_currency
=False)
263 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
264 self
.assertEqual(0, len(trade
.orders
))
265 self
.m
.report
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
266 Order
.assert_not_called()
268 with self
.subTest(action
="dispose", inverted
=False):
269 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
270 compute_value
.return_value
= D("0.125")
272 value_from
= portfolio
.Amount("BTC", "10")
273 value_from
.rate
= D("0.1")
274 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
275 value_to
= portfolio
.Amount("BTC", "1")
276 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
278 trade
.prepare_order()
280 filled_amount
.assert_called_with(in_base_currency
=False)
281 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
282 self
.assertEqual(1, len(trade
.orders
))
283 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
284 D("0.125"), "BTC", "long", self
.m
,
285 trade
, close_if_possible
=False)
287 with self
.subTest(action
="dispose", inverted
=False, close_if_possible
=True):
288 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
289 compute_value
.return_value
= D("0.125")
291 value_from
= portfolio
.Amount("BTC", "10")
292 value_from
.rate
= D("0.1")
293 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
294 value_to
= portfolio
.Amount("BTC", "1")
295 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
297 trade
.prepare_order(close_if_possible
=True)
299 filled_amount
.assert_called_with(in_base_currency
=False)
300 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
301 self
.assertEqual(1, len(trade
.orders
))
302 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
303 D("0.125"), "BTC", "long", self
.m
,
304 trade
, close_if_possible
=True)
306 with self
.subTest(action
="acquire", inverted
=False):
307 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
308 compute_value
.return_value
= D("0.125")
310 value_from
= portfolio
.Amount("BTC", "1")
311 value_from
.rate
= D("0.1")
312 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
313 value_to
= portfolio
.Amount("BTC", "10")
314 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
316 trade
.prepare_order()
318 filled_amount
.assert_called_with(in_base_currency
=True)
319 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "buy", compute_value
="default")
320 self
.assertEqual(1, len(trade
.orders
))
322 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
323 D("0.125"), "BTC", "long", self
.m
,
324 trade
, close_if_possible
=False)
326 with self
.subTest(close_if_possible
=True):
327 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
328 compute_value
.return_value
= D("0.125")
330 value_from
= portfolio
.Amount("BTC", "10")
331 value_from
.rate
= D("0.1")
332 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
333 value_to
= portfolio
.Amount("BTC", "0")
334 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
336 trade
.prepare_order()
338 filled_amount
.assert_called_with(in_base_currency
=False)
339 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
340 self
.assertEqual(1, len(trade
.orders
))
341 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
342 D("0.125"), "BTC", "long", self
.m
,
343 trade
, close_if_possible
=True)
345 self
.m
.get_ticker
.return_value
= { "inverted": True, "original": {}
}
346 with self
.subTest(action
="dispose", inverted
=True):
347 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
348 compute_value
.return_value
= D("125")
350 value_from
= portfolio
.Amount("BTC", "10")
351 value_from
.rate
= D("0.01")
352 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
353 value_to
= portfolio
.Amount("BTC", "1")
354 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
356 trade
.prepare_order(compute_value
="foo")
358 filled_amount
.assert_called_with(in_base_currency
=True)
359 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "buy", compute_value
="foo")
360 self
.assertEqual(1, len(trade
.orders
))
361 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
362 D("125"), "FOO", "long", self
.m
,
363 trade
, close_if_possible
=False)
365 with self
.subTest(action
="acquire", inverted
=True):
366 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
367 compute_value
.return_value
= D("125")
369 value_from
= portfolio
.Amount("BTC", "1")
370 value_from
.rate
= D("0.01")
371 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
372 value_to
= portfolio
.Amount("BTC", "10")
373 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
375 trade
.prepare_order(compute_value
="foo")
377 filled_amount
.assert_called_with(in_base_currency
=False)
378 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "sell", compute_value
="foo")
379 self
.assertEqual(1, len(trade
.orders
))
380 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
381 D("125"), "FOO", "long", self
.m
,
382 trade
, close_if_possible
=False)
384 def test_tick_actions_recreate(self
):
385 value_from
= portfolio
.Amount("BTC", "0.5")
386 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
387 value_to
= portfolio
.Amount("BTC", "1.0")
388 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
390 self
.assertEqual("average", trade
.tick_actions_recreate(0))
391 self
.assertEqual("foo", trade
.tick_actions_recreate(0, default
="foo"))
392 self
.assertEqual("average", trade
.tick_actions_recreate(1))
393 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(2))
394 self
.assertEqual(trade
.tick_actions
[2][1], trade
.tick_actions_recreate(3))
395 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(5))
396 self
.assertEqual(trade
.tick_actions
[5][1], trade
.tick_actions_recreate(6))
397 self
.assertEqual("default", trade
.tick_actions_recreate(7))
398 self
.assertEqual("default", trade
.tick_actions_recreate(8))
400 @mock.patch.object(portfolio
.Trade
, "prepare_order")
401 def test_update_order(self
, prepare_order
):
402 order_mock
= mock
.Mock()
403 new_order_mock
= mock
.Mock()
405 value_from
= portfolio
.Amount("BTC", "0.5")
406 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
407 value_to
= portfolio
.Amount("BTC", "1.0")
408 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
409 prepare_order
.return_value
= new_order_mock
411 for i
in [0, 1, 3, 4, 6]:
412 with self
.subTest(tick
=i
):
413 trade
.update_order(order_mock
, i
)
414 order_mock
.cancel
.assert_not_called()
415 new_order_mock
.run
.assert_not_called()
416 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
,
417 update
="waiting", compute_value
=None, new_order
=None)
419 order_mock
.reset_mock()
420 new_order_mock
.reset_mock()
422 self
.m
.report
.log_order
.reset_mock()
424 trade
.update_order(order_mock
, 2)
425 order_mock
.cancel
.assert_called()
426 new_order_mock
.run
.assert_called()
427 prepare_order
.assert_called()
428 self
.m
.report
.log_order
.assert_called()
429 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
431 mock
.call(order_mock
, 2, update
="adjusting",
432 compute_value
=mock
.ANY
,
433 new_order
=new_order_mock
),
434 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
436 self
.m
.report
.log_order
.assert_has_calls(calls
)
438 order_mock
.reset_mock()
439 new_order_mock
.reset_mock()
441 self
.m
.report
.log_order
.reset_mock()
443 trade
.update_order(order_mock
, 5)
444 order_mock
.cancel
.assert_called()
445 new_order_mock
.run
.assert_called()
446 prepare_order
.assert_called()
447 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
448 self
.m
.report
.log_order
.assert_called()
450 mock
.call(order_mock
, 5, update
="adjusting",
451 compute_value
=mock
.ANY
,
452 new_order
=new_order_mock
),
453 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
455 self
.m
.report
.log_order
.assert_has_calls(calls
)
457 order_mock
.reset_mock()
458 new_order_mock
.reset_mock()
460 self
.m
.report
.log_order
.reset_mock()
462 trade
.update_order(order_mock
, 7)
463 order_mock
.cancel
.assert_called()
464 new_order_mock
.run
.assert_called()
465 prepare_order
.assert_called_with(compute_value
="default")
466 self
.m
.report
.log_order
.assert_called()
467 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
469 mock
.call(order_mock
, 7, update
="market_fallback",
470 compute_value
='default',
471 new_order
=new_order_mock
),
472 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
474 self
.m
.report
.log_order
.assert_has_calls(calls
)
476 order_mock
.reset_mock()
477 new_order_mock
.reset_mock()
479 self
.m
.report
.log_order
.reset_mock()
481 for i
in [10, 13, 16]:
482 with self
.subTest(tick
=i
):
483 trade
.update_order(order_mock
, i
)
484 order_mock
.cancel
.assert_called()
485 new_order_mock
.run
.assert_called()
486 prepare_order
.assert_called_with(compute_value
="default")
487 self
.m
.report
.log_order
.assert_called()
488 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
490 mock
.call(order_mock
, i
, update
="market_adjust",
491 compute_value
='default',
492 new_order
=new_order_mock
),
493 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
495 self
.m
.report
.log_order
.assert_has_calls(calls
)
497 order_mock
.reset_mock()
498 new_order_mock
.reset_mock()
500 self
.m
.report
.log_order
.reset_mock()
502 for i
in [8, 9, 11, 12]:
503 with self
.subTest(tick
=i
):
504 trade
.update_order(order_mock
, i
)
505 order_mock
.cancel
.assert_not_called()
506 new_order_mock
.run
.assert_not_called()
507 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
508 compute_value
=None, new_order
=None)
510 order_mock
.reset_mock()
511 new_order_mock
.reset_mock()
513 self
.m
.report
.log_order
.reset_mock()
515 with self
.subTest(tick
=22):
516 trade
.update_order(order_mock
, 22)
517 order_mock
.cancel
.assert_called()
518 new_order_mock
.run
.assert_called()
519 prepare_order
.assert_called_with(compute_value
=mock
.ANY
)
520 self
.m
.report
.log_order
.assert_called()
521 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
523 mock
.call(order_mock
, 22, update
="market_adjust_eat",
524 compute_value
=mock
.ANY
,
525 new_order
=new_order_mock
),
526 mock
.call(order_mock
, 22, new_order
=new_order_mock
),
528 self
.m
.report
.log_order
.assert_has_calls(calls
)
530 order_mock
.reset_mock()
531 new_order_mock
.reset_mock()
533 self
.m
.report
.log_order
.reset_mock()
535 def test_print_with_order(self
):
536 value_from
= portfolio
.Amount("BTC", "0.5")
537 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
538 value_to
= portfolio
.Amount("BTC", "1.0")
539 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
541 order_mock1
= mock
.Mock()
542 order_mock1
.__repr__
= mock
.Mock()
543 order_mock1
.__repr__
.return_value
= "Mock 1"
544 order_mock2
= mock
.Mock()
545 order_mock2
.__repr__
= mock
.Mock()
546 order_mock2
.__repr__
.return_value
= "Mock 2"
547 order_mock1
.mouvements
= []
548 mouvement_mock1
= mock
.Mock()
549 mouvement_mock1
.__repr__
= mock
.Mock()
550 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
551 mouvement_mock2
= mock
.Mock()
552 mouvement_mock2
.__repr__
= mock
.Mock()
553 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
554 order_mock2
.mouvements
= [
555 mouvement_mock1
, mouvement_mock2
557 trade
.orders
.append(order_mock1
)
558 trade
.orders
.append(order_mock2
)
560 with mock
.patch
.object(trade
, "filled_amount") as filled
:
561 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
563 trade
.print_with_order()
565 self
.m
.report
.print_log
.assert_called()
566 calls
= self
.m
.report
.print_log
.mock_calls
567 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
568 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
569 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
570 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
571 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
573 self
.m
.report
.print_log
.reset_mock()
575 filled
.return_value
= portfolio
.Amount("BTC", "0.5")
576 trade
.print_with_order()
577 calls
= self
.m
.report
.print_log
.mock_calls
578 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls
[0][1][0]))
580 self
.m
.report
.print_log
.reset_mock()
582 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
584 trade
.print_with_order()
585 calls
= self
.m
.report
.print_log
.mock_calls
586 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls
[0][1][0]))
588 def test_close(self
):
589 value_from
= portfolio
.Amount("BTC", "0.5")
590 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
591 value_to
= portfolio
.Amount("BTC", "1.0")
592 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
594 trade
.orders
.append(order1
)
598 self
.assertEqual(True, trade
.closed
)
599 order1
.cancel
.assert_called_once_with()
601 def test_pending(self
):
602 value_from
= portfolio
.Amount("BTC", "0.5")
603 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
604 value_to
= portfolio
.Amount("BTC", "1.0")
605 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
608 self
.assertEqual(False, trade
.pending
)
611 self
.assertEqual(True, trade
.pending
)
614 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
615 trade
.orders
.append(order1
)
616 self
.assertEqual(False, trade
.pending
)
618 def test__repr(self
):
619 value_from
= portfolio
.Amount("BTC", "0.5")
620 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
621 value_to
= portfolio
.Amount("BTC", "1.0")
622 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
624 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
626 def test_as_json(self
):
627 value_from
= portfolio
.Amount("BTC", "0.5")
628 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
629 value_to
= portfolio
.Amount("BTC", "1.0")
630 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
632 as_json
= trade
.as_json()
633 self
.assertEqual("acquire", as_json
["action"])
634 self
.assertEqual(D("0.5"), as_json
["from"])
635 self
.assertEqual(D("1.0"), as_json
["to"])
636 self
.assertEqual("ETH", as_json
["currency"])
637 self
.assertEqual("BTC", as_json
["base_currency"])
639 @unittest.skipUnless("unit" in limits
, "Unit skipped")
640 class BalanceTest(WebMockTestCase
):
641 def test_values(self
):
642 balance
= portfolio
.Balance("BTC", {
643 "exchange_total": "0.65",
644 "exchange_free": "0.35",
645 "exchange_used": "0.30",
646 "margin_total": "-10",
647 "margin_borrowed": "10",
648 "margin_available": "0",
649 "margin_in_position": "0",
650 "margin_position_type": "short",
651 "margin_borrowed_base_currency": "USDT",
652 "margin_liquidation_price": "1.20",
653 "margin_pending_gain": "10",
654 "margin_lending_fees": "0.4",
655 "margin_borrowed_base_price": "0.15",
657 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
658 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
659 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
660 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
661 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
662 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
664 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
665 self
.assertEqual(portfolio
.D("10"), balance
.margin_borrowed
.value
)
666 self
.assertEqual(portfolio
.D("0"), balance
.margin_available
.value
)
667 self
.assertEqual("BTC", balance
.margin_total
.currency
)
668 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
669 self
.assertEqual("BTC", balance
.margin_available
.currency
)
671 self
.assertEqual("BTC", balance
.currency
)
673 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
674 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
676 def test__repr(self
):
677 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
678 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
679 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
680 "exchange_used": 1, "exchange_free": 2 })
681 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
683 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
684 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
686 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
687 "margin_in_position": 1, "margin_available": 2 })
688 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
690 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
691 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
693 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
694 "margin_borrowed_base_price": D("0.1"),
695 "margin_borrowed_base_currency": "BTC",
696 "margin_lending_fees": D("0.002") })
697 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
699 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
700 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
701 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
703 def test_as_json(self
):
704 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
705 as_json
= balance
.as_json()
706 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
707 self
.assertEqual(D(0), as_json
["total"])
708 self
.assertEqual(D(2), as_json
["exchange_total"])
709 self
.assertEqual(D(2), as_json
["exchange_free"])
710 self
.assertEqual(D(0), as_json
["exchange_used"])
711 self
.assertEqual(D(0), as_json
["margin_total"])
712 self
.assertEqual(D(0), as_json
["margin_available"])
713 self
.assertEqual(D(0), as_json
["margin_borrowed"])
715 @unittest.skipUnless("unit" in limits
, "Unit skipped")
716 class OrderTest(WebMockTestCase
):
717 def test_values(self
):
718 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
719 D("0.1"), "BTC", "long", "market", "trade")
720 self
.assertEqual("buy", order
.action
)
721 self
.assertEqual(10, order
.amount
.value
)
722 self
.assertEqual("ETH", order
.amount
.currency
)
723 self
.assertEqual(D("0.1"), order
.rate
)
724 self
.assertEqual("BTC", order
.base_currency
)
725 self
.assertEqual("market", order
.market
)
726 self
.assertEqual("long", order
.trade_type
)
727 self
.assertEqual("pending", order
.status
)
728 self
.assertEqual("trade", order
.trade
)
729 self
.assertIsNone(order
.id)
730 self
.assertFalse(order
.close_if_possible
)
732 def test__repr(self
):
733 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
734 D("0.1"), "BTC", "long", "market", "trade")
735 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
737 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
738 D("0.1"), "BTC", "long", "market", "trade",
739 close_if_possible
=True)
740 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
742 def test_as_json(self
):
743 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
744 D("0.1"), "BTC", "long", "market", "trade")
745 mouvement_mock1
= mock
.Mock()
746 mouvement_mock1
.as_json
.return_value
= 1
747 mouvement_mock2
= mock
.Mock()
748 mouvement_mock2
.as_json
.return_value
= 2
750 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
751 as_json
= order
.as_json()
752 self
.assertEqual("buy", as_json
["action"])
753 self
.assertEqual("long", as_json
["trade_type"])
754 self
.assertEqual(10, as_json
["amount"])
755 self
.assertEqual("ETH", as_json
["currency"])
756 self
.assertEqual("BTC", as_json
["base_currency"])
757 self
.assertEqual(D("0.1"), as_json
["rate"])
758 self
.assertEqual("pending", as_json
["status"])
759 self
.assertEqual(False, as_json
["close_if_possible"])
760 self
.assertIsNone(as_json
["id"])
761 self
.assertEqual([1, 2], as_json
["mouvements"])
763 def test_account(self
):
764 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
765 D("0.1"), "BTC", "long", "market", "trade")
766 self
.assertEqual("exchange", order
.account
)
768 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
769 D("0.1"), "BTC", "short", "market", "trade")
770 self
.assertEqual("margin", order
.account
)
772 def test_pending(self
):
773 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
774 D("0.1"), "BTC", "long", "market", "trade")
775 self
.assertTrue(order
.pending
)
776 order
.status
= "open"
777 self
.assertFalse(order
.pending
)
780 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
781 D("0.1"), "BTC", "long", "market", "trade")
782 self
.assertFalse(order
.open)
783 order
.status
= "open"
784 self
.assertTrue(order
.open)
786 def test_finished(self
):
787 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
788 D("0.1"), "BTC", "long", "market", "trade")
789 self
.assertFalse(order
.finished
)
790 order
.status
= "closed"
791 self
.assertTrue(order
.finished
)
792 order
.status
= "canceled"
793 self
.assertTrue(order
.finished
)
794 order
.status
= "error"
795 self
.assertTrue(order
.finished
)
797 @mock.patch.object(portfolio
.Order
, "fetch")
798 def test_cancel(self
, fetch
):
799 with self
.subTest(debug
=True):
801 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
802 D("0.1"), "BTC", "long", self
.m
, "trade")
803 order
.status
= "open"
806 self
.m
.ccxt
.cancel_order
.assert_not_called()
807 self
.m
.report
.log_debug_action
.assert_called_once()
808 self
.m
.report
.log_debug_action
.reset_mock()
809 self
.assertEqual("canceled", order
.status
)
811 with self
.subTest(desc
="Nominal case"):
813 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
814 D("0.1"), "BTC", "long", self
.m
, "trade")
815 order
.status
= "open"
819 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
820 fetch
.assert_called_once_with()
821 self
.m
.report
.log_debug_action
.assert_not_called()
823 with self
.subTest(exception
=True):
824 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
825 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
826 D("0.1"), "BTC", "long", self
.m
, "trade")
827 order
.status
= "open"
830 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
831 self
.m
.report
.log_error
.assert_called_once()
834 with self
.subTest(id=None):
835 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
836 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
837 D("0.1"), "BTC", "long", self
.m
, "trade")
838 order
.status
= "open"
840 self
.m
.ccxt
.cancel_order
.assert_not_called()
843 with self
.subTest(open=False):
844 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
845 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
846 D("0.1"), "BTC", "long", self
.m
, "trade")
847 order
.status
= "closed"
849 self
.m
.ccxt
.cancel_order
.assert_not_called()
851 def test_mark_dust_amount_remaining(self
):
852 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
853 D("0.1"), "BTC", "long", self
.m
, "trade")
854 self
.m
.ccxt
.is_dust_trade
.return_value
= False
855 order
.mark_dust_amount_remaining_order()
856 self
.assertEqual("pending", order
.status
)
858 self
.m
.ccxt
.is_dust_trade
.return_value
= True
859 order
.mark_dust_amount_remaining_order()
860 self
.assertEqual("pending", order
.status
)
862 order
.status
= "open"
863 order
.mark_dust_amount_remaining_order()
864 self
.assertEqual("closed_dust_remaining", order
.status
)
866 @mock.patch.object(portfolio
.Order
, "fetch")
867 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
868 def test_remaining_amount(self
, filled_amount
, fetch
):
869 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
870 D("0.1"), "BTC", "long", self
.m
, "trade")
872 self
.assertEqual(9, order
.remaining_amount().value
)
874 order
.status
= "open"
875 self
.assertEqual(9, order
.remaining_amount().value
)
877 @mock.patch.object(portfolio
.Order
, "fetch")
878 def test_filled_amount(self
, fetch
):
879 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
880 D("0.1"), "BTC", "long", self
.m
, "trade")
881 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
882 "tradeID": 42, "type": "buy", "fee": "0.0015",
883 "date": "2017-12-30 12:00:12", "rate": "0.1",
884 "amount": "3", "total": "0.3"
886 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
887 "tradeID": 43, "type": "buy", "fee": "0.0015",
888 "date": "2017-12-30 13:00:12", "rate": "0.2",
889 "amount": "2", "total": "0.4"
891 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
892 fetch
.assert_not_called()
893 order
.status
= "open"
894 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
895 fetch
.assert_not_called()
896 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False, refetch
=True))
897 fetch
.assert_called_once()
898 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
900 def test_fetch_mouvements(self
):
901 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
903 "tradeID": 42, "type": "buy", "fee": "0.0015",
904 "date": "2017-12-30 13:00:12", "rate": "0.1",
905 "amount": "3", "total": "0.3"
908 "tradeID": 43, "type": "buy", "fee": "0.0015",
909 "date": "2017-12-30 12:00:12", "rate": "0.2",
910 "amount": "2", "total": "0.4"
913 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
914 D("0.1"), "BTC", "long", self
.m
, "trade")
916 order
.mouvements
= ["Foo", "Bar", "Baz"]
918 order
.fetch_mouvements()
920 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
921 self
.assertEqual(2, len(order
.mouvements
))
922 self
.assertEqual(43, order
.mouvements
[0].id)
923 self
.assertEqual(42, order
.mouvements
[1].id)
925 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
926 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
927 D("0.1"), "BTC", "long", self
.m
, "trade")
928 order
.fetch_mouvements()
929 self
.assertEqual(0, len(order
.mouvements
))
931 def test_mark_finished_order(self
):
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"
938 order
.mark_finished_order()
939 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
940 self
.m
.ccxt
.close_margin_position
.reset_mock()
942 order
.status
= "open"
943 order
.mark_finished_order()
944 self
.m
.ccxt
.close_margin_position
.assert_not_called()
946 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
947 D("0.1"), "BTC", "short", self
.m
, "trade",
948 close_if_possible
=False)
949 order
.status
= "closed"
950 order
.mark_finished_order()
951 self
.m
.ccxt
.close_margin_position
.assert_not_called()
953 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
954 D("0.1"), "BTC", "short", self
.m
, "trade",
955 close_if_possible
=True)
956 order
.status
= "closed"
957 order
.mark_finished_order()
958 self
.m
.ccxt
.close_margin_position
.assert_not_called()
960 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
961 D("0.1"), "BTC", "long", self
.m
, "trade",
962 close_if_possible
=True)
963 order
.status
= "closed"
964 order
.mark_finished_order()
965 self
.m
.ccxt
.close_margin_position
.assert_not_called()
969 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
970 D("0.1"), "BTC", "short", self
.m
, "trade",
971 close_if_possible
=True)
972 order
.status
= "closed"
974 order
.mark_finished_order()
975 self
.m
.ccxt
.close_margin_position
.assert_not_called()
976 self
.m
.report
.log_debug_action
.assert_called_once()
978 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
979 @mock.patch.object(portfolio
.Order
, "mark_disappeared_order")
980 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
981 def test_fetch(self
, mark_finished_order
, mark_disappeared_order
, fetch_mouvements
):
982 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
983 D("0.1"), "BTC", "long", self
.m
, "trade")
985 with self
.subTest(debug
=True):
988 self
.m
.report
.log_debug_action
.assert_called_once()
989 self
.m
.report
.log_debug_action
.reset_mock()
990 self
.m
.ccxt
.fetch_order
.assert_not_called()
991 mark_finished_order
.assert_not_called()
992 mark_disappeared_order
.assert_not_called()
993 fetch_mouvements
.assert_not_called()
995 with self
.subTest(debug
=False):
997 self
.m
.ccxt
.fetch_order
.return_value
= {
999 "datetime": "timestamp"
1001 self
.m
.ccxt
.is_dust_trade
.return_value
= False
1004 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
1005 fetch_mouvements
.assert_called_once()
1006 self
.assertEqual("foo", order
.status
)
1007 self
.assertEqual("timestamp", order
.timestamp
)
1008 self
.assertEqual(1, len(order
.results
))
1009 self
.m
.report
.log_debug_action
.assert_not_called()
1010 mark_finished_order
.assert_called_once()
1011 mark_disappeared_order
.assert_called_once()
1013 mark_finished_order
.reset_mock()
1014 with self
.subTest(missing_order
=True):
1015 self
.m
.ccxt
.fetch_order
.side_effect
= [
1016 portfolio
.OrderNotCached
,
1019 self
.assertEqual("closed_unknown", order
.status
)
1020 mark_finished_order
.assert_called_once()
1022 def test_mark_disappeared_order(self
):
1023 with self
.subTest("Open order"):
1024 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1025 D("0.1"), "BTC", "long", self
.m
, "trade")
1027 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1029 "currencyPair":"BTC_XRP",
1031 "rate":"0.00007013",
1032 "amount":"0.00000222",
1033 "total":"0.00000000",
1035 "date":"2018-04-02 00:09:13"
1037 order
.mark_disappeared_order()
1038 self
.assertEqual("pending", order
.status
)
1040 with self
.subTest("Non-zero amount"):
1041 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1042 D("0.1"), "BTC", "long", self
.m
, "trade")
1044 order
.status
= "closed"
1045 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1047 "currencyPair":"BTC_XRP",
1049 "rate":"0.00007013",
1050 "amount":"0.00000222",
1051 "total":"0.00000010",
1053 "date":"2018-04-02 00:09:13"
1055 order
.mark_disappeared_order()
1056 self
.assertEqual("closed", order
.status
)
1058 with self
.subTest("Other mouvements"):
1059 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1060 D("0.1"), "BTC", "long", self
.m
, "trade")
1062 order
.status
= "closed"
1063 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1065 "currencyPair":"BTC_XRP",
1067 "rate":"0.00007013",
1068 "amount":"0.00000222",
1069 "total":"0.00000001",
1071 "date":"2018-04-02 00:09:13"
1073 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1075 "currencyPair":"BTC_XRP",
1077 "rate":"0.00007013",
1078 "amount":"0.00000222",
1079 "total":"0.00000000",
1081 "date":"2018-04-02 00:09:13"
1083 order
.mark_disappeared_order()
1084 self
.assertEqual("error_disappeared", order
.status
)
1086 with self
.subTest("Order disappeared"):
1087 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1088 D("0.1"), "BTC", "long", self
.m
, "trade")
1090 order
.status
= "closed"
1091 order
.mouvements
.append(portfolio
.Mouvement("XRP", "BTC", {
1093 "currencyPair":"BTC_XRP",
1095 "rate":"0.00007013",
1096 "amount":"0.00000222",
1097 "total":"0.00000000",
1099 "date":"2018-04-02 00:09:13"
1101 order
.mark_disappeared_order()
1102 self
.assertEqual("error_disappeared", order
.status
)
1104 @mock.patch.object(portfolio
.Order
, "fetch")
1105 def test_get_status(self
, fetch
):
1106 with self
.subTest(debug
=True):
1108 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1109 D("0.1"), "BTC", "long", self
.m
, "trade")
1110 self
.assertEqual("pending", order
.get_status())
1111 fetch
.assert_not_called()
1112 self
.m
.report
.log_debug_action
.assert_called_once()
1114 with self
.subTest(debug
=False, finished
=False):
1115 self
.m
.debug
= False
1116 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1117 D("0.1"), "BTC", "long", self
.m
, "trade")
1119 def update_status():
1120 order
.status
= "open"
1121 return update_status
1122 fetch
.side_effect
= _fetch(order
)
1123 self
.assertEqual("open", order
.get_status())
1124 fetch
.assert_called_once()
1127 with self
.subTest(debug
=False, finished
=True):
1128 self
.m
.debug
= False
1129 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1130 D("0.1"), "BTC", "long", self
.m
, "trade")
1132 def update_status():
1133 order
.status
= "closed"
1134 return update_status
1135 fetch
.side_effect
= _fetch(order
)
1136 self
.assertEqual("closed", order
.get_status())
1137 fetch
.assert_called_once()
1140 self
.m
.ccxt
.order_precision
.return_value
= 4
1141 with self
.subTest(debug
=True):
1143 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1144 D("0.1"), "BTC", "long", self
.m
, "trade")
1146 self
.m
.ccxt
.create_order
.assert_not_called()
1147 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1148 self
.assertEqual("open", order
.status
)
1149 self
.assertEqual(1, len(order
.results
))
1150 self
.assertEqual(-1, order
.id)
1152 self
.m
.ccxt
.create_order
.reset_mock()
1153 with self
.subTest(debug
=False):
1154 self
.m
.debug
= False
1155 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1156 D("0.1"), "BTC", "long", self
.m
, "trade")
1157 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
1159 self
.m
.ccxt
.create_order
.assert_called_once()
1160 self
.assertEqual(1, len(order
.results
))
1161 self
.assertEqual("open", order
.status
)
1163 self
.m
.ccxt
.create_order
.reset_mock()
1164 with self
.subTest(exception
=True):
1165 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1166 D("0.1"), "BTC", "long", self
.m
, "trade")
1167 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
1169 self
.m
.ccxt
.create_order
.assert_called_once()
1170 self
.assertEqual(0, len(order
.results
))
1171 self
.assertEqual("error", order
.status
)
1172 self
.m
.report
.log_error
.assert_called_once()
1174 self
.m
.ccxt
.create_order
.reset_mock()
1175 with self
.subTest(dust_amount_exception
=True),\
1176 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1177 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
1178 D("0.1"), "BTC", "long", self
.m
, "trade")
1179 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
1181 self
.m
.ccxt
.create_order
.assert_called_once()
1182 self
.assertEqual(0, len(order
.results
))
1183 self
.assertEqual("closed", order
.status
)
1184 mark_finished_order
.assert_called_once()
1186 self
.m
.ccxt
.order_precision
.return_value
= 8
1187 self
.m
.ccxt
.create_order
.reset_mock()
1188 with self
.subTest(insufficient_funds
=True),\
1189 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1190 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1191 D("0.1"), "BTC", "long", self
.m
, "trade")
1192 self
.m
.ccxt
.create_order
.side_effect
= [
1193 portfolio
.InsufficientFunds
,
1194 portfolio
.InsufficientFunds
,
1195 portfolio
.InsufficientFunds
,
1199 self
.m
.ccxt
.create_order
.assert_has_calls([
1200 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1201 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1202 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1203 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1205 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
1206 self
.assertEqual(1, len(order
.results
))
1207 self
.assertEqual("open", order
.status
)
1208 self
.assertEqual(4, order
.tries
)
1209 self
.m
.report
.log_error
.assert_called()
1210 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
1212 self
.m
.ccxt
.order_precision
.return_value
= 8
1213 self
.m
.ccxt
.create_order
.reset_mock()
1214 self
.m
.report
.log_error
.reset_mock()
1215 with self
.subTest(insufficient_funds
=True),\
1216 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
1217 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1218 D("0.1"), "BTC", "long", self
.m
, "trade")
1219 self
.m
.ccxt
.create_order
.side_effect
= [
1220 portfolio
.InsufficientFunds
,
1221 portfolio
.InsufficientFunds
,
1222 portfolio
.InsufficientFunds
,
1223 portfolio
.InsufficientFunds
,
1224 portfolio
.InsufficientFunds
,
1227 self
.m
.ccxt
.create_order
.assert_has_calls([
1228 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1229 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
1230 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
1231 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
1232 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
1234 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1235 self
.assertEqual(0, len(order
.results
))
1236 self
.assertEqual("error", order
.status
)
1237 self
.assertEqual(5, order
.tries
)
1238 self
.m
.report
.log_error
.assert_called()
1239 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1240 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
)
1243 with self
.subTest(invalid_nonce
=True):
1244 with self
.subTest(retry_success
=True):
1245 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1246 D("0.1"), "BTC", "long", self
.m
, "trade")
1247 self
.m
.ccxt
.create_order
.side_effect
= [
1248 portfolio
.InvalidNonce
,
1249 portfolio
.InvalidNonce
,
1253 self
.m
.ccxt
.create_order
.assert_has_calls([
1254 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1255 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1256 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1258 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1259 self
.assertEqual(3, order
.tries
)
1260 self
.m
.report
.log_error
.assert_called()
1261 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1262 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after invalid nonce", exception
=mock
.ANY
)
1263 self
.assertEqual(123, order
.id)
1266 with self
.subTest(retry_success
=False):
1267 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1268 D("0.1"), "BTC", "long", self
.m
, "trade")
1269 self
.m
.ccxt
.create_order
.side_effect
= [
1270 portfolio
.InvalidNonce
,
1271 portfolio
.InvalidNonce
,
1272 portfolio
.InvalidNonce
,
1273 portfolio
.InvalidNonce
,
1274 portfolio
.InvalidNonce
,
1277 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1278 self
.assertEqual(5, order
.tries
)
1279 self
.m
.report
.log_error
.assert_called()
1280 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1281 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
)
1282 self
.assertEqual("error", order
.status
)
1285 with self
.subTest(request_timeout
=True):
1286 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1287 D("0.1"), "BTC", "long", self
.m
, "trade")
1288 with self
.subTest(retrieved
=False), \
1289 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1290 self
.m
.ccxt
.create_order
.side_effect
= [
1291 portfolio
.RequestTimeout
,
1292 portfolio
.RequestTimeout
,
1295 retrieve
.return_value
= False
1297 self
.m
.ccxt
.create_order
.assert_has_calls([
1298 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1299 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1300 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1302 self
.assertEqual(3, self
.m
.ccxt
.create_order
.call_count
)
1303 self
.assertEqual(3, order
.tries
)
1304 self
.m
.report
.log_error
.assert_called()
1305 self
.assertEqual(2, self
.m
.report
.log_error
.call_count
)
1306 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Retrying after timeout", exception
=mock
.ANY
)
1307 self
.assertEqual(123, order
.id)
1310 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1311 D("0.1"), "BTC", "long", self
.m
, "trade")
1312 with self
.subTest(retrieved
=True), \
1313 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1314 self
.m
.ccxt
.create_order
.side_effect
= [
1315 portfolio
.RequestTimeout
,
1318 order
.results
.append({"id": 123}
)
1320 retrieve
.side_effect
= _retrieve
1322 self
.m
.ccxt
.create_order
.assert_has_calls([
1323 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1325 self
.assertEqual(1, self
.m
.ccxt
.create_order
.call_count
)
1326 self
.assertEqual(1, order
.tries
)
1327 self
.m
.report
.log_error
.assert_called()
1328 self
.assertEqual(1, self
.m
.report
.log_error
.call_count
)
1329 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Timeout, found the order")
1330 self
.assertEqual(123, order
.id)
1333 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1334 D("0.1"), "BTC", "long", self
.m
, "trade")
1335 with self
.subTest(retrieved
=False), \
1336 mock
.patch
.object(order
, "retrieve_order") as retrieve
:
1337 self
.m
.ccxt
.create_order
.side_effect
= [
1338 portfolio
.RequestTimeout
,
1339 portfolio
.RequestTimeout
,
1340 portfolio
.RequestTimeout
,
1341 portfolio
.RequestTimeout
,
1342 portfolio
.RequestTimeout
,
1344 retrieve
.return_value
= False
1346 self
.m
.ccxt
.create_order
.assert_has_calls([
1347 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1348 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1349 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1350 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1351 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
1353 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
1354 self
.assertEqual(5, order
.tries
)
1355 self
.m
.report
.log_error
.assert_called()
1356 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
1357 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
)
1358 self
.assertEqual("error", order
.status
)
1360 def test_retrieve_order(self
):
1361 with self
.subTest(similar_open_order
=True):
1362 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1363 D("0.1"), "BTC", "long", self
.m
, "trade")
1364 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1366 self
.m
.ccxt
.order_precision
.return_value
= 8
1367 self
.m
.ccxt
.fetch_orders
.return_value
= [
1369 'amount': 0.002, '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': '1',
1377 'price': '0.1', 'rate': '0.1',
1378 'side': 'buy', 'startingAmount': '0.002',
1379 'status': 'open', 'total': '0.0002',
1382 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
1383 'status': 'open', 'symbol': 'ETH/BTC',
1384 'timestamp': 1521990951000, 'trades': None,
1388 'amount': 0.001, 'cost': 0.1,
1389 'datetime': '2018-03-25T15:15:51.000Z',
1390 'fee': None, 'filled': 0.0,
1394 'date': '2018-03-25 15:15:51',
1395 'margin': 1, 'orderNumber': '2',
1396 'price': '0.1', 'rate': '0.1',
1397 'side': 'buy', 'startingAmount': '0.001',
1398 'status': 'open', 'total': '0.0001',
1401 'price': 0.1, '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': '3',
1415 'price': '0.1', 'rate': '0.1',
1416 'side': 'sell', 'startingAmount': '0.001',
1417 'status': 'open', 'total': '0.0001',
1420 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
1421 'status': 'open', 'symbol': 'ETH/BTC',
1422 'timestamp': 1521990951000, 'trades': None,
1426 'amount': 0.001, 'cost': 0.15,
1427 'datetime': '2018-03-25T15:15:51.000Z',
1428 'fee': None, 'filled': 0.0,
1432 'date': '2018-03-25 15:15:51',
1433 'margin': 0, 'orderNumber': '4',
1434 'price': '0.15', 'rate': '0.15',
1435 'side': 'buy', 'startingAmount': '0.001',
1436 'status': 'open', 'total': '0.0001',
1439 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
1440 'status': 'open', 'symbol': 'ETH/BTC',
1441 'timestamp': 1521990951000, 'trades': None,
1445 'amount': 0.001, 'cost': 0.1,
1446 'datetime': '2018-03-25T15:15:51.000Z',
1447 'fee': None, 'filled': 0.0,
1451 'date': '2018-03-25 15:15:51',
1452 'margin': 0, 'orderNumber': '1',
1453 'price': '0.1', 'rate': '0.1',
1454 'side': 'buy', 'startingAmount': '0.001',
1455 'status': 'open', 'total': '0.0001',
1458 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
1459 'status': 'open', 'symbol': 'ETH/BTC',
1460 'timestamp': 1521990951000, 'trades': None,
1464 result
= order
.retrieve_order()
1465 self
.assertTrue(result
)
1466 self
.assertEqual('5', order
.results
[0]["id"])
1467 self
.m
.ccxt
.fetch_my_trades
.assert_not_called()
1468 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1471 with self
.subTest(similar_open_order
=False, past_trades
=True):
1472 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1473 D("0.1"), "BTC", "long", self
.m
, "trade")
1474 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1476 self
.m
.ccxt
.order_precision
.return_value
= 8
1477 self
.m
.ccxt
.fetch_orders
.return_value
= []
1478 self
.m
.ccxt
.fetch_my_trades
.return_value
= [
1479 { # Wrong timestamp 1
1482 'datetime': '2018-03-25T15:15:14.000Z',
1486 'category': 'exchange',
1487 'date': '2018-03-25 15:15:14',
1488 'fee': '0.00150000',
1499 'symbol': 'ETH/BTC',
1500 'timestamp': 1521983714,
1503 { # Wrong timestamp 2
1506 'datetime': '2018-03-25T15:16:54.000Z',
1510 'category': 'exchange',
1511 'date': '2018-03-25 15:16:54',
1512 'fee': '0.00150000',
1523 'symbol': 'ETH/BTC',
1524 'timestamp': 1521983814,
1530 'datetime': '2018-03-25T15:15:54.000Z',
1534 'category': 'exchange',
1535 'date': '2018-03-25 15:15:54',
1536 'fee': '0.00150000',
1547 'symbol': 'ETH/BTC',
1548 'timestamp': 1521983754,
1554 'datetime': '2018-03-25T15:16:54.000Z',
1558 'category': 'exchange',
1559 'date': '2018-03-25 15:16:54',
1560 'fee': '0.00150000',
1571 'symbol': 'ETH/BTC',
1572 'timestamp': 1521983814,
1578 'datetime': '2018-03-25T15:15:54.000Z',
1582 'category': 'marginTrade',
1583 'date': '2018-03-25 15:15:54',
1584 'fee': '0.00150000',
1595 'symbol': 'ETH/BTC',
1596 'timestamp': 1521983754,
1602 'datetime': '2018-03-25T15:16:54.000Z',
1606 'category': 'marginTrade',
1607 'date': '2018-03-25 15:16:54',
1608 'fee': '0.00150000',
1619 'symbol': 'ETH/BTC',
1620 'timestamp': 1521983814,
1626 'datetime': '2018-03-25T15:15:54.000Z',
1630 'category': 'exchange',
1631 'date': '2018-03-25 15:15:54',
1632 'fee': '0.00150000',
1643 'symbol': 'ETH/BTC',
1644 'timestamp': 1521983754,
1650 'datetime': '2018-03-25T15:16:54.000Z',
1654 'category': 'exchange',
1655 'date': '2018-03-25 15:16:54',
1656 'fee': '0.00150000',
1667 'symbol': 'ETH/BTC',
1668 'timestamp': 1521983814,
1674 'datetime': '2018-03-25T15:15:54.000Z',
1678 'category': 'exchange',
1679 'date': '2018-03-25 15:15:54',
1680 'fee': '0.00150000',
1684 'total': '0.000066',
1691 'symbol': 'ETH/BTC',
1692 'timestamp': 1521983754,
1698 'datetime': '2018-03-25T15:16:54.000Z',
1702 'category': 'exchange',
1703 'date': '2018-03-25 15:16:54',
1704 'fee': '0.00150000',
1715 'symbol': 'ETH/BTC',
1716 'timestamp': 1521983814,
1722 'datetime': '2018-03-25T15:15:54.000Z',
1726 'category': 'exchange',
1727 'date': '2018-03-25 15:15:54',
1728 'fee': '0.00150000',
1739 'symbol': 'ETH/BTC',
1740 'timestamp': 1521983754,
1746 'datetime': '2018-03-25T15:16:54.000Z',
1750 'category': 'exchange',
1751 'date': '2018-03-25 15:16:54',
1752 'fee': '0.00150000',
1756 'total': '0.000036',
1763 'symbol': 'ETH/BTC',
1764 'timestamp': 1521983814,
1769 result
= order
.retrieve_order()
1770 self
.assertTrue(result
)
1771 self
.assertEqual('7', order
.results
[0]["id"])
1772 self
.m
.ccxt
.fetch_orders
.assert_called_once_with(symbol
="ETH/BTC", since
=1521983750)
1775 with self
.subTest(similar_open_order
=False, past_trades
=False):
1776 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
1777 D("0.1"), "BTC", "long", self
.m
, "trade")
1778 order
.start_date
= datetime
.datetime(2018, 3, 25, 15, 15, 55)
1780 self
.m
.ccxt
.order_precision
.return_value
= 8
1781 self
.m
.ccxt
.fetch_orders
.return_value
= []
1782 self
.m
.ccxt
.fetch_my_trades
.return_value
= []
1783 result
= order
.retrieve_order()
1784 self
.assertFalse(result
)
1786 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1787 class MouvementTest(WebMockTestCase
):
1788 def test_values(self
):
1789 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1790 "tradeID": 42, "type": "buy", "fee": "0.0015",
1791 "date": "2017-12-30 12:00:12", "rate": "0.1",
1792 "amount": "10", "total": "1"
1794 self
.assertEqual("ETH", mouvement
.currency
)
1795 self
.assertEqual("BTC", mouvement
.base_currency
)
1796 self
.assertEqual(42, mouvement
.id)
1797 self
.assertEqual("buy", mouvement
.action
)
1798 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
1799 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
1800 self
.assertEqual(D("0.1"), mouvement
.rate
)
1801 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
1802 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
1804 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
1805 self
.assertIsNone(mouvement
.date
)
1806 self
.assertIsNone(mouvement
.id)
1807 self
.assertIsNone(mouvement
.action
)
1808 self
.assertEqual(-1, mouvement
.fee_rate
)
1809 self
.assertEqual(0, mouvement
.rate
)
1810 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
1811 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
1813 def test__repr(self
):
1814 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1815 "tradeID": 42, "type": "buy", "fee": "0.0015",
1816 "date": "2017-12-30 12:00:12", "rate": "0.1",
1817 "amount": "10", "total": "1"
1819 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
1821 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1822 "tradeID": 42, "type": "buy",
1823 "date": "garbage", "rate": "0.1",
1824 "amount": "10", "total": "1"
1826 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
1828 def test_as_json(self
):
1829 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
1830 "tradeID": 42, "type": "buy", "fee": "0.0015",
1831 "date": "2017-12-30 12:00:12", "rate": "0.1",
1832 "amount": "10", "total": "1"
1834 as_json
= mouvement
.as_json()
1836 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
1837 self
.assertEqual(portfolio
.datetime
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
1838 self
.assertEqual("buy", as_json
["action"])
1839 self
.assertEqual(D("10"), as_json
["total"])
1840 self
.assertEqual(D("1"), as_json
["total_in_base"])
1841 self
.assertEqual("BTC", as_json
["base_currency"])
1842 self
.assertEqual("ETH", as_json
["currency"])
1844 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1845 class AmountTest(WebMockTestCase
):
1846 def test_values(self
):
1847 amount
= portfolio
.Amount("BTC", "0.65")
1848 self
.assertEqual(D("0.65"), amount
.value
)
1849 self
.assertEqual("BTC", amount
.currency
)
1851 def test_in_currency(self
):
1852 amount
= portfolio
.Amount("ETC", 10)
1854 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
1856 with self
.subTest(desc
="no ticker for currency"):
1857 self
.m
.get_ticker
.return_value
= None
1859 self
.assertEqual(portfolio
.Amount("ETH", 0), amount
.in_currency("ETH", self
.m
))
1861 with self
.subTest(desc
="nominal case"):
1862 self
.m
.get_ticker
.return_value
= {
1865 "average": D("0.3"),
1868 converted_amount
= amount
.in_currency("ETH", self
.m
)
1870 self
.assertEqual(D("3.0"), converted_amount
.value
)
1871 self
.assertEqual("ETH", converted_amount
.currency
)
1872 self
.assertEqual(amount
, converted_amount
.linked_to
)
1873 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
1875 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
1876 self
.assertEqual(D("2"), converted_amount
.value
)
1878 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
1879 self
.assertEqual(D("4"), converted_amount
.value
)
1881 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
1882 self
.assertEqual(D("0.2"), converted_amount
.value
)
1884 def test__round(self
):
1885 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
1886 self
.assertEqual(D("1.23456789"), round(amount
).value
)
1887 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
1889 def test__abs(self
):
1890 amount
= portfolio
.Amount("SC", -120)
1891 self
.assertEqual(120, abs(amount
).value
)
1892 self
.assertEqual("SC", abs(amount
).currency
)
1894 amount
= portfolio
.Amount("SC", 10)
1895 self
.assertEqual(10, abs(amount
).value
)
1896 self
.assertEqual("SC", abs(amount
).currency
)
1898 def test__add(self
):
1899 amount1
= portfolio
.Amount("XVG", "12.9")
1900 amount2
= portfolio
.Amount("XVG", "13.1")
1902 self
.assertEqual(26, (amount1
+ amount2
).value
)
1903 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
1905 amount3
= portfolio
.Amount("ETH", "1.6")
1906 with self
.assertRaises(Exception):
1909 amount4
= portfolio
.Amount("ETH", 0.0)
1910 self
.assertEqual(amount1
, amount1
+ amount4
)
1912 self
.assertEqual(amount1
, amount1
+ 0)
1914 def test__radd(self
):
1915 amount
= portfolio
.Amount("XVG", "12.9")
1917 self
.assertEqual(amount
, 0 + amount
)
1918 with self
.assertRaises(Exception):
1921 def test__sub(self
):
1922 amount1
= portfolio
.Amount("XVG", "13.3")
1923 amount2
= portfolio
.Amount("XVG", "13.1")
1925 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
1926 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
1928 amount3
= portfolio
.Amount("ETH", "1.6")
1929 with self
.assertRaises(Exception):
1932 amount4
= portfolio
.Amount("ETH", 0.0)
1933 self
.assertEqual(amount1
, amount1
- amount4
)
1935 def test__rsub(self
):
1936 amount
= portfolio
.Amount("ETH", "1.6")
1937 with self
.assertRaises(Exception):
1940 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
1942 def test__mul(self
):
1943 amount
= portfolio
.Amount("XEM", 11)
1945 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
1946 self
.assertEqual(D("33"), (amount
* 3).value
)
1948 with self
.assertRaises(Exception):
1951 def test__rmul(self
):
1952 amount
= portfolio
.Amount("XEM", 11)
1954 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
1955 self
.assertEqual(D("33"), (3 * amount
).value
)
1957 def test__floordiv(self
):
1958 amount
= portfolio
.Amount("XEM", 11)
1960 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1961 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1963 with self
.assertRaises(Exception):
1966 def test__truediv(self
):
1967 amount
= portfolio
.Amount("XEM", 11)
1969 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
1970 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
1973 amount1
= portfolio
.Amount("BTD", 11.3)
1974 amount2
= portfolio
.Amount("BTD", 13.1)
1976 self
.assertTrue(amount1
< amount2
)
1977 self
.assertFalse(amount2
< amount1
)
1978 self
.assertFalse(amount1
< amount1
)
1980 amount3
= portfolio
.Amount("BTC", 1.6)
1981 with self
.assertRaises(Exception):
1985 amount1
= portfolio
.Amount("BTD", 11.3)
1986 amount2
= portfolio
.Amount("BTD", 13.1)
1988 self
.assertTrue(amount1
<= amount2
)
1989 self
.assertFalse(amount2
<= amount1
)
1990 self
.assertTrue(amount1
<= amount1
)
1992 amount3
= portfolio
.Amount("BTC", 1.6)
1993 with self
.assertRaises(Exception):
1997 amount1
= portfolio
.Amount("BTD", 11.3)
1998 amount2
= portfolio
.Amount("BTD", 13.1)
2000 self
.assertTrue(amount2
> amount1
)
2001 self
.assertFalse(amount1
> amount2
)
2002 self
.assertFalse(amount1
> amount1
)
2004 amount3
= portfolio
.Amount("BTC", 1.6)
2005 with self
.assertRaises(Exception):
2009 amount1
= portfolio
.Amount("BTD", 11.3)
2010 amount2
= portfolio
.Amount("BTD", 13.1)
2012 self
.assertTrue(amount2
>= amount1
)
2013 self
.assertFalse(amount1
>= amount2
)
2014 self
.assertTrue(amount1
>= amount1
)
2016 amount3
= portfolio
.Amount("BTC", 1.6)
2017 with self
.assertRaises(Exception):
2021 amount1
= portfolio
.Amount("BTD", 11.3)
2022 amount2
= portfolio
.Amount("BTD", 13.1)
2023 amount3
= portfolio
.Amount("BTD", 11.3)
2025 self
.assertFalse(amount1
== amount2
)
2026 self
.assertFalse(amount2
== amount1
)
2027 self
.assertTrue(amount1
== amount3
)
2028 self
.assertFalse(amount2
== 0)
2030 amount4
= portfolio
.Amount("BTC", 1.6)
2031 with self
.assertRaises(Exception):
2034 amount5
= portfolio
.Amount("BTD", 0)
2035 self
.assertTrue(amount5
== 0)
2038 amount1
= portfolio
.Amount("BTD", 11.3)
2039 amount2
= portfolio
.Amount("BTD", 13.1)
2040 amount3
= portfolio
.Amount("BTD", 11.3)
2042 self
.assertTrue(amount1
!= amount2
)
2043 self
.assertTrue(amount2
!= amount1
)
2044 self
.assertFalse(amount1
!= amount3
)
2045 self
.assertTrue(amount2
!= 0)
2047 amount4
= portfolio
.Amount("BTC", 1.6)
2048 with self
.assertRaises(Exception):
2051 amount5
= portfolio
.Amount("BTD", 0)
2052 self
.assertFalse(amount5
!= 0)
2054 def test__neg(self
):
2055 amount1
= portfolio
.Amount("BTD", "11.3")
2057 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
2059 def test__str(self
):
2060 amount1
= portfolio
.Amount("BTX", 32)
2061 self
.assertEqual("32.00000000 BTX", str(amount1
))
2063 amount2
= portfolio
.Amount("USDT", 12000)
2064 amount1
.linked_to
= amount2
2065 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
2067 def test__repr(self
):
2068 amount1
= portfolio
.Amount("BTX", 32)
2069 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
2071 amount2
= portfolio
.Amount("USDT", 12000)
2072 amount1
.linked_to
= amount2
2073 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
2075 amount3
= portfolio
.Amount("BTC", 0.1)
2076 amount2
.linked_to
= amount3
2077 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
2079 def test_as_json(self
):
2080 amount
= portfolio
.Amount("BTX", 32)
2081 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
2083 amount
= portfolio
.Amount("BTX", "1E-10")
2084 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
2086 amount
= portfolio
.Amount("BTX", "1E-5")
2087 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
2088 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))