diff options
Diffstat (limited to 'portfolio.py')
-rw-r--r-- | portfolio.py | 91 |
1 files changed, 61 insertions, 30 deletions
diff --git a/portfolio.py b/portfolio.py index 6d51989..acb61b2 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -4,9 +4,7 @@ from decimal import Decimal as D | |||
4 | # Put your poloniex api key in market.py | 4 | # Put your poloniex api key in market.py |
5 | from market import market | 5 | from market import market |
6 | 6 | ||
7 | # FIXME: Améliorer le bid/ask | 7 | debug = False |
8 | # FIXME: J'essayais d'utiliser plus de bitcoins que j'en avais à disposition | ||
9 | # FIXME: better compute moves to avoid rounding errors | ||
10 | 8 | ||
11 | class Portfolio: | 9 | class Portfolio: |
12 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" | 10 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" |
@@ -78,8 +76,6 @@ class Portfolio: | |||
78 | } | 76 | } |
79 | 77 | ||
80 | class Amount: | 78 | class Amount: |
81 | MAX_DIGITS = 18 | ||
82 | |||
83 | def __init__(self, currency, value, linked_to=None, ticker=None, rate=None): | 79 | def __init__(self, currency, value, linked_to=None, ticker=None, rate=None): |
84 | self.currency = currency | 80 | self.currency = currency |
85 | self.value = D(value) | 81 | self.value = D(value) |
@@ -202,7 +198,7 @@ class Balance: | |||
202 | for key in hash_: | 198 | for key in hash_: |
203 | if key in ["info", "free", "used", "total"]: | 199 | if key in ["info", "free", "used", "total"]: |
204 | continue | 200 | continue |
205 | if hash_[key]["total"] > 0: | 201 | if hash_[key]["total"] > 0 or key in cls.known_balances: |
206 | cls.known_balances[key] = cls.from_hash(key, hash_[key]) | 202 | cls.known_balances[key] = cls.from_hash(key, hash_[key]) |
207 | 203 | ||
208 | @classmethod | 204 | @classmethod |
@@ -222,14 +218,22 @@ class Balance: | |||
222 | return amounts | 218 | return amounts |
223 | 219 | ||
224 | @classmethod | 220 | @classmethod |
225 | def prepare_trades(cls, market, base_currency="BTC", compute_value=None): | 221 | def prepare_trades(cls, market, base_currency="BTC", compute_value="average"): |
226 | cls.fetch_balances(market) | 222 | cls.fetch_balances(market) |
227 | values_in_base = cls.in_currency(base_currency, market) | 223 | values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) |
228 | total_base_value = sum(values_in_base.values()) | 224 | total_base_value = sum(values_in_base.values()) |
229 | new_repartition = cls.dispatch_assets(total_base_value) | 225 | new_repartition = cls.dispatch_assets(total_base_value) |
230 | # Recompute it in case we have new currencies | 226 | # Recompute it in case we have new currencies |
231 | values_in_base = cls.in_currency(base_currency, market) | 227 | values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) |
232 | Trade.compute_trades(values_in_base, new_repartition, market=market, compute_value=compute_value) | 228 | Trade.compute_trades(values_in_base, new_repartition, market=market) |
229 | |||
230 | @classmethod | ||
231 | def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None): | ||
232 | cls.fetch_balances(market) | ||
233 | values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) | ||
234 | total_base_value = sum(values_in_base.values()) | ||
235 | new_repartition = cls.dispatch_assets(total_base_value) | ||
236 | Trade.compute_trades(values_in_base, new_repartition, only=only, market=market) | ||
233 | 237 | ||
234 | def __repr__(self): | 238 | def __repr__(self): |
235 | return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) | 239 | return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) |
@@ -302,21 +306,27 @@ class Trade: | |||
302 | return cls.get_ticker(c1, c2, market) | 306 | return cls.get_ticker(c1, c2, market) |
303 | 307 | ||
304 | @classmethod | 308 | @classmethod |
305 | def compute_trades(cls, values_in_base, new_repartition, market=None, compute_value=None): | 309 | def compute_trades(cls, values_in_base, new_repartition, only=None, market=None): |
306 | base_currency = sum(values_in_base.values()).currency | 310 | base_currency = sum(values_in_base.values()).currency |
307 | for currency in Balance.currencies(): | 311 | for currency in Balance.currencies(): |
308 | if currency == base_currency: | 312 | if currency == base_currency: |
309 | continue | 313 | continue |
310 | cls.trades[currency] = cls( | 314 | trade = cls( |
311 | values_in_base.get(currency, Amount(base_currency, 0)), | 315 | values_in_base.get(currency, Amount(base_currency, 0)), |
312 | new_repartition.get(currency, Amount(base_currency, 0)), | 316 | new_repartition.get(currency, Amount(base_currency, 0)), |
313 | currency, | 317 | currency, |
314 | market=market | 318 | market=market |
315 | ) | 319 | ) |
316 | if compute_value is not None: | 320 | if only is None or trade.action == only: |
317 | cls.trades[currency].prepare_order(compute_value=compute_value) | 321 | cls.trades[currency] = trade |
318 | return cls.trades | 322 | return cls.trades |
319 | 323 | ||
324 | @classmethod | ||
325 | def prepare_orders(cls, only=None, compute_value="default"): | ||
326 | for currency, trade in cls.trades.items(): | ||
327 | if only is None or trade.action == only: | ||
328 | trade.prepare_order(compute_value=compute_value) | ||
329 | |||
320 | @property | 330 | @property |
321 | def action(self): | 331 | def action(self): |
322 | if self.value_from == self.value_to: | 332 | if self.value_from == self.value_to: |
@@ -353,7 +363,7 @@ class Trade: | |||
353 | 363 | ||
354 | rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) | 364 | rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) |
355 | 365 | ||
356 | self.orders.append(Order(self.order_action(inverted), delta, rate, currency)) | 366 | self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.market)) |
357 | 367 | ||
358 | @classmethod | 368 | @classmethod |
359 | def compute_value(cls, ticker, action, compute_value="default"): | 369 | def compute_value(cls, ticker, action, compute_value="default"): |
@@ -362,22 +372,33 @@ class Trade: | |||
362 | return compute_value(ticker, action) | 372 | return compute_value(ticker, action) |
363 | 373 | ||
364 | @classmethod | 374 | @classmethod |
365 | def all_orders(cls): | 375 | def all_orders(cls, state=None): |
366 | return sum(map(lambda v: v.orders, cls.trades.values()), []) | 376 | all_orders = sum(map(lambda v: v.orders, cls.trades.values()), []) |
377 | if state is None: | ||
378 | return all_orders | ||
379 | else: | ||
380 | return list(filter(lambda o: o.status == state, all_orders)) | ||
381 | |||
382 | @classmethod | ||
383 | def run_orders(cls): | ||
384 | for order in cls.all_orders(state="pending"): | ||
385 | order.run() | ||
367 | 386 | ||
368 | @classmethod | 387 | @classmethod |
369 | def follow_orders(cls, market): | 388 | def follow_orders(cls, verbose=True, sleep=30): |
370 | orders = cls.all_orders() | 389 | orders = cls.all_orders() |
371 | finished_orders = [] | 390 | finished_orders = [] |
372 | while len(orders) != len(finished_orders): | 391 | while len(orders) != len(finished_orders): |
373 | time.sleep(30) | 392 | time.sleep(sleep) |
374 | for order in orders: | 393 | for order in orders: |
375 | if order in finished_orders: | 394 | if order in finished_orders: |
376 | continue | 395 | continue |
377 | if order.get_status(market) != "open": | 396 | if order.get_status() != "open": |
378 | finished_orders.append(order) | 397 | finished_orders.append(order) |
379 | print("finished {}".format(order)) | 398 | if verbose: |
380 | print("All orders finished") | 399 | print("finished {}".format(order)) |
400 | if verbose: | ||
401 | print("All orders finished") | ||
381 | 402 | ||
382 | def __repr__(self): | 403 | def __repr__(self): |
383 | return "Trade({} -> {} in {}, {})".format( | 404 | return "Trade({} -> {} in {}, {})".format( |
@@ -387,15 +408,16 @@ class Trade: | |||
387 | self.action) | 408 | self.action) |
388 | 409 | ||
389 | class Order: | 410 | class Order: |
390 | DEBUG = True | 411 | DEBUG = debug |
391 | 412 | ||
392 | def __init__(self, action, amount, rate, base_currency): | 413 | def __init__(self, action, amount, rate, base_currency, market): |
393 | self.action = action | 414 | self.action = action |
394 | self.amount = amount | 415 | self.amount = amount |
395 | self.rate = rate | 416 | self.rate = rate |
396 | self.base_currency = base_currency | 417 | self.base_currency = base_currency |
418 | self.market = market | ||
397 | self.result = None | 419 | self.result = None |
398 | self.status = "not run" | 420 | self.status = "pending" |
399 | 421 | ||
400 | def __repr__(self): | 422 | def __repr__(self): |
401 | return "Order({} {} at {} {} [{}])".format( | 423 | return "Order({} {} at {} {} [{}])".format( |
@@ -406,7 +428,15 @@ class Order: | |||
406 | self.status | 428 | self.status |
407 | ) | 429 | ) |
408 | 430 | ||
409 | def run(self, market): | 431 | @property |
432 | def pending(self): | ||
433 | return self.status == "pending" | ||
434 | |||
435 | @property | ||
436 | def finished(self): | ||
437 | return self.status == "closed" or self.status == "canceled" | ||
438 | |||
439 | def run(self): | ||
410 | symbol = "{}/{}".format(self.amount.currency, self.base_currency) | 440 | symbol = "{}/{}".format(self.amount.currency, self.base_currency) |
411 | amount = self.amount.value | 441 | amount = self.amount.value |
412 | 442 | ||
@@ -415,20 +445,21 @@ class Order: | |||
415 | symbol, self.action, amount, self.rate)) | 445 | symbol, self.action, amount, self.rate)) |
416 | else: | 446 | else: |
417 | try: | 447 | try: |
418 | self.result = market.create_order(symbol, 'limit', self.action, amount, price=self.rate) | 448 | self.result = self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate) |
419 | self.status = "open" | 449 | self.status = "open" |
420 | except Exception: | 450 | except Exception: |
421 | pass | 451 | pass |
422 | 452 | ||
423 | def get_status(self, market): | 453 | def get_status(self): |
424 | # other states are "closed" and "canceled" | 454 | # other states are "closed" and "canceled" |
425 | if self.status == "open": | 455 | if self.status == "open": |
426 | result = market.fetch_order(self.result['id']) | 456 | result = self.market.fetch_order(self.result['id']) |
427 | self.status = result["status"] | 457 | self.status = result["status"] |
428 | return self.status | 458 | return self.status |
429 | 459 | ||
430 | def print_orders(market, base_currency="BTC"): | 460 | def print_orders(market, base_currency="BTC"): |
431 | Balance.prepare_trades(market, base_currency=base_currency, compute_value="average") | 461 | Balance.prepare_trades(market, base_currency=base_currency, compute_value="average") |
462 | Trade.prepare_orders(compute_value="average") | ||
432 | for currency, balance in Balance.known_balances.items(): | 463 | for currency, balance in Balance.known_balances.items(): |
433 | print(balance) | 464 | print(balance) |
434 | for currency, trade in Trade.trades.items(): | 465 | for currency, trade in Trade.trades.items(): |
@@ -442,7 +473,7 @@ def make_orders(market, base_currency="BTC"): | |||
442 | print(trade) | 473 | print(trade) |
443 | for order in trade.orders: | 474 | for order in trade.orders: |
444 | print("\t", order, sep="") | 475 | print("\t", order, sep="") |
445 | order.run(market) | 476 | order.run() |
446 | 477 | ||
447 | if __name__ == '__main__': | 478 | if __name__ == '__main__': |
448 | print_orders(market) | 479 | print_orders(market) |