aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--portfolio.py79
-rw-r--r--test.py501
2 files changed, 572 insertions, 8 deletions
diff --git a/portfolio.py b/portfolio.py
index 69e3755..9c58676 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -1,9 +1,7 @@
1from datetime import datetime 1from datetime import datetime
2from decimal import Decimal as D, ROUND_DOWN
3from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound
4from retry import retry 2from retry import retry
5 3from decimal import Decimal as D, ROUND_DOWN
6# FIXME: correctly handle web call timeouts 4from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound, RequestTimeout
7 5
8class Computation: 6class Computation:
9 computations = { 7 computations = {
@@ -414,6 +412,9 @@ class Trade:
414 for mouvement in order.mouvements: 412 for mouvement in order.mouvements:
415 self.market.report.print_log("{}\t\t{}".format(ind, mouvement)) 413 self.market.report.print_log("{}\t\t{}".format(ind, mouvement))
416 414
415class RetryException(Exception):
416 pass
417
417class Order: 418class Order:
418 def __init__(self, action, amount, rate, base_currency, trade_type, market, 419 def __init__(self, action, amount, rate, base_currency, trade_type, market,
419 trade, close_if_possible=False): 420 trade, close_if_possible=False):
@@ -430,6 +431,7 @@ class Order:
430 self.close_if_possible = close_if_possible 431 self.close_if_possible = close_if_possible
431 self.id = None 432 self.id = None
432 self.tries = 0 433 self.tries = 0
434 self.start_date = None
433 435
434 def as_json(self): 436 def as_json(self):
435 return { 437 return {
@@ -475,18 +477,18 @@ class Order:
475 def finished(self): 477 def finished(self):
476 return self.status.startswith("closed") or self.status == "canceled" or self.status == "error" 478 return self.status.startswith("closed") or self.status == "canceled" or self.status == "error"
477 479
478 @retry(InsufficientFunds) 480 @retry((InsufficientFunds, RetryException))
479 def run(self): 481 def run(self):
480 self.tries += 1 482 self.tries += 1
481 symbol = "{}/{}".format(self.amount.currency, self.base_currency) 483 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
482 amount = round(self.amount, self.market.ccxt.order_precision(symbol)).value 484 amount = round(self.amount, self.market.ccxt.order_precision(symbol)).value
483 485
486 action = "market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account)
484 if self.market.debug: 487 if self.market.debug:
485 self.market.report.log_debug_action("market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 488 self.market.report.log_debug_action(action)
486 symbol, self.action, amount, self.rate, self.account))
487 self.results.append({"debug": True, "id": -1}) 489 self.results.append({"debug": True, "id": -1})
488 else: 490 else:
489 action = "market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account) 491 self.start_date = datetime.now()
490 try: 492 try:
491 self.results.append(self.market.ccxt.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)) 493 self.results.append(self.market.ccxt.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account))
492 except InvalidOrder: 494 except InvalidOrder:
@@ -494,6 +496,19 @@ class Order:
494 self.status = "closed" 496 self.status = "closed"
495 self.mark_finished_order() 497 self.mark_finished_order()
496 return 498 return
499 except RequestTimeout as e:
500 if not self.retrieve_order():
501 if self.tries < 5:
502 self.market.report.log_error(action, message="Retrying after timeout", exception=e)
503 # We make a specific call in case retrieve_order
504 # would raise itself
505 raise RetryException
506 else:
507 self.market.report.log_error(action, message="Giving up {} after timeouts".format(self), exception=e)
508 self.status = "error"
509 return
510 else:
511 self.market.report.log_error(action, message="Timeout, found the order")
497 except InsufficientFunds as e: 512 except InsufficientFunds as e:
498 if self.tries < 5: 513 if self.tries < 5:
499 self.market.report.log_error(action, message="Retrying with reduced amount", exception=e) 514 self.market.report.log_error(action, message="Retrying with reduced amount", exception=e)
@@ -585,6 +600,54 @@ class Order:
585 self.market.report.log_error("cancel_order", message="Already cancelled order", exception=e) 600 self.market.report.log_error("cancel_order", message="Already cancelled order", exception=e)
586 self.fetch() 601 self.fetch()
587 602
603 def retrieve_order(self):
604 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
605 amount = round(self.amount, self.market.ccxt.order_precision(symbol)).value
606 start_timestamp = self.start_date.timestamp() - 5
607
608 similar_open_orders = self.market.ccxt.fetch_orders(symbol=symbol, since=start_timestamp)
609 for order in similar_open_orders:
610 if (order["info"]["margin"] == 1 and self.account == "exchange") or\
611 (order["info"]["margin"] != 1 and self.account == "margin"):
612 i_m_tested = True # coverage bug ?!
613 continue
614 if order["info"]["side"] != self.action:
615 continue
616 amount_diff = round(
617 abs(D(order["info"]["startingAmount"]) - amount),
618 self.market.ccxt.order_precision(symbol))
619 rate_diff = round(
620 abs(D(order["info"]["rate"]) - self.rate),
621 self.market.ccxt.order_precision(symbol))
622 if amount_diff != 0 or rate_diff != 0:
623 continue
624 self.results.append({"id": order["id"]})
625 return True
626
627 similar_trades = self.market.ccxt.fetch_my_trades(symbol=symbol, since=start_timestamp)
628 # FIXME: use set instead of sorted(list(...))
629 for order_id in sorted(list(map(lambda x: x["order"], similar_trades))):
630 trades = list(filter(lambda x: x["order"] == order_id, similar_trades))
631 if any(x["timestamp"] < start_timestamp for x in trades):
632 continue
633 if any(x["side"] != self.action for x in trades):
634 continue
635 if any(x["info"]["category"] == "exchange" and self.account == "margin" for x in trades) or\
636 any(x["info"]["category"] == "marginTrade" and self.account == "exchange" for x in trades):
637 continue
638 trade_sum = sum(D(x["info"]["amount"]) for x in trades)
639 amount_diff = round(abs(trade_sum - amount),
640 self.market.ccxt.order_precision(symbol))
641 if amount_diff != 0:
642 continue
643 if (self.action == "sell" and any(D(x["info"]["rate"]) < self.rate for x in trades)) or\
644 (self.action == "buy" and any(D(x["info"]["rate"]) > self.rate for x in trades)):
645 continue
646 self.results.append({"id": order_id})
647 return True
648
649 return False
650
588class Mouvement: 651class Mouvement:
589 def __init__(self, currency, base_currency, hash_): 652 def __init__(self, currency, base_currency, hash_):
590 self.currency = currency 653 self.currency = currency
diff --git a/test.py b/test.py
index ea1fd9a..5b9e2b4 100644
--- a/test.py
+++ b/test.py
@@ -3213,6 +3213,507 @@ class OrderTest(WebMockTestCase):
3213 self.assertEqual(5, self.m.report.log_error.call_count) 3213 self.assertEqual(5, self.m.report.log_error.call_count)
3214 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) 3214 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)
3215 3215
3216 self.m.reset_mock()
3217 with self.subTest(request_timeout=True):
3218 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3219 D("0.1"), "BTC", "long", self.m, "trade")
3220 with self.subTest(retrieved=False), \
3221 mock.patch.object(order, "retrieve_order") as retrieve:
3222 self.m.ccxt.create_order.side_effect = [
3223 portfolio.RequestTimeout,
3224 portfolio.RequestTimeout,
3225 { "id": 123 },
3226 ]
3227 retrieve.return_value = False
3228 order.run()
3229 self.m.ccxt.create_order.assert_has_calls([
3230 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3231 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3232 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3233 ])
3234 self.assertEqual(3, self.m.ccxt.create_order.call_count)
3235 self.assertEqual(3, order.tries)
3236 self.m.report.log_error.assert_called()
3237 self.assertEqual(2, self.m.report.log_error.call_count)
3238 self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after timeout", exception=mock.ANY)
3239 self.assertEqual(123, order.id)
3240
3241 self.m.reset_mock()
3242 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3243 D("0.1"), "BTC", "long", self.m, "trade")
3244 with self.subTest(retrieved=True), \
3245 mock.patch.object(order, "retrieve_order") as retrieve:
3246 self.m.ccxt.create_order.side_effect = [
3247 portfolio.RequestTimeout,
3248 ]
3249 def _retrieve():
3250 order.results.append({"id": 123})
3251 return True
3252 retrieve.side_effect = _retrieve
3253 order.run()
3254 self.m.ccxt.create_order.assert_has_calls([
3255 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3256 ])
3257 self.assertEqual(1, self.m.ccxt.create_order.call_count)
3258 self.assertEqual(1, order.tries)
3259 self.m.report.log_error.assert_called()
3260 self.assertEqual(1, self.m.report.log_error.call_count)
3261 self.m.report.log_error.assert_called_with(mock.ANY, message="Timeout, found the order")
3262 self.assertEqual(123, order.id)
3263
3264 self.m.reset_mock()
3265 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3266 D("0.1"), "BTC", "long", self.m, "trade")
3267 with self.subTest(retrieved=False), \
3268 mock.patch.object(order, "retrieve_order") as retrieve:
3269 self.m.ccxt.create_order.side_effect = [
3270 portfolio.RequestTimeout,
3271 portfolio.RequestTimeout,
3272 portfolio.RequestTimeout,
3273 portfolio.RequestTimeout,
3274 portfolio.RequestTimeout,
3275 ]
3276 retrieve.return_value = False
3277 order.run()
3278 self.m.ccxt.create_order.assert_has_calls([
3279 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3280 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3281 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3282 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3283 mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
3284 ])
3285 self.assertEqual(5, self.m.ccxt.create_order.call_count)
3286 self.assertEqual(5, order.tries)
3287 self.m.report.log_error.assert_called()
3288 self.assertEqual(5, self.m.report.log_error.call_count)
3289 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)
3290 self.assertEqual("error", order.status)
3291
3292 def test_retrieve_order(self):
3293 with self.subTest(similar_open_order=True):
3294 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3295 D("0.1"), "BTC", "long", self.m, "trade")
3296 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
3297
3298 self.m.ccxt.order_precision.return_value = 8
3299 self.m.ccxt.fetch_orders.return_value = [
3300 { # Wrong amount
3301 'amount': 0.002, 'cost': 0.1,
3302 'datetime': '2018-03-25T15:15:51.000Z',
3303 'fee': None, 'filled': 0.0,
3304 'id': '1',
3305 'info': {
3306 'amount': '0.002',
3307 'date': '2018-03-25 15:15:51',
3308 'margin': 0, 'orderNumber': '1',
3309 'price': '0.1', 'rate': '0.1',
3310 'side': 'buy', 'startingAmount': '0.002',
3311 'status': 'open', 'total': '0.0002',
3312 'type': 'limit'
3313 },
3314 'price': 0.1, 'remaining': 0.002, 'side': 'buy',
3315 'status': 'open', 'symbol': 'ETH/BTC',
3316 'timestamp': 1521990951000, 'trades': None,
3317 'type': 'limit'
3318 },
3319 { # Margin
3320 'amount': 0.001, 'cost': 0.1,
3321 'datetime': '2018-03-25T15:15:51.000Z',
3322 'fee': None, 'filled': 0.0,
3323 'id': '2',
3324 'info': {
3325 'amount': '0.001',
3326 'date': '2018-03-25 15:15:51',
3327 'margin': 1, 'orderNumber': '2',
3328 'price': '0.1', 'rate': '0.1',
3329 'side': 'buy', 'startingAmount': '0.001',
3330 'status': 'open', 'total': '0.0001',
3331 'type': 'limit'
3332 },
3333 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
3334 'status': 'open', 'symbol': 'ETH/BTC',
3335 'timestamp': 1521990951000, 'trades': None,
3336 'type': 'limit'
3337 },
3338 { # selling
3339 'amount': 0.001, 'cost': 0.1,
3340 'datetime': '2018-03-25T15:15:51.000Z',
3341 'fee': None, 'filled': 0.0,
3342 'id': '3',
3343 'info': {
3344 'amount': '0.001',
3345 'date': '2018-03-25 15:15:51',
3346 'margin': 0, 'orderNumber': '3',
3347 'price': '0.1', 'rate': '0.1',
3348 'side': 'sell', 'startingAmount': '0.001',
3349 'status': 'open', 'total': '0.0001',
3350 'type': 'limit'
3351 },
3352 'price': 0.1, 'remaining': 0.001, 'side': 'sell',
3353 'status': 'open', 'symbol': 'ETH/BTC',
3354 'timestamp': 1521990951000, 'trades': None,
3355 'type': 'limit'
3356 },
3357 { # Wrong rate
3358 'amount': 0.001, 'cost': 0.15,
3359 'datetime': '2018-03-25T15:15:51.000Z',
3360 'fee': None, 'filled': 0.0,
3361 'id': '4',
3362 'info': {
3363 'amount': '0.001',
3364 'date': '2018-03-25 15:15:51',
3365 'margin': 0, 'orderNumber': '4',
3366 'price': '0.15', 'rate': '0.15',
3367 'side': 'buy', 'startingAmount': '0.001',
3368 'status': 'open', 'total': '0.0001',
3369 'type': 'limit'
3370 },
3371 'price': 0.15, 'remaining': 0.001, 'side': 'buy',
3372 'status': 'open', 'symbol': 'ETH/BTC',
3373 'timestamp': 1521990951000, 'trades': None,
3374 'type': 'limit'
3375 },
3376 { # All good
3377 'amount': 0.001, 'cost': 0.1,
3378 'datetime': '2018-03-25T15:15:51.000Z',
3379 'fee': None, 'filled': 0.0,
3380 'id': '5',
3381 'info': {
3382 'amount': '0.001',
3383 'date': '2018-03-25 15:15:51',
3384 'margin': 0, 'orderNumber': '1',
3385 'price': '0.1', 'rate': '0.1',
3386 'side': 'buy', 'startingAmount': '0.001',
3387 'status': 'open', 'total': '0.0001',
3388 'type': 'limit'
3389 },
3390 'price': 0.1, 'remaining': 0.001, 'side': 'buy',
3391 'status': 'open', 'symbol': 'ETH/BTC',
3392 'timestamp': 1521990951000, 'trades': None,
3393 'type': 'limit'
3394 }
3395 ]
3396 result = order.retrieve_order()
3397 self.assertTrue(result)
3398 self.assertEqual('5', order.results[0]["id"])
3399 self.m.ccxt.fetch_my_trades.assert_not_called()
3400 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
3401
3402 self.m.reset_mock()
3403 with self.subTest(similar_open_order=False, past_trades=True):
3404 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3405 D("0.1"), "BTC", "long", self.m, "trade")
3406 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
3407
3408 self.m.ccxt.order_precision.return_value = 8
3409 self.m.ccxt.fetch_orders.return_value = []
3410 self.m.ccxt.fetch_my_trades.return_value = [
3411 { # Wrong timestamp 1
3412 'amount': 0.0006,
3413 'cost': 0.00006,
3414 'datetime': '2018-03-25T15:15:14.000Z',
3415 'id': '1-1',
3416 'info': {
3417 'amount': '0.0006',
3418 'category': 'exchange',
3419 'date': '2018-03-25 15:15:14',
3420 'fee': '0.00150000',
3421 'globalTradeID': 1,
3422 'orderNumber': '1',
3423 'rate': '0.1',
3424 'total': '0.00006',
3425 'tradeID': '1-1',
3426 'type': 'buy'
3427 },
3428 'order': '1',
3429 'price': 0.1,
3430 'side': 'buy',
3431 'symbol': 'ETH/BTC',
3432 'timestamp': 1521983714,
3433 'type': 'limit'
3434 },
3435 { # Wrong timestamp 2
3436 'amount': 0.0004,
3437 'cost': 0.00004,
3438 'datetime': '2018-03-25T15:16:54.000Z',
3439 'id': '1-2',
3440 'info': {
3441 'amount': '0.0004',
3442 'category': 'exchange',
3443 'date': '2018-03-25 15:16:54',
3444 'fee': '0.00150000',
3445 'globalTradeID': 2,
3446 'orderNumber': '1',
3447 'rate': '0.1',
3448 'total': '0.00004',
3449 'tradeID': '1-2',
3450 'type': 'buy'
3451 },
3452 'order': '1',
3453 'price': 0.1,
3454 'side': 'buy',
3455 'symbol': 'ETH/BTC',
3456 'timestamp': 1521983814,
3457 'type': 'limit'
3458 },
3459 { # Wrong side 1
3460 'amount': 0.0006,
3461 'cost': 0.00006,
3462 'datetime': '2018-03-25T15:15:54.000Z',
3463 'id': '2-1',
3464 'info': {
3465 'amount': '0.0006',
3466 'category': 'exchange',
3467 'date': '2018-03-25 15:15:54',
3468 'fee': '0.00150000',
3469 'globalTradeID': 1,
3470 'orderNumber': '2',
3471 'rate': '0.1',
3472 'total': '0.00006',
3473 'tradeID': '2-1',
3474 'type': 'sell'
3475 },
3476 'order': '2',
3477 'price': 0.1,
3478 'side': 'sell',
3479 'symbol': 'ETH/BTC',
3480 'timestamp': 1521983754,
3481 'type': 'limit'
3482 },
3483 { # Wrong side 2
3484 'amount': 0.0004,
3485 'cost': 0.00004,
3486 'datetime': '2018-03-25T15:16:54.000Z',
3487 'id': '2-2',
3488 'info': {
3489 'amount': '0.0004',
3490 'category': 'exchange',
3491 'date': '2018-03-25 15:16:54',
3492 'fee': '0.00150000',
3493 'globalTradeID': 2,
3494 'orderNumber': '2',
3495 'rate': '0.1',
3496 'total': '0.00004',
3497 'tradeID': '2-2',
3498 'type': 'buy'
3499 },
3500 'order': '2',
3501 'price': 0.1,
3502 'side': 'buy',
3503 'symbol': 'ETH/BTC',
3504 'timestamp': 1521983814,
3505 'type': 'limit'
3506 },
3507 { # Margin trade 1
3508 'amount': 0.0006,
3509 'cost': 0.00006,
3510 'datetime': '2018-03-25T15:15:54.000Z',
3511 'id': '3-1',
3512 'info': {
3513 'amount': '0.0006',
3514 'category': 'marginTrade',
3515 'date': '2018-03-25 15:15:54',
3516 'fee': '0.00150000',
3517 'globalTradeID': 1,
3518 'orderNumber': '3',
3519 'rate': '0.1',
3520 'total': '0.00006',
3521 'tradeID': '3-1',
3522 'type': 'buy'
3523 },
3524 'order': '3',
3525 'price': 0.1,
3526 'side': 'buy',
3527 'symbol': 'ETH/BTC',
3528 'timestamp': 1521983754,
3529 'type': 'limit'
3530 },
3531 { # Margin trade 2
3532 'amount': 0.0004,
3533 'cost': 0.00004,
3534 'datetime': '2018-03-25T15:16:54.000Z',
3535 'id': '3-2',
3536 'info': {
3537 'amount': '0.0004',
3538 'category': 'marginTrade',
3539 'date': '2018-03-25 15:16:54',
3540 'fee': '0.00150000',
3541 'globalTradeID': 2,
3542 'orderNumber': '3',
3543 'rate': '0.1',
3544 'total': '0.00004',
3545 'tradeID': '3-2',
3546 'type': 'buy'
3547 },
3548 'order': '3',
3549 'price': 0.1,
3550 'side': 'buy',
3551 'symbol': 'ETH/BTC',
3552 'timestamp': 1521983814,
3553 'type': 'limit'
3554 },
3555 { # Wrong amount 1
3556 'amount': 0.0005,
3557 'cost': 0.00005,
3558 'datetime': '2018-03-25T15:15:54.000Z',
3559 'id': '4-1',
3560 'info': {
3561 'amount': '0.0005',
3562 'category': 'exchange',
3563 'date': '2018-03-25 15:15:54',
3564 'fee': '0.00150000',
3565 'globalTradeID': 1,
3566 'orderNumber': '4',
3567 'rate': '0.1',
3568 'total': '0.00005',
3569 'tradeID': '4-1',
3570 'type': 'buy'
3571 },
3572 'order': '4',
3573 'price': 0.1,
3574 'side': 'buy',
3575 'symbol': 'ETH/BTC',
3576 'timestamp': 1521983754,
3577 'type': 'limit'
3578 },
3579 { # Wrong amount 2
3580 'amount': 0.0004,
3581 'cost': 0.00004,
3582 'datetime': '2018-03-25T15:16:54.000Z',
3583 'id': '4-2',
3584 'info': {
3585 'amount': '0.0004',
3586 'category': 'exchange',
3587 'date': '2018-03-25 15:16:54',
3588 'fee': '0.00150000',
3589 'globalTradeID': 2,
3590 'orderNumber': '4',
3591 'rate': '0.1',
3592 'total': '0.00004',
3593 'tradeID': '4-2',
3594 'type': 'buy'
3595 },
3596 'order': '4',
3597 'price': 0.1,
3598 'side': 'buy',
3599 'symbol': 'ETH/BTC',
3600 'timestamp': 1521983814,
3601 'type': 'limit'
3602 },
3603 { # Wrong price 1
3604 'amount': 0.0006,
3605 'cost': 0.000066,
3606 'datetime': '2018-03-25T15:15:54.000Z',
3607 'id': '5-1',
3608 'info': {
3609 'amount': '0.0006',
3610 'category': 'exchange',
3611 'date': '2018-03-25 15:15:54',
3612 'fee': '0.00150000',
3613 'globalTradeID': 1,
3614 'orderNumber': '5',
3615 'rate': '0.11',
3616 'total': '0.000066',
3617 'tradeID': '5-1',
3618 'type': 'buy'
3619 },
3620 'order': '5',
3621 'price': 0.11,
3622 'side': 'buy',
3623 'symbol': 'ETH/BTC',
3624 'timestamp': 1521983754,
3625 'type': 'limit'
3626 },
3627 { # Wrong price 2
3628 'amount': 0.0004,
3629 'cost': 0.00004,
3630 'datetime': '2018-03-25T15:16:54.000Z',
3631 'id': '5-2',
3632 'info': {
3633 'amount': '0.0004',
3634 'category': 'exchange',
3635 'date': '2018-03-25 15:16:54',
3636 'fee': '0.00150000',
3637 'globalTradeID': 2,
3638 'orderNumber': '5',
3639 'rate': '0.1',
3640 'total': '0.00004',
3641 'tradeID': '5-2',
3642 'type': 'buy'
3643 },
3644 'order': '5',
3645 'price': 0.1,
3646 'side': 'buy',
3647 'symbol': 'ETH/BTC',
3648 'timestamp': 1521983814,
3649 'type': 'limit'
3650 },
3651 { # All good 1
3652 'amount': 0.0006,
3653 'cost': 0.00006,
3654 'datetime': '2018-03-25T15:15:54.000Z',
3655 'id': '7-1',
3656 'info': {
3657 'amount': '0.0006',
3658 'category': 'exchange',
3659 'date': '2018-03-25 15:15:54',
3660 'fee': '0.00150000',
3661 'globalTradeID': 1,
3662 'orderNumber': '7',
3663 'rate': '0.1',
3664 'total': '0.00006',
3665 'tradeID': '7-1',
3666 'type': 'buy'
3667 },
3668 'order': '7',
3669 'price': 0.1,
3670 'side': 'buy',
3671 'symbol': 'ETH/BTC',
3672 'timestamp': 1521983754,
3673 'type': 'limit'
3674 },
3675 { # All good 2
3676 'amount': 0.0004,
3677 'cost': 0.000036,
3678 'datetime': '2018-03-25T15:16:54.000Z',
3679 'id': '7-2',
3680 'info': {
3681 'amount': '0.0004',
3682 'category': 'exchange',
3683 'date': '2018-03-25 15:16:54',
3684 'fee': '0.00150000',
3685 'globalTradeID': 2,
3686 'orderNumber': '7',
3687 'rate': '0.09',
3688 'total': '0.000036',
3689 'tradeID': '7-2',
3690 'type': 'buy'
3691 },
3692 'order': '7',
3693 'price': 0.09,
3694 'side': 'buy',
3695 'symbol': 'ETH/BTC',
3696 'timestamp': 1521983814,
3697 'type': 'limit'
3698 },
3699 ]
3700
3701 result = order.retrieve_order()
3702 self.assertTrue(result)
3703 self.assertEqual('7', order.results[0]["id"])
3704 self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)
3705
3706 self.m.reset_mock()
3707 with self.subTest(similar_open_order=False, past_trades=False):
3708 order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
3709 D("0.1"), "BTC", "long", self.m, "trade")
3710 order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)
3711
3712 self.m.ccxt.order_precision.return_value = 8
3713 self.m.ccxt.fetch_orders.return_value = []
3714 self.m.ccxt.fetch_my_trades.return_value = []
3715 result = order.retrieve_order()
3716 self.assertFalse(result)
3216 3717
3217@unittest.skipUnless("unit" in limits, "Unit skipped") 3718@unittest.skipUnless("unit" in limits, "Unit skipped")
3218class MouvementTest(WebMockTestCase): 3719class MouvementTest(WebMockTestCase):