aboutsummaryrefslogtreecommitdiff
path: root/portfolio.py
diff options
context:
space:
mode:
Diffstat (limited to 'portfolio.py')
-rw-r--r--portfolio.py79
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 @@
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