diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-01 13:14:41 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-01 13:14:41 +0100 |
commit | aca4d4372553110ab5d76740ff536de83d5617b2 (patch) | |
tree | a9bfdca4226daf422273da97a9e139721469c9f1 /portfolio.py | |
parent | 2033e7fef780298be2ec15455a0ec1d26515de55 (diff) | |
download | Trader-aca4d4372553110ab5d76740ff536de83d5617b2.tar.gz Trader-aca4d4372553110ab5d76740ff536de83d5617b2.tar.zst Trader-aca4d4372553110ab5d76740ff536de83d5617b2.zip |
Various fixes/improvementsv0.3
- Use pending gains to compute the move_balance
- Use ttl_cache for tickers
Diffstat (limited to 'portfolio.py')
-rw-r--r-- | portfolio.py | 99 |
1 files changed, 52 insertions, 47 deletions
diff --git a/portfolio.py b/portfolio.py index eb3390e..b77850b 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -3,7 +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 | from json import JSONDecodeError | 4 | from json import JSONDecodeError |
5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | 5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError |
6 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder | 6 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached |
7 | from retry import retry | 7 | from retry import retry |
8 | import requests | 8 | import requests |
9 | 9 | ||
@@ -226,8 +226,8 @@ class Amount: | |||
226 | 226 | ||
227 | class Balance: | 227 | class Balance: |
228 | base_keys = ["total", "exchange_total", "exchange_used", | 228 | base_keys = ["total", "exchange_total", "exchange_used", |
229 | "exchange_free", "margin_total", "margin_borrowed", | 229 | "exchange_free", "margin_total", "margin_in_position", |
230 | "margin_free"] | 230 | "margin_available", "margin_borrowed", "margin_pending_gain"] |
231 | 231 | ||
232 | def __init__(self, currency, hash_): | 232 | def __init__(self, currency, hash_): |
233 | self.currency = currency | 233 | self.currency = currency |
@@ -240,8 +240,8 @@ class Balance: | |||
240 | base_currency = hash_["margin_borrowed_base_currency"] | 240 | base_currency = hash_["margin_borrowed_base_currency"] |
241 | for key in [ | 241 | for key in [ |
242 | "margin_liquidation_price", | 242 | "margin_liquidation_price", |
243 | "margin_pending_gain", | ||
244 | "margin_lending_fees", | 243 | "margin_lending_fees", |
244 | "margin_pending_base_gain", | ||
245 | "margin_borrowed_base_price" | 245 | "margin_borrowed_base_price" |
246 | ]: | 246 | ]: |
247 | setattr(self, key, Amount(base_currency, hash_.get(key, 0))) | 247 | setattr(self, key, Amount(base_currency, hash_.get(key, 0))) |
@@ -261,12 +261,12 @@ class Balance: | |||
261 | exchange = "" | 261 | exchange = "" |
262 | 262 | ||
263 | if self.margin_total > 0: | 263 | if self.margin_total > 0: |
264 | if self.margin_free != 0 and self.margin_borrowed != 0: | 264 | if self.margin_available != 0 and self.margin_in_position != 0: |
265 | margin = " Margin: [✔{} + borrowed {} = {}]".format(str(self.margin_free), str(self.margin_borrowed), str(self.margin_total)) | 265 | margin = " Margin: [✔{} + ❌{} = {}]".format(str(self.margin_available), str(self.margin_in_position), str(self.margin_total)) |
266 | elif self.margin_free != 0: | 266 | elif self.margin_available != 0: |
267 | margin = " Margin: [✔{}]".format(str(self.margin_free)) | 267 | margin = " Margin: [✔{}]".format(str(self.margin_available)) |
268 | else: | 268 | else: |
269 | margin = " Margin: [borrowed {}]".format(str(self.margin_borrowed)) | 269 | margin = " Margin: [❌{}]".format(str(self.margin_in_position)) |
270 | elif self.margin_total < 0: | 270 | elif self.margin_total < 0: |
271 | margin = " Margin: [{} @@ {}/{}]".format(str(self.margin_total), | 271 | margin = " Margin: [{} @@ {}/{}]".format(str(self.margin_total), |
272 | str(self.margin_borrowed_base_price), | 272 | str(self.margin_borrowed_base_price), |
@@ -290,6 +290,7 @@ class Trade: | |||
290 | self.value_to = value_to | 290 | self.value_to = value_to |
291 | self.orders = [] | 291 | self.orders = [] |
292 | self.market = market | 292 | self.market = market |
293 | assert self.value_from.value * self.value_to.value >= 0 | ||
293 | assert self.value_from.currency == self.value_to.currency | 294 | assert self.value_from.currency == self.value_to.currency |
294 | if self.value_from != 0: | 295 | if self.value_from != 0: |
295 | assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency | 296 | assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency |
@@ -298,6 +299,10 @@ class Trade: | |||
298 | self.base_currency = self.value_from.currency | 299 | self.base_currency = self.value_from.currency |
299 | 300 | ||
300 | @property | 301 | @property |
302 | def delta(self): | ||
303 | return self.value_to - self.value_from | ||
304 | |||
305 | @property | ||
301 | def action(self): | 306 | def action(self): |
302 | if self.value_from == self.value_to: | 307 | if self.value_from == self.value_to: |
303 | return None | 308 | return None |
@@ -322,6 +327,10 @@ class Trade: | |||
322 | else: | 327 | else: |
323 | return "long" | 328 | return "long" |
324 | 329 | ||
330 | @property | ||
331 | def is_fullfiled(self): | ||
332 | return abs(self.filled_amount(in_base_currency=True)) >= abs(self.delta) | ||
333 | |||
325 | def filled_amount(self, in_base_currency=False): | 334 | def filled_amount(self, in_base_currency=False): |
326 | filled_amount = 0 | 335 | filled_amount = 0 |
327 | for order in self.orders: | 336 | for order in self.orders: |
@@ -329,34 +338,36 @@ class Trade: | |||
329 | return filled_amount | 338 | return filled_amount |
330 | 339 | ||
331 | def update_order(self, order, tick): | 340 | def update_order(self, order, tick): |
332 | new_order = None | 341 | actions = { |
333 | if tick in [0, 1, 3, 4, 6]: | 342 | 0: ["waiting", None], |
343 | 1: ["waiting", None], | ||
344 | 2: ["adjusting", lambda x, y: (x[y] + x["average"]) / 2], | ||
345 | 3: ["waiting", None], | ||
346 | 4: ["waiting", None], | ||
347 | 5: ["adjusting", lambda x, y: (x[y]*2 + x["average"]) / 3], | ||
348 | 6: ["waiting", None], | ||
349 | 7: ["market_fallback", "default"], | ||
350 | } | ||
351 | |||
352 | if tick in actions: | ||
353 | update, compute_value = actions[tick] | ||
354 | elif tick % 3 == 1: | ||
355 | update = "market_adjust" | ||
356 | compute_value = "default" | ||
357 | else: | ||
334 | update = "waiting" | 358 | update = "waiting" |
335 | compute_value = None | 359 | compute_value = None |
336 | elif tick == 2: | 360 | |
337 | update = "adjusting" | 361 | if compute_value is not None: |
338 | compute_value = 'lambda x, y: (x[y] + x["average"]) / 2' | 362 | order.cancel() |
339 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) | 363 | new_order = self.prepare_order(compute_value=compute_value) |
340 | elif tick ==5: | 364 | else: |
341 | update = "adjusting" | 365 | new_order = None |
342 | compute_value = 'lambda x, y: (x[y]*2 + x["average"]) / 3' | ||
343 | new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) | ||
344 | elif tick >= 7: | ||
345 | if (tick - 7) % 3 == 0: | ||
346 | new_order = self.prepare_order(compute_value="default") | ||
347 | update = "market_adjust" | ||
348 | compute_value = "default" | ||
349 | else: | ||
350 | update = "waiting" | ||
351 | compute_value = None | ||
352 | if tick == 7: | ||
353 | update = "market_fallback" | ||
354 | 366 | ||
355 | self.market.report.log_order(order, tick, update=update, | 367 | self.market.report.log_order(order, tick, update=update, |
356 | compute_value=compute_value, new_order=new_order) | 368 | compute_value=compute_value, new_order=new_order) |
357 | 369 | ||
358 | if new_order is not None: | 370 | if new_order is not None: |
359 | order.cancel() | ||
360 | new_order.run() | 371 | new_order.run() |
361 | self.market.report.log_order(order, tick, new_order=new_order) | 372 | self.market.report.log_order(order, tick, new_order=new_order) |
362 | 373 | ||
@@ -369,10 +380,9 @@ class Trade: | |||
369 | ticker = ticker["original"] | 380 | ticker = ticker["original"] |
370 | rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) | 381 | rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) |
371 | 382 | ||
372 | #TODO: store when the order is considered filled | ||
373 | # FIXME: Dust amount should be removed from there if they werent | 383 | # FIXME: Dust amount should be removed from there if they werent |
374 | # honored in other sales | 384 | # honored in other sales |
375 | delta_in_base = abs(self.value_from - self.value_to) | 385 | delta_in_base = abs(self.delta) |
376 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) | 386 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) |
377 | 387 | ||
378 | if not inverted: | 388 | if not inverted: |
@@ -477,7 +487,6 @@ class Order: | |||
477 | self.trade = trade | 487 | self.trade = trade |
478 | self.close_if_possible = close_if_possible | 488 | self.close_if_possible = close_if_possible |
479 | self.id = None | 489 | self.id = None |
480 | self.fetch_cache_timestamp = None | ||
481 | self.tries = 0 | 490 | self.tries = 0 |
482 | 491 | ||
483 | def as_json(self): | 492 | def as_json(self): |
@@ -578,21 +587,19 @@ class Order: | |||
578 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: | 587 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: |
579 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) | 588 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) |
580 | 589 | ||
581 | def fetch(self, force=False): | 590 | def fetch(self): |
582 | if self.market.debug: | 591 | if self.market.debug: |
583 | self.market.report.log_debug_action("Fetching {}".format(self)) | 592 | self.market.report.log_debug_action("Fetching {}".format(self)) |
584 | return | 593 | return |
585 | if (not force and self.fetch_cache_timestamp is not None | 594 | try: |
586 | and time.time() - self.fetch_cache_timestamp < 10): | 595 | result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency) |
587 | return | 596 | self.results.append(result) |
588 | self.fetch_cache_timestamp = time.time() | 597 | self.status = result["status"] |
589 | 598 | # Time at which the order started | |
590 | result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency) | 599 | self.timestamp = result["datetime"] |
591 | self.results.append(result) | 600 | except OrderNotCached: |
601 | self.status = "closed_unknown" | ||
592 | 602 | ||
593 | self.status = result["status"] | ||
594 | # Time at which the order started | ||
595 | self.timestamp = result["datetime"] | ||
596 | self.fetch_mouvements() | 603 | self.fetch_mouvements() |
597 | 604 | ||
598 | # FIXME: consider open order with dust remaining as closed | 605 | # FIXME: consider open order with dust remaining as closed |
@@ -601,8 +608,6 @@ class Order: | |||
601 | return self.remaining_amount() < Amount(self.amount.currency, D("0.001")) | 608 | return self.remaining_amount() < Amount(self.amount.currency, D("0.001")) |
602 | 609 | ||
603 | def remaining_amount(self): | 610 | def remaining_amount(self): |
604 | if self.status == "open": | ||
605 | self.fetch() | ||
606 | return self.amount - self.filled_amount() | 611 | return self.amount - self.filled_amount() |
607 | 612 | ||
608 | def filled_amount(self, in_base_currency=False): | 613 | def filled_amount(self, in_base_currency=False): |
@@ -633,7 +638,7 @@ class Order: | |||
633 | self.status = "canceled" | 638 | self.status = "canceled" |
634 | return | 639 | return |
635 | self.market.ccxt.cancel_order(self.id) | 640 | self.market.ccxt.cancel_order(self.id) |
636 | self.fetch(force=True) | 641 | self.fetch() |
637 | 642 | ||
638 | class Mouvement: | 643 | class Mouvement: |
639 | def __init__(self, currency, base_currency, hash_): | 644 | def __init__(self, currency, base_currency, hash_): |