diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-24 19:53:58 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-24 19:53:58 +0100 |
commit | 3d0247f944d7510943dfaa64eeb0e15a43b6c989 (patch) | |
tree | 2695181c77e444d52bca5de47b6c99b9b6c1ed32 /portfolio.py | |
parent | c31df868c655612b8387a25111e69882f0fe6344 (diff) | |
download | Trader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.tar.gz Trader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.tar.zst Trader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.zip |
Add report store to store messages and logs
Diffstat (limited to 'portfolio.py')
-rw-r--r-- | portfolio.py | 107 |
1 files changed, 85 insertions, 22 deletions
diff --git a/portfolio.py b/portfolio.py index c3809f0..f9423b9 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -3,6 +3,7 @@ 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 | 4 | # Put your poloniex api key in market.py |
5 | from json import JSONDecodeError | 5 | from json import JSONDecodeError |
6 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | ||
6 | from ccxt import ExchangeError, ExchangeNotAvailable | 7 | from ccxt import ExchangeError, ExchangeNotAvailable |
7 | import requests | 8 | import requests |
8 | import helper as h | 9 | import helper as h |
@@ -33,11 +34,14 @@ class Portfolio: | |||
33 | def get_cryptoportfolio(cls): | 34 | def get_cryptoportfolio(cls): |
34 | try: | 35 | try: |
35 | r = requests.get(cls.URL) | 36 | r = requests.get(cls.URL) |
36 | except Exception: | 37 | ReportStore.log_http_request(r.request.method, |
38 | r.request.url, r.request.body, r.request.headers, r) | ||
39 | except Exception as e: | ||
40 | ReportStore.log_error("get_cryptoportfolio", exception=e) | ||
37 | return | 41 | return |
38 | try: | 42 | try: |
39 | cls.data = r.json(parse_int=D, parse_float=D) | 43 | cls.data = r.json(parse_int=D, parse_float=D) |
40 | except JSONDecodeError: | 44 | except (JSONDecodeError, SimpleJSONDecodeError): |
41 | cls.data = None | 45 | cls.data = None |
42 | 46 | ||
43 | @classmethod | 47 | @classmethod |
@@ -126,6 +130,12 @@ class Amount: | |||
126 | else: | 130 | else: |
127 | raise Exception("This asset is not available in the chosen market") | 131 | raise Exception("This asset is not available in the chosen market") |
128 | 132 | ||
133 | def as_json(self): | ||
134 | return { | ||
135 | "currency": self.currency, | ||
136 | "value": round(self).value.normalize(), | ||
137 | } | ||
138 | |||
129 | def __round__(self, n=8): | 139 | def __round__(self, n=8): |
130 | return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN)) | 140 | return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN)) |
131 | 141 | ||
@@ -216,12 +226,13 @@ class Amount: | |||
216 | return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to)) | 226 | return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to)) |
217 | 227 | ||
218 | class Balance: | 228 | class Balance: |
229 | base_keys = ["total", "exchange_total", "exchange_used", | ||
230 | "exchange_free", "margin_total", "margin_borrowed", | ||
231 | "margin_free"] | ||
219 | 232 | ||
220 | def __init__(self, currency, hash_): | 233 | def __init__(self, currency, hash_): |
221 | self.currency = currency | 234 | self.currency = currency |
222 | for key in ["total", | 235 | for key in self.base_keys: |
223 | "exchange_total", "exchange_used", "exchange_free", | ||
224 | "margin_total", "margin_borrowed", "margin_free"]: | ||
225 | setattr(self, key, Amount(currency, hash_.get(key, 0))) | 236 | setattr(self, key, Amount(currency, hash_.get(key, 0))) |
226 | 237 | ||
227 | self.margin_position_type = hash_.get("margin_position_type") | 238 | self.margin_position_type = hash_.get("margin_position_type") |
@@ -236,6 +247,9 @@ class Balance: | |||
236 | ]: | 247 | ]: |
237 | setattr(self, key, Amount(base_currency, hash_.get(key, 0))) | 248 | setattr(self, key, Amount(base_currency, hash_.get(key, 0))) |
238 | 249 | ||
250 | def as_json(self): | ||
251 | return dict(map(lambda x: (x, getattr(self, x).as_json()["value"]), self.base_keys)) | ||
252 | |||
239 | def __repr__(self): | 253 | def __repr__(self): |
240 | if self.exchange_total > 0: | 254 | if self.exchange_total > 0: |
241 | if self.exchange_free > 0 and self.exchange_used > 0: | 255 | if self.exchange_free > 0 and self.exchange_used > 0: |
@@ -318,23 +332,34 @@ class Trade: | |||
318 | def update_order(self, order, tick): | 332 | def update_order(self, order, tick): |
319 | new_order = None | 333 | new_order = None |
320 | if tick in [0, 1, 3, 4, 6]: | 334 | if tick in [0, 1, 3, 4, 6]: |
321 | print("{}, tick {}, waiting".format(order, tick)) | 335 | update = "waiting" |
336 | compute_value = None | ||
322 | elif tick == 2: | 337 | elif tick == 2: |
338 | update = "adjusting" | ||
339 | compute_value = 'lambda x, y: (x[y] + x["average"]) / 2' | ||
323 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) | 340 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) |
324 | print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) | ||
325 | elif tick ==5: | 341 | elif tick ==5: |
342 | update = "adjusting" | ||
343 | compute_value = 'lambda x, y: (x[y]*2 + x["average"]) / 3' | ||
326 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) | 344 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) |
327 | print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) | ||
328 | elif tick >= 7: | 345 | elif tick >= 7: |
329 | if tick == 7: | ||
330 | print("{}, tick {}, fallbacking to market value".format(order, tick)) | ||
331 | if (tick - 7) % 3 == 0: | 346 | if (tick - 7) % 3 == 0: |
332 | new_order = self.prepare_order(compute_value="default") | 347 | new_order = self.prepare_order(compute_value="default") |
333 | print("{}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) | 348 | update = "market_adjust" |
349 | compute_value = "default" | ||
350 | else: | ||
351 | update = "waiting" | ||
352 | compute_value = None | ||
353 | if tick == 7: | ||
354 | update = "market_fallback" | ||
355 | |||
356 | ReportStore.log_order(order, tick, update=update, | ||
357 | compute_value=compute_value, new_order=new_order) | ||
334 | 358 | ||
335 | if new_order is not None: | 359 | if new_order is not None: |
336 | order.cancel() | 360 | order.cancel() |
337 | new_order.run() | 361 | new_order.run() |
362 | ReportStore.log_order(order, tick, new_order=new_order) | ||
338 | 363 | ||
339 | def prepare_order(self, compute_value="default"): | 364 | def prepare_order(self, compute_value="default"): |
340 | if self.action is None: | 365 | if self.action is None: |
@@ -405,7 +430,7 @@ class Trade: | |||
405 | close_if_possible = (self.value_to == 0) | 430 | close_if_possible = (self.value_to == 0) |
406 | 431 | ||
407 | if delta <= 0: | 432 | if delta <= 0: |
408 | print("Less to do than already filled: {}".format(delta)) | 433 | ReportStore.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) |
409 | return None | 434 | return None |
410 | 435 | ||
411 | order = Order(self.order_action(inverted), | 436 | order = Order(self.order_action(inverted), |
@@ -414,6 +439,15 @@ class Trade: | |||
414 | self.orders.append(order) | 439 | self.orders.append(order) |
415 | return order | 440 | return order |
416 | 441 | ||
442 | def as_json(self): | ||
443 | return { | ||
444 | "action": self.action, | ||
445 | "from": self.value_from.as_json()["value"], | ||
446 | "to": self.value_to.as_json()["value"], | ||
447 | "currency": self.currency, | ||
448 | "base_currency": self.base_currency, | ||
449 | } | ||
450 | |||
417 | def __repr__(self): | 451 | def __repr__(self): |
418 | return "Trade({} -> {} in {}, {})".format( | 452 | return "Trade({} -> {} in {}, {})".format( |
419 | self.value_from, | 453 | self.value_from, |
@@ -421,12 +455,12 @@ class Trade: | |||
421 | self.currency, | 455 | self.currency, |
422 | self.action) | 456 | self.action) |
423 | 457 | ||
424 | def print_with_order(self): | 458 | def print_with_order(self, ind=""): |
425 | print(self) | 459 | ReportStore.print_log("{}{}".format(ind, self)) |
426 | for order in self.orders: | 460 | for order in self.orders: |
427 | print("\t", order, sep="") | 461 | ReportStore.print_log("{}\t{}".format(ind, order)) |
428 | for mouvement in order.mouvements: | 462 | for mouvement in order.mouvements: |
429 | print("\t\t", mouvement, sep="") | 463 | ReportStore.print_log("{}\t\t{}".format(ind, mouvement)) |
430 | 464 | ||
431 | class Order: | 465 | class Order: |
432 | def __init__(self, action, amount, rate, base_currency, trade_type, market, | 466 | def __init__(self, action, amount, rate, base_currency, trade_type, market, |
@@ -445,6 +479,20 @@ class Order: | |||
445 | self.id = None | 479 | self.id = None |
446 | self.fetch_cache_timestamp = None | 480 | self.fetch_cache_timestamp = None |
447 | 481 | ||
482 | def as_json(self): | ||
483 | return { | ||
484 | "action": self.action, | ||
485 | "trade_type": self.trade_type, | ||
486 | "amount": self.amount.as_json()["value"], | ||
487 | "currency": self.amount.as_json()["currency"], | ||
488 | "base_currency": self.base_currency, | ||
489 | "rate": self.rate, | ||
490 | "status": self.status, | ||
491 | "close_if_possible": self.close_if_possible, | ||
492 | "id": self.id, | ||
493 | "mouvements": list(map(lambda x: x.as_json(), self.mouvements)) | ||
494 | } | ||
495 | |||
448 | def __repr__(self): | 496 | def __repr__(self): |
449 | return "Order({} {} {} at {} {} [{}]{})".format( | 497 | return "Order({} {} {} at {} {} [{}]{})".format( |
450 | self.action, | 498 | self.action, |
@@ -480,7 +528,7 @@ class Order: | |||
480 | amount = round(self.amount, self.market.order_precision(symbol)).value | 528 | amount = round(self.amount, self.market.order_precision(symbol)).value |
481 | 529 | ||
482 | if TradeStore.debug: | 530 | if TradeStore.debug: |
483 | print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( | 531 | ReportStore.log_debug_action("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( |
484 | symbol, self.action, amount, self.rate, self.account)) | 532 | symbol, self.action, amount, self.rate, self.account)) |
485 | self.results.append({"debug": True, "id": -1}) | 533 | self.results.append({"debug": True, "id": -1}) |
486 | else: | 534 | else: |
@@ -493,16 +541,15 @@ class Order: | |||
493 | return | 541 | return |
494 | except Exception as e: | 542 | except Exception as e: |
495 | self.status = "error" | 543 | self.status = "error" |
496 | print("error when running market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( | 544 | action = "market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account) |
497 | symbol, self.action, amount, self.rate, self.account)) | 545 | ReportStore.log_error(action, exception=e) |
498 | self.error_message = str("{}: {}".format(e.__class__.__name__, e)) | ||
499 | print(self.error_message) | ||
500 | return | 546 | return |
501 | self.id = self.results[0]["id"] | 547 | self.id = self.results[0]["id"] |
502 | self.status = "open" | 548 | self.status = "open" |
503 | 549 | ||
504 | def get_status(self): | 550 | def get_status(self): |
505 | if TradeStore.debug: | 551 | if TradeStore.debug: |
552 | ReportStore.log_debug_action("Getting {} status".format(self)) | ||
506 | return self.status | 553 | return self.status |
507 | # other states are "closed" and "canceled" | 554 | # other states are "closed" and "canceled" |
508 | if not self.finished: | 555 | if not self.finished: |
@@ -513,13 +560,17 @@ class Order: | |||
513 | 560 | ||
514 | def mark_finished_order(self): | 561 | def mark_finished_order(self): |
515 | if TradeStore.debug: | 562 | if TradeStore.debug: |
563 | ReportStore.log_debug_action("Mark {} as finished".format(self)) | ||
516 | return | 564 | return |
517 | if self.status == "closed": | 565 | if self.status == "closed": |
518 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: | 566 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: |
519 | self.market.close_margin_position(self.amount.currency, self.base_currency) | 567 | self.market.close_margin_position(self.amount.currency, self.base_currency) |
520 | 568 | ||
521 | def fetch(self, force=False): | 569 | def fetch(self, force=False): |
522 | if TradeStore.debug or (not force and self.fetch_cache_timestamp is not None | 570 | if TradeStore.debug: |
571 | ReportStore.log_debug_action("Fetching {}".format(self)) | ||
572 | return | ||
573 | if (not force and self.fetch_cache_timestamp is not None | ||
523 | and time.time() - self.fetch_cache_timestamp < 10): | 574 | and time.time() - self.fetch_cache_timestamp < 10): |
524 | return | 575 | return |
525 | self.fetch_cache_timestamp = time.time() | 576 | self.fetch_cache_timestamp = time.time() |
@@ -566,6 +617,7 @@ class Order: | |||
566 | 617 | ||
567 | def cancel(self): | 618 | def cancel(self): |
568 | if TradeStore.debug: | 619 | if TradeStore.debug: |
620 | ReportStore.log_debug_action("Mark {} as cancelled".format(self)) | ||
569 | self.status = "canceled" | 621 | self.status = "canceled" |
570 | return | 622 | return |
571 | self.market.cancel_order(self.id) | 623 | self.market.cancel_order(self.id) |
@@ -587,6 +639,17 @@ class Mouvement: | |||
587 | # rate * total = total_in_base | 639 | # rate * total = total_in_base |
588 | self.total_in_base = Amount(base_currency, hash_.get("total", 0)) | 640 | self.total_in_base = Amount(base_currency, hash_.get("total", 0)) |
589 | 641 | ||
642 | def as_json(self): | ||
643 | return { | ||
644 | "fee_rate": self.fee_rate, | ||
645 | "date": self.date, | ||
646 | "action": self.action, | ||
647 | "total": self.total.value, | ||
648 | "currency": self.currency, | ||
649 | "total_in_base": self.total_in_base.value, | ||
650 | "base_currency": self.base_currency | ||
651 | } | ||
652 | |||
590 | def __repr__(self): | 653 | def __repr__(self): |
591 | if self.fee_rate > 0: | 654 | if self.fee_rate > 0: |
592 | fee_rate = " fee: {}%".format(self.fee_rate * 100) | 655 | fee_rate = " fee: {}%".format(self.fee_rate * 100) |