diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-03 21:31:29 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-03 21:31:29 +0100 |
commit | 350ed24de673dc125be9e2fdecb0f1abc7835b41 (patch) | |
tree | c567dd8ebc773da6329d5a48cd4b23cbf831888f /portfolio.py | |
parent | ecba11139e357567c46f7ba2a0cf8dbd98266fe8 (diff) | |
download | Trader-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.py | 132 |
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 @@ | |||
1 | from ccxt import ExchangeError | 1 | from ccxt import ExchangeError |
2 | import time | 2 | import time |
3 | from decimal import Decimal as D | 3 | from 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 |
5 | from market import market | 5 | from 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 | |||
257 | class Trade: | 274 | class 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 | ||
469 | class Order: | 485 | class 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 | ||
532 | def make_orders(market, base_currency="BTC"): | 558 | def make_orders(market, base_currency="BTC"): |
533 | Balance.prepare_trades(market, base_currency=base_currency) | 559 | Balance.prepare_trades(market, base_currency=base_currency) |