aboutsummaryrefslogtreecommitdiff
path: root/portfolio.py
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-01-21 20:46:39 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-01-21 21:41:26 +0100
commita9950fd073198f3c9dc938fd731d97c9821a3845 (patch)
treec613cdeb603e186c702f1dc4679ad6d0d9f5a7d0 /portfolio.py
parentc2644ba8db6e3890458af6a244aa3217e2ac4797 (diff)
downloadTrader-a9950fd073198f3c9dc938fd731d97c9821a3845.tar.gz
Trader-a9950fd073198f3c9dc938fd731d97c9821a3845.tar.zst
Trader-a9950fd073198f3c9dc938fd731d97c9821a3845.zip
Add some tests and cleanup exchange process
- Acceptance test for the whole exchange process - Cut exchange two steps: - Compute the outcome of the exchange - Do all the sells - Recompute the buys according to the sells result - Do all the buys
Diffstat (limited to 'portfolio.py')
-rw-r--r--portfolio.py91
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
5from market import market 5from market import market
6 6
7# FIXME: Améliorer le bid/ask 7debug = 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
11class Portfolio: 9class 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
80class Amount: 78class 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
389class Order: 410class 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
430def print_orders(market, base_currency="BTC"): 460def 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
447if __name__ == '__main__': 478if __name__ == '__main__':
448 print_orders(market) 479 print_orders(market)