diff options
Diffstat (limited to 'portfolio.py')
-rw-r--r-- | portfolio.py | 81 |
1 files changed, 38 insertions, 43 deletions
diff --git a/portfolio.py b/portfolio.py index f9423b9..43a39c4 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -1,13 +1,10 @@ | |||
1 | import time | 1 | import time |
2 | from datetime import datetime, timedelta | 2 | from datetime import datetime, timedelta |
3 | from decimal import Decimal as D, ROUND_DOWN | 3 | from decimal import Decimal as D, ROUND_DOWN |
4 | # Put your poloniex api key in market.py | ||
5 | from json import JSONDecodeError | 4 | from json import JSONDecodeError |
6 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | 5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError |
7 | from ccxt import ExchangeError, ExchangeNotAvailable | 6 | from ccxt import ExchangeError, ExchangeNotAvailable |
8 | import requests | 7 | import requests |
9 | import helper as h | ||
10 | from store import * | ||
11 | 8 | ||
12 | # FIXME: correctly handle web call timeouts | 9 | # FIXME: correctly handle web call timeouts |
13 | 10 | ||
@@ -18,26 +15,27 @@ class Portfolio: | |||
18 | last_date = None | 15 | last_date = None |
19 | 16 | ||
20 | @classmethod | 17 | @classmethod |
21 | def wait_for_recent(cls, delta=4): | 18 | def wait_for_recent(cls, market, delta=4): |
22 | cls.repartition(refetch=True) | 19 | cls.repartition(market, refetch=True) |
23 | while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): | 20 | while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): |
24 | time.sleep(30) | 21 | time.sleep(30) |
25 | cls.repartition(refetch=True) | 22 | market.report.print_log("Attempt to fetch up-to-date cryptoportfolio") |
23 | cls.repartition(market, refetch=True) | ||
26 | 24 | ||
27 | @classmethod | 25 | @classmethod |
28 | def repartition(cls, liquidity="medium", refetch=False): | 26 | def repartition(cls, market, liquidity="medium", refetch=False): |
29 | cls.parse_cryptoportfolio(refetch=refetch) | 27 | cls.parse_cryptoportfolio(market, refetch=refetch) |
30 | liquidities = cls.liquidities[liquidity] | 28 | liquidities = cls.liquidities[liquidity] |
31 | return liquidities[cls.last_date] | 29 | return liquidities[cls.last_date] |
32 | 30 | ||
33 | @classmethod | 31 | @classmethod |
34 | def get_cryptoportfolio(cls): | 32 | def get_cryptoportfolio(cls, market): |
35 | try: | 33 | try: |
36 | r = requests.get(cls.URL) | 34 | r = requests.get(cls.URL) |
37 | ReportStore.log_http_request(r.request.method, | 35 | market.report.log_http_request(r.request.method, |
38 | r.request.url, r.request.body, r.request.headers, r) | 36 | r.request.url, r.request.body, r.request.headers, r) |
39 | except Exception as e: | 37 | except Exception as e: |
40 | ReportStore.log_error("get_cryptoportfolio", exception=e) | 38 | market.report.log_error("get_cryptoportfolio", exception=e) |
41 | return | 39 | return |
42 | try: | 40 | try: |
43 | cls.data = r.json(parse_int=D, parse_float=D) | 41 | cls.data = r.json(parse_int=D, parse_float=D) |
@@ -45,9 +43,9 @@ class Portfolio: | |||
45 | cls.data = None | 43 | cls.data = None |
46 | 44 | ||
47 | @classmethod | 45 | @classmethod |
48 | def parse_cryptoportfolio(cls, refetch=False): | 46 | def parse_cryptoportfolio(cls, market, refetch=False): |
49 | if refetch or cls.data is None: | 47 | if refetch or cls.data is None: |
50 | cls.get_cryptoportfolio() | 48 | cls.get_cryptoportfolio(market) |
51 | 49 | ||
52 | def filter_weights(weight_hash): | 50 | def filter_weights(weight_hash): |
53 | if weight_hash[1][0] == 0: | 51 | if weight_hash[1][0] == 0: |
@@ -118,7 +116,7 @@ class Amount: | |||
118 | self.value * rate, | 116 | self.value * rate, |
119 | linked_to=self, | 117 | linked_to=self, |
120 | rate=rate) | 118 | rate=rate) |
121 | asset_ticker = h.get_ticker(self.currency, other_currency, market) | 119 | asset_ticker = market.get_ticker(self.currency, other_currency) |
122 | if asset_ticker is not None: | 120 | if asset_ticker is not None: |
123 | rate = Computation.compute_value(asset_ticker, action, compute_value=compute_value) | 121 | rate = Computation.compute_value(asset_ticker, action, compute_value=compute_value) |
124 | return Amount( | 122 | return Amount( |
@@ -283,7 +281,7 @@ class Balance: | |||
283 | return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")" | 281 | return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")" |
284 | 282 | ||
285 | class Trade: | 283 | class Trade: |
286 | def __init__(self, value_from, value_to, currency, market=None): | 284 | def __init__(self, value_from, value_to, currency, market): |
287 | # We have value_from of currency, and want to finish with value_to of | 285 | # We have value_from of currency, and want to finish with value_to of |
288 | # that currency. value_* may not be in currency's terms | 286 | # that currency. value_* may not be in currency's terms |
289 | self.currency = currency | 287 | self.currency = currency |
@@ -353,18 +351,18 @@ class Trade: | |||
353 | if tick == 7: | 351 | if tick == 7: |
354 | update = "market_fallback" | 352 | update = "market_fallback" |
355 | 353 | ||
356 | ReportStore.log_order(order, tick, update=update, | 354 | self.market.report.log_order(order, tick, update=update, |
357 | compute_value=compute_value, new_order=new_order) | 355 | compute_value=compute_value, new_order=new_order) |
358 | 356 | ||
359 | if new_order is not None: | 357 | if new_order is not None: |
360 | order.cancel() | 358 | order.cancel() |
361 | new_order.run() | 359 | new_order.run() |
362 | ReportStore.log_order(order, tick, new_order=new_order) | 360 | self.market.report.log_order(order, tick, new_order=new_order) |
363 | 361 | ||
364 | def prepare_order(self, compute_value="default"): | 362 | def prepare_order(self, compute_value="default"): |
365 | if self.action is None: | 363 | if self.action is None: |
366 | return None | 364 | return None |
367 | ticker = h.get_ticker(self.currency, self.base_currency, self.market) | 365 | ticker = self.market.get_ticker(self.currency, self.base_currency) |
368 | inverted = ticker["inverted"] | 366 | inverted = ticker["inverted"] |
369 | if inverted: | 367 | if inverted: |
370 | ticker = ticker["original"] | 368 | ticker = ticker["original"] |
@@ -430,7 +428,7 @@ class Trade: | |||
430 | close_if_possible = (self.value_to == 0) | 428 | close_if_possible = (self.value_to == 0) |
431 | 429 | ||
432 | if delta <= 0: | 430 | if delta <= 0: |
433 | ReportStore.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) | 431 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) |
434 | return None | 432 | return None |
435 | 433 | ||
436 | order = Order(self.order_action(inverted), | 434 | order = Order(self.order_action(inverted), |
@@ -456,11 +454,11 @@ class Trade: | |||
456 | self.action) | 454 | self.action) |
457 | 455 | ||
458 | def print_with_order(self, ind=""): | 456 | def print_with_order(self, ind=""): |
459 | ReportStore.print_log("{}{}".format(ind, self)) | 457 | self.market.report.print_log("{}{}".format(ind, self)) |
460 | for order in self.orders: | 458 | for order in self.orders: |
461 | ReportStore.print_log("{}\t{}".format(ind, order)) | 459 | self.market.report.print_log("{}\t{}".format(ind, order)) |
462 | for mouvement in order.mouvements: | 460 | for mouvement in order.mouvements: |
463 | ReportStore.print_log("{}\t\t{}".format(ind, mouvement)) | 461 | self.market.report.print_log("{}\t\t{}".format(ind, mouvement)) |
464 | 462 | ||
465 | class Order: | 463 | class Order: |
466 | def __init__(self, action, amount, rate, base_currency, trade_type, market, | 464 | def __init__(self, action, amount, rate, base_currency, trade_type, market, |
@@ -525,15 +523,15 @@ class Order: | |||
525 | 523 | ||
526 | def run(self): | 524 | def run(self): |
527 | symbol = "{}/{}".format(self.amount.currency, self.base_currency) | 525 | symbol = "{}/{}".format(self.amount.currency, self.base_currency) |
528 | amount = round(self.amount, self.market.order_precision(symbol)).value | 526 | amount = round(self.amount, self.market.ccxt.order_precision(symbol)).value |
529 | 527 | ||
530 | if TradeStore.debug: | 528 | if self.market.debug: |
531 | ReportStore.log_debug_action("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( | 529 | self.market.report.log_debug_action("market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( |
532 | symbol, self.action, amount, self.rate, self.account)) | 530 | symbol, self.action, amount, self.rate, self.account)) |
533 | self.results.append({"debug": True, "id": -1}) | 531 | self.results.append({"debug": True, "id": -1}) |
534 | else: | 532 | else: |
535 | try: | 533 | try: |
536 | self.results.append(self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)) | 534 | self.results.append(self.market.ccxt.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)) |
537 | except ExchangeNotAvailable: | 535 | except ExchangeNotAvailable: |
538 | # Impossible to honor the order (dust amount) | 536 | # Impossible to honor the order (dust amount) |
539 | self.status = "closed" | 537 | self.status = "closed" |
@@ -541,15 +539,15 @@ class Order: | |||
541 | return | 539 | return |
542 | except Exception as e: | 540 | except Exception as e: |
543 | self.status = "error" | 541 | self.status = "error" |
544 | action = "market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account) | 542 | action = "market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account) |
545 | ReportStore.log_error(action, exception=e) | 543 | self.market.report.log_error(action, exception=e) |
546 | return | 544 | return |
547 | self.id = self.results[0]["id"] | 545 | self.id = self.results[0]["id"] |
548 | self.status = "open" | 546 | self.status = "open" |
549 | 547 | ||
550 | def get_status(self): | 548 | def get_status(self): |
551 | if TradeStore.debug: | 549 | if self.market.debug: |
552 | ReportStore.log_debug_action("Getting {} status".format(self)) | 550 | self.market.report.log_debug_action("Getting {} status".format(self)) |
553 | return self.status | 551 | return self.status |
554 | # other states are "closed" and "canceled" | 552 | # other states are "closed" and "canceled" |
555 | if not self.finished: | 553 | if not self.finished: |
@@ -559,23 +557,23 @@ class Order: | |||
559 | return self.status | 557 | return self.status |
560 | 558 | ||
561 | def mark_finished_order(self): | 559 | def mark_finished_order(self): |
562 | if TradeStore.debug: | 560 | if self.market.debug: |
563 | ReportStore.log_debug_action("Mark {} as finished".format(self)) | 561 | self.market.report.log_debug_action("Mark {} as finished".format(self)) |
564 | return | 562 | return |
565 | if self.status == "closed": | 563 | if self.status == "closed": |
566 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: | 564 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: |
567 | self.market.close_margin_position(self.amount.currency, self.base_currency) | 565 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) |
568 | 566 | ||
569 | def fetch(self, force=False): | 567 | def fetch(self, force=False): |
570 | if TradeStore.debug: | 568 | if self.market.debug: |
571 | ReportStore.log_debug_action("Fetching {}".format(self)) | 569 | self.market.report.log_debug_action("Fetching {}".format(self)) |
572 | return | 570 | return |
573 | if (not force and self.fetch_cache_timestamp is not None | 571 | if (not force and self.fetch_cache_timestamp is not None |
574 | and time.time() - self.fetch_cache_timestamp < 10): | 572 | and time.time() - self.fetch_cache_timestamp < 10): |
575 | return | 573 | return |
576 | self.fetch_cache_timestamp = time.time() | 574 | self.fetch_cache_timestamp = time.time() |
577 | 575 | ||
578 | result = self.market.fetch_order(self.id) | 576 | result = self.market.ccxt.fetch_order(self.id) |
579 | self.results.append(result) | 577 | self.results.append(result) |
580 | 578 | ||
581 | self.status = result["status"] | 579 | self.status = result["status"] |
@@ -606,7 +604,7 @@ class Order: | |||
606 | 604 | ||
607 | def fetch_mouvements(self): | 605 | def fetch_mouvements(self): |
608 | try: | 606 | try: |
609 | mouvements = self.market.privatePostReturnOrderTrades({"orderNumber": self.id}) | 607 | mouvements = self.market.ccxt.privatePostReturnOrderTrades({"orderNumber": self.id}) |
610 | except ExchangeError: | 608 | except ExchangeError: |
611 | mouvements = [] | 609 | mouvements = [] |
612 | self.mouvements = [] | 610 | self.mouvements = [] |
@@ -616,11 +614,11 @@ class Order: | |||
616 | self.base_currency, mouvement_hash)) | 614 | self.base_currency, mouvement_hash)) |
617 | 615 | ||
618 | def cancel(self): | 616 | def cancel(self): |
619 | if TradeStore.debug: | 617 | if self.market.debug: |
620 | ReportStore.log_debug_action("Mark {} as cancelled".format(self)) | 618 | self.market.report.log_debug_action("Mark {} as cancelled".format(self)) |
621 | self.status = "canceled" | 619 | self.status = "canceled" |
622 | return | 620 | return |
623 | self.market.cancel_order(self.id) | 621 | self.market.ccxt.cancel_order(self.id) |
624 | self.fetch() | 622 | self.fetch() |
625 | 623 | ||
626 | class Mouvement: | 624 | class Mouvement: |
@@ -663,6 +661,3 @@ class Mouvement: | |||
663 | date, self.action, self.total, self.total_in_base, | 661 | date, self.action, self.total, self.total_in_base, |
664 | fee_rate) | 662 | fee_rate) |
665 | 663 | ||
666 | if __name__ == '__main__': # pragma: no cover | ||
667 | from market import market | ||
668 | h.print_orders(market) | ||