aboutsummaryrefslogtreecommitdiff
path: root/portfolio.py
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-03 21:31:29 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-03 21:31:29 +0100
commit350ed24de673dc125be9e2fdecb0f1abc7835b41 (patch)
treec567dd8ebc773da6329d5a48cd4b23cbf831888f /portfolio.py
parentecba11139e357567c46f7ba2a0cf8dbd98266fe8 (diff)
downloadTrader-350ed24de673dc125be9e2fdecb0f1abc7835b41.tar.gz
Trader-350ed24de673dc125be9e2fdecb0f1abc7835b41.tar.zst
Trader-350ed24de673dc125be9e2fdecb0f1abc7835b41.zip
Work in progress to use shorts
Diffstat (limited to 'portfolio.py')
-rw-r--r--portfolio.py132
1 files changed, 79 insertions, 53 deletions
diff --git a/portfolio.py b/portfolio.py
index 3257bcf..d9d2d4d 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -1,6 +1,6 @@
1from ccxt import ExchangeError 1from ccxt import ExchangeError
2import time 2import time
3from decimal import Decimal as D 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 market import market 5from market import market
6 6
@@ -10,7 +10,7 @@ class Portfolio:
10 data = None 10 data = None
11 11
12 @classmethod 12 @classmethod
13 def repartition_pertenthousand(cls, liquidity="medium"): 13 def repartition(cls, liquidity="medium"):
14 cls.parse_cryptoportfolio() 14 cls.parse_cryptoportfolio()
15 liquidities = cls.liquidities[liquidity] 15 liquidities = cls.liquidities[liquidity]
16 cls.last_date = sorted(liquidities.keys())[-1] 16 cls.last_date = sorted(liquidities.keys())[-1]
@@ -40,7 +40,7 @@ class Portfolio:
40 cls.get_cryptoportfolio() 40 cls.get_cryptoportfolio()
41 41
42 def filter_weights(weight_hash): 42 def filter_weights(weight_hash):
43 if weight_hash[1] == 0: 43 if weight_hash[1][0] == 0:
44 return False 44 return False
45 if weight_hash[0] == "_row": 45 if weight_hash[0] == "_row":
46 return False 46 return False
@@ -48,15 +48,13 @@ class Portfolio:
48 48
49 def clean_weights(i): 49 def clean_weights(i):
50 def clean_weights_(h): 50 def clean_weights_(h):
51 if isinstance(h[1][i], str): 51 if h[0].endswith("s"):
52 return [h[0], h[1][i]] 52 return [h[0][0:-1], (h[1][i], "short")]
53 else: 53 else:
54 return [h[0], int(h[1][i] * 10000)] 54 return [h[0], (h[1][i], "long")]
55 return clean_weights_ 55 return clean_weights_
56 56
57 def parse_weights(portfolio_hash): 57 def parse_weights(portfolio_hash):
58 # FIXME: we'll need shorts at some point
59 assert all(map(lambda x: x == "long", portfolio_hash["holding"]["direction"]))
60 weights_hash = portfolio_hash["weights"] 58 weights_hash = portfolio_hash["weights"]
61 weights = {} 59 weights = {}
62 for i in range(len(weights_hash["_row"])): 60 for i in range(len(weights_hash["_row"])):
@@ -105,6 +103,9 @@ class Amount:
105 else: 103 else:
106 raise Exception("This asset is not available in the chosen market") 104 raise Exception("This asset is not available in the chosen market")
107 105
106 def __round__(self, n=8):
107 return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN))
108
108 def __abs__(self): 109 def __abs__(self):
109 return Amount(self.currency, abs(self.value)) 110 return Amount(self.currency, abs(self.value))
110 111
@@ -196,35 +197,50 @@ class Balance:
196 for key in hash_: 197 for key in hash_:
197 if key in ["info", "free", "used", "total"]: 198 if key in ["info", "free", "used", "total"]:
198 continue 199 continue
199 if hash_[key]["total"] > 0 or key in cls.known_balances: 200 if hash_[key]["total"] != 0 or key in cls.known_balances:
200 cls.known_balances[key] = cls.from_hash(key, hash_[key]) 201 cls.known_balances[key] = cls.from_hash(key, hash_[key])
201 202
202 @classmethod 203 @classmethod
203 def fetch_balances(cls, market): 204 def fetch_balances(cls, market):
204 cls._fill_balances(market.fetch_balance()) 205 cls._fill_balances(market.fetch_balance())
205 return cls.known_balances 206 return cls.known_balances
207 # FIXME:Separate balances per trade type and in position
208 # Need to check how balances in position are represented
209
206 210
207 @classmethod 211 @classmethod
208 def dispatch_assets(cls, amount, repartition=None): 212 def dispatch_assets(cls, amount, repartition=None):
209 if repartition is None: 213 if repartition is None:
210 repartition = Portfolio.repartition_pertenthousand() 214 repartition = Portfolio.repartition()
211 sum_pertenthousand = sum([v for k, v in repartition.items()]) 215 sum_ratio = sum([v[0] for k, v in repartition.items()])
212 amounts = {} 216 amounts = {}
213 for currency, ptt in repartition.items(): 217 for currency, (ptt, trade_type) in repartition.items():
214 amounts[currency] = ptt * amount / sum_pertenthousand 218 amounts[currency] = ptt * amount / sum_ratio
215 if currency not in cls.known_balances: 219 if currency not in cls.known_balances:
216 cls.known_balances[currency] = cls(currency, 0, 0, 0) 220 cls.known_balances[currency] = cls(currency, 0, 0, 0)
217 return amounts 221 return amounts
218 222
219 @classmethod 223 @classmethod
224 def dispatch_trade_types(cls, repartition=None):
225 if repartition is None:
226 repartition = Portfolio.repartition()
227 trade_types = {}
228 for currency, (ptt, trade_type) in repartition.items():
229 trade_types[currency] = trade_type
230 return trade_types
231 # FIXME: once we know the repartition and sold everything, we can move
232 # the necessary part to the margin account
233
234 @classmethod
220 def prepare_trades(cls, market, base_currency="BTC", compute_value="average"): 235 def prepare_trades(cls, market, base_currency="BTC", compute_value="average"):
221 cls.fetch_balances(market) 236 cls.fetch_balances(market)
222 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) 237 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
223 total_base_value = sum(values_in_base.values()) 238 total_base_value = sum(values_in_base.values())
224 new_repartition = cls.dispatch_assets(total_base_value) 239 new_repartition = cls.dispatch_assets(total_base_value)
240 trade_types = cls.dispatch_trade_types()
225 # Recompute it in case we have new currencies 241 # Recompute it in case we have new currencies
226 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) 242 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
227 Trade.compute_trades(values_in_base, new_repartition, market=market) 243 Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market)
228 244
229 @classmethod 245 @classmethod
230 def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None): 246 def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None):
@@ -232,15 +248,17 @@ class Balance:
232 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) 248 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
233 total_base_value = sum(values_in_base.values()) 249 total_base_value = sum(values_in_base.values())
234 new_repartition = cls.dispatch_assets(total_base_value) 250 new_repartition = cls.dispatch_assets(total_base_value)
235 Trade.compute_trades(values_in_base, new_repartition, only=only, market=market) 251 trade_types = cls.dispatch_trade_types()
252 Trade.compute_trades(values_in_base, new_repartition, trade_types, only=only, market=market)
236 253
237 @classmethod 254 @classmethod
238 def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average"): 255 def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average"):
239 cls.fetch_balances(market) 256 cls.fetch_balances(market)
240 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) 257 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
241 total_base_value = sum(values_in_base.values()) 258 total_base_value = sum(values_in_base.values())
242 new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: 1 }) 259 new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
243 Trade.compute_trades(values_in_base, new_repartition, market=market) 260 trade_types = cls.dispatch_trade_types()
261 Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market)
244 262
245 def __repr__(self): 263 def __repr__(self):
246 return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) 264 return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total))
@@ -253,16 +271,16 @@ class Computation:
253 "ask": lambda x, y: x["ask"], 271 "ask": lambda x, y: x["ask"],
254 } 272 }
255 273
256
257class Trade: 274class Trade:
258 trades = {} 275 trades = {}
259 276
260 def __init__(self, value_from, value_to, currency, market=None): 277 def __init__(self, value_from, value_to, currency, trade_type, market=None):
261 # We have value_from of currency, and want to finish with value_to of 278 # We have value_from of currency, and want to finish with value_to of
262 # that currency. value_* may not be in currency's terms 279 # that currency. value_* may not be in currency's terms
263 self.currency = currency 280 self.currency = currency
264 self.value_from = value_from 281 self.value_from = value_from
265 self.value_to = value_to 282 self.value_to = value_to
283 self.trade_type = trade_type
266 self.orders = [] 284 self.orders = []
267 self.market = market 285 self.market = market
268 assert self.value_from.currency == self.value_to.currency 286 assert self.value_from.currency == self.value_to.currency
@@ -313,7 +331,7 @@ class Trade:
313 return cls.get_ticker(c1, c2, market) 331 return cls.get_ticker(c1, c2, market)
314 332
315 @classmethod 333 @classmethod
316 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None): 334 def compute_trades(cls, values_in_base, new_repartition, trade_types, only=None, market=None):
317 base_currency = sum(values_in_base.values()).currency 335 base_currency = sum(values_in_base.values()).currency
318 for currency in Balance.currencies(): 336 for currency in Balance.currencies():
319 if currency == base_currency: 337 if currency == base_currency:
@@ -322,6 +340,7 @@ class Trade:
322 values_in_base.get(currency, Amount(base_currency, 0)), 340 values_in_base.get(currency, Amount(base_currency, 0)),
323 new_repartition.get(currency, Amount(base_currency, 0)), 341 new_repartition.get(currency, Amount(base_currency, 0)),
324 currency, 342 currency,
343 trade_types.get(currency, "long"),
325 market=market 344 market=market
326 ) 345 )
327 if only is None or trade.action == only: 346 if only is None or trade.action == only:
@@ -347,25 +366,30 @@ class Trade:
347 return "sell" 366 return "sell"
348 367
349 def order_action(self, inverted): 368 def order_action(self, inverted):
350 if self.value_from < self.value_to: 369 # a xor b xor c
351 return "buy" if not inverted else "sell" 370 if (self.trade_type == "short") != ((self.value_from < self.value_to) != inverted):
371 return "buy"
352 else: 372 else:
353 return "sell" if not inverted else "buy" 373 return "sell"
354 374
355 def prepare_order(self, compute_value="default"): 375 def prepare_order(self, compute_value="default"):
356 if self.action is None: 376 if self.action is None:
357 return 377 return
358 ticker = self.value_from.ticker 378 ticker = Trade.get_ticker(self.currency, self.base_currency, self.market)
359 inverted = ticker["inverted"] 379 inverted = ticker["inverted"]
360 if inverted: 380 if inverted:
361 ticker = ticker["original"] 381 ticker = ticker["original"]
362 rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) 382 rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
363 # 0.1 383 # 0.1
364 384
385 # FIXME: optimize if value_to == 0 or value_from == 0?)
386
365 delta_in_base = abs(self.value_from - self.value_to) 387 delta_in_base = abs(self.value_from - self.value_to)
366 # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) 388 # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)
367 389
368 if not inverted: 390 if not inverted:
391 currency = self.base_currency
392 # BTC
369 if self.action == "sell": 393 if self.action == "sell":
370 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it 394 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
371 # At rate 1 Foo = 0.1 BTC 395 # At rate 1 Foo = 0.1 BTC
@@ -382,35 +406,26 @@ class Trade:
382 delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate) 406 delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate)
383 # I want to buy 9 / 0.1 FOO 407 # I want to buy 9 / 0.1 FOO
384 # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market" 408 # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market"
385
386 # FIXME: Need to round up to the correct amount of FOO in case
387 # we want to use all BTC
388 currency = self.base_currency
389 # BTC
390 else: 409 else:
391 if self.action == "sell":
392 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
393 # At rate 1 Foo = 0.1 BTC
394 delta = delta_in_base
395 # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market
396
397 # FIXME: Need to round up to the correct amount of FOO in case
398 # we want to sell all
399 else:
400 delta = delta_in_base
401 # I want to buy 9 / 0.1 FOO
402 # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market"
403
404 # FIXME: Need to round up to the correct amount of FOO in case
405 # we want to use all BTC
406
407 currency = self.currency 410 currency = self.currency
408 # FOO 411 # FOO
412 delta = delta_in_base
413 # sell:
414 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
415 # At rate 1 Foo = 0.1 BTC
416 # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market
417 # buy:
418 # I want to buy 9 / 0.1 FOO
419 # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market"
409 420
410 self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.market)) 421 self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.trade_type, self.market))
411 422
412 @classmethod 423 @classmethod
413 def compute_value(cls, ticker, action, compute_value="default"): 424 def compute_value(cls, ticker, action, compute_value="default"):
425 if action == "buy":
426 action = "ask"
427 if action == "sell":
428 action = "bid"
414 if isinstance(compute_value, str): 429 if isinstance(compute_value, str):
415 compute_value = Computation.computations[compute_value] 430 compute_value = Computation.computations[compute_value]
416 return compute_value(ticker, action) 431 return compute_value(ticker, action)
@@ -450,11 +465,12 @@ class Trade:
450 order.get_status() 465 order.get_status()
451 466
452 def __repr__(self): 467 def __repr__(self):
453 return "Trade({} -> {} in {}, {})".format( 468 return "Trade({} -> {} in {}, {} {})".format(
454 self.value_from, 469 self.value_from,
455 self.value_to, 470 self.value_to,
456 self.currency, 471 self.currency,
457 self.action) 472 self.action,
473 self.trade_type)
458 474
459 @classmethod 475 @classmethod
460 def print_all_with_order(cls): 476 def print_all_with_order(cls):
@@ -467,19 +483,20 @@ class Trade:
467 print("\t", order, sep="") 483 print("\t", order, sep="")
468 484
469class Order: 485class Order:
470 def __init__(self, action, amount, rate, base_currency, market, account="exchange"): 486 def __init__(self, action, amount, rate, base_currency, trade_type, market):
471 self.action = action 487 self.action = action
472 self.amount = amount 488 self.amount = amount
473 self.rate = rate 489 self.rate = rate
474 self.base_currency = base_currency 490 self.base_currency = base_currency
475 self.market = market 491 self.market = market
476 self.account = account 492 self.trade_type = trade_type
477 self.result = None 493 self.result = None
478 self.status = "pending" 494 self.status = "pending"
479 495
480 def __repr__(self): 496 def __repr__(self):
481 return "Order({} {} at {} {} [{}])".format( 497 return "Order({} {} {} at {} {} [{}])".format(
482 self.action, 498 self.action,
499 self.trade_type,
483 self.amount, 500 self.amount,
484 self.rate, 501 self.rate,
485 self.base_currency, 502 self.base_currency,
@@ -487,6 +504,13 @@ class Order:
487 ) 504 )
488 505
489 @property 506 @property
507 def account(self):
508 if self.trade_type == "long":
509 return "exchange"
510 else:
511 return "margin"
512
513 @property
490 def pending(self): 514 def pending(self):
491 return self.status == "pending" 515 return self.status == "pending"
492 516
@@ -496,13 +520,15 @@ class Order:
496 520
497 def run(self, debug=False): 521 def run(self, debug=False):
498 symbol = "{}/{}".format(self.amount.currency, self.base_currency) 522 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
499 amount = self.amount.value 523 amount = round(self.amount, self.market.order_precision(symbol)).value
500 524
501 if debug: 525 if debug:
502 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 526 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
503 symbol, self.action, amount, self.rate, self.account)) 527 symbol, self.action, amount, self.rate, self.account))
504 else: 528 else:
505 try: 529 try:
530 if self.action == "sell" and self.trade_type == "short":
531 assert self.market.transfer_balance(self.base_currency, amount * self.rate, "exchange", "margin")
506 self.result = self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account) 532 self.result = self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)
507 self.status = "open" 533 self.status = "open"
508 except Exception as e: 534 except Exception as e:
@@ -527,7 +553,7 @@ def print_orders(market, base_currency="BTC"):
527 Trade.prepare_orders(compute_value="average") 553 Trade.prepare_orders(compute_value="average")
528 for currency, balance in Balance.known_balances.items(): 554 for currency, balance in Balance.known_balances.items():
529 print(balance) 555 print(balance)
530 portfolio.Trade.print_all_with_order() 556 Trade.print_all_with_order()
531 557
532def make_orders(market, base_currency="BTC"): 558def make_orders(market, base_currency="BTC"):
533 Balance.prepare_trades(market, base_currency=base_currency) 559 Balance.prepare_trades(market, base_currency=base_currency)