aboutsummaryrefslogtreecommitdiff
path: root/portfolio.py
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-24 19:53:58 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-24 19:53:58 +0100
commit3d0247f944d7510943dfaa64eeb0e15a43b6c989 (patch)
tree2695181c77e444d52bca5de47b6c99b9b6c1ed32 /portfolio.py
parentc31df868c655612b8387a25111e69882f0fe6344 (diff)
downloadTrader-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.py107
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
3from decimal import Decimal as D, ROUND_DOWN 3from 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
5from json import JSONDecodeError 5from json import JSONDecodeError
6from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError
6from ccxt import ExchangeError, ExchangeNotAvailable 7from ccxt import ExchangeError, ExchangeNotAvailable
7import requests 8import requests
8import helper as h 9import 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
218class Balance: 228class 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
431class Order: 465class 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)