diff options
Diffstat (limited to 'portfolio.py')
-rw-r--r-- | portfolio.py | 79 |
1 files changed, 71 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 @@ | |||
1 | from datetime import datetime | 1 | from datetime import datetime |
2 | from decimal import Decimal as D, ROUND_DOWN | ||
3 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound | ||
4 | from retry import retry | 2 | from retry import retry |
5 | 3 | from decimal import Decimal as D, ROUND_DOWN | |
6 | # FIXME: correctly handle web call timeouts | 4 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound, RequestTimeout |
7 | 5 | ||
8 | class Computation: | 6 | class 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 | ||
415 | class RetryException(Exception): | ||
416 | pass | ||
417 | |||
417 | class Order: | 418 | class 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 | |||
588 | class Mouvement: | 651 | class 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 |