]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - portfolio.py
Implement order following
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / portfolio.py
1 from ccxt import ExchangeError
2 import time
3 from decimal import Decimal as D, ROUND_DOWN
4 # Put your poloniex api key in market.py
5 from market import market
6 from json import JSONDecodeError
7 import requests
8
9 # FIXME: correctly handle web call timeouts
10
11
12 class Portfolio:
13 URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json"
14 liquidities = {}
15 data = None
16
17 @classmethod
18 def repartition(cls, liquidity="medium"):
19 cls.parse_cryptoportfolio()
20 liquidities = cls.liquidities[liquidity]
21 cls.last_date = sorted(liquidities.keys())[-1]
22 return liquidities[cls.last_date]
23
24 @classmethod
25 def get_cryptoportfolio(cls):
26 try:
27 r = requests.get(cls.URL)
28 except Exception:
29 return
30 try:
31 cls.data = r.json(parse_int=D, parse_float=D)
32 except JSONDecodeError:
33 cls.data = None
34
35 @classmethod
36 def parse_cryptoportfolio(cls):
37 if cls.data is None:
38 cls.get_cryptoportfolio()
39
40 def filter_weights(weight_hash):
41 if weight_hash[1][0] == 0:
42 return False
43 if weight_hash[0] == "_row":
44 return False
45 return True
46
47 def clean_weights(i):
48 def clean_weights_(h):
49 if h[0].endswith("s"):
50 return [h[0][0:-1], (h[1][i], "short")]
51 else:
52 return [h[0], (h[1][i], "long")]
53 return clean_weights_
54
55 def parse_weights(portfolio_hash):
56 weights_hash = portfolio_hash["weights"]
57 weights = {}
58 for i in range(len(weights_hash["_row"])):
59 weights[weights_hash["_row"][i]] = dict(filter(
60 filter_weights,
61 map(clean_weights(i), weights_hash.items())))
62 return weights
63
64 high_liquidity = parse_weights(cls.data["portfolio_1"])
65 medium_liquidity = parse_weights(cls.data["portfolio_2"])
66
67 cls.liquidities = {
68 "medium": medium_liquidity,
69 "high": high_liquidity,
70 }
71
72 class Amount:
73 def __init__(self, currency, value, linked_to=None, ticker=None, rate=None):
74 self.currency = currency
75 self.value = D(value)
76 self.linked_to = linked_to
77 self.ticker = ticker
78 self.rate = rate
79
80 def in_currency(self, other_currency, market, rate=None, action=None, compute_value="average"):
81 if other_currency == self.currency:
82 return self
83 if rate is not None:
84 return Amount(
85 other_currency,
86 self.value * rate,
87 linked_to=self,
88 rate=rate)
89 asset_ticker = Trade.get_ticker(self.currency, other_currency, market)
90 if asset_ticker is not None:
91 rate = Trade.compute_value(asset_ticker, action, compute_value=compute_value)
92 return Amount(
93 other_currency,
94 self.value * rate,
95 linked_to=self,
96 ticker=asset_ticker,
97 rate=rate)
98 else:
99 raise Exception("This asset is not available in the chosen market")
100
101 def __round__(self, n=8):
102 return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN))
103
104 def __abs__(self):
105 return Amount(self.currency, abs(self.value))
106
107 def __add__(self, other):
108 if other.currency != self.currency and other.value * self.value != 0:
109 raise Exception("Summing amounts must be done with same currencies")
110 return Amount(self.currency, self.value + other.value)
111
112 def __radd__(self, other):
113 if other == 0:
114 return self
115 else:
116 return self.__add__(other)
117
118 def __sub__(self, other):
119 if other.currency != self.currency and other.value * self.value != 0:
120 raise Exception("Summing amounts must be done with same currencies")
121 return Amount(self.currency, self.value - other.value)
122
123 def __mul__(self, value):
124 if not isinstance(value, (int, float, D)):
125 raise TypeError("Amount may only be multiplied by numbers")
126 return Amount(self.currency, self.value * value)
127
128 def __rmul__(self, value):
129 return self.__mul__(value)
130
131 def __floordiv__(self, value):
132 if not isinstance(value, (int, float, D)):
133 raise TypeError("Amount may only be multiplied by integers")
134 return Amount(self.currency, self.value / value)
135
136 def __truediv__(self, value):
137 return self.__floordiv__(value)
138
139 def __lt__(self, other):
140 if other == 0:
141 return self.value < 0
142 if self.currency != other.currency:
143 raise Exception("Comparing amounts must be done with same currencies")
144 return self.value < other.value
145
146 def __le__(self, other):
147 return self == other or self < other
148
149 def __gt__(self, other):
150 return not self <= other
151
152 def __ge__(self, other):
153 return not self < other
154
155 def __eq__(self, other):
156 if other == 0:
157 return self.value == 0
158 if self.currency != other.currency:
159 raise Exception("Comparing amounts must be done with same currencies")
160 return self.value == other.value
161
162 def __ne__(self, other):
163 return not self == other
164
165 def __neg__(self):
166 return Amount(self.currency, - self.value)
167
168 def __str__(self):
169 if self.linked_to is None:
170 return "{:.8f} {}".format(self.value, self.currency)
171 else:
172 return "{:.8f} {} [{}]".format(self.value, self.currency, self.linked_to)
173
174 def __repr__(self):
175 if self.linked_to is None:
176 return "Amount({:.8f} {})".format(self.value, self.currency)
177 else:
178 return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to))
179
180 class Balance:
181 known_balances = {}
182
183 def __init__(self, currency, hash_):
184 self.currency = currency
185 for key in ["total",
186 "exchange_total", "exchange_used", "exchange_free",
187 "margin_total", "margin_borrowed", "margin_free"]:
188 setattr(self, key, Amount(currency, hash_.get(key, 0)))
189
190 self.margin_position_type = hash_.get("margin_position_type")
191
192 if hash_.get("margin_borrowed_base_currency") is not None:
193 base_currency = hash_["margin_borrowed_base_currency"]
194 for key in [
195 "margin_liquidation_price",
196 "margin_pending_gain",
197 "margin_lending_fees",
198 "margin_borrowed_base_price"
199 ]:
200 setattr(self, key, Amount(base_currency, hash_[key]))
201
202 @classmethod
203 def in_currency(cls, other_currency, market, compute_value="average", type="total"):
204 amounts = {}
205 for currency in cls.known_balances:
206 balance = cls.known_balances[currency]
207 other_currency_amount = getattr(balance, type)\
208 .in_currency(other_currency, market, compute_value=compute_value)
209 amounts[currency] = other_currency_amount
210 return amounts
211
212 @classmethod
213 def currencies(cls):
214 return cls.known_balances.keys()
215
216 @classmethod
217 def fetch_balances(cls, market):
218 all_balances = market.fetch_all_balances()
219 for currency, balance in all_balances.items():
220 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
221 currency in cls.known_balances:
222 cls.known_balances[currency] = cls(currency, balance)
223 return cls.known_balances
224
225 @classmethod
226 def dispatch_assets(cls, amount, repartition=None):
227 if repartition is None:
228 repartition = Portfolio.repartition()
229 sum_ratio = sum([v[0] for k, v in repartition.items()])
230 amounts = {}
231 for currency, (ptt, trade_type) in repartition.items():
232 amounts[currency] = ptt * amount / sum_ratio
233 if trade_type == "short":
234 amounts[currency] = - amounts[currency]
235 if currency not in cls.known_balances:
236 cls.known_balances[currency] = cls(currency, {})
237 return amounts
238
239 @classmethod
240 def prepare_trades(cls, market, base_currency="BTC", compute_value="average", debug=False):
241 cls.fetch_balances(market)
242 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
243 total_base_value = sum(values_in_base.values())
244 new_repartition = cls.dispatch_assets(total_base_value)
245 # Recompute it in case we have new currencies
246 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
247 Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
248
249 @classmethod
250 def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None, debug=False):
251 cls.fetch_balances(market)
252 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
253 total_base_value = sum(values_in_base.values())
254 new_repartition = cls.dispatch_assets(total_base_value)
255 Trade.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)
256
257 @classmethod
258 def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average", debug=False):
259 cls.fetch_balances(market)
260 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
261 total_base_value = sum(values_in_base.values())
262 new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
263 Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
264
265 def __repr__(self):
266 if self.exchange_total > 0:
267 if self.exchange_free > 0 and self.exchange_used > 0:
268 exchange = " Exch: [✔{} + ❌{} = {}]".format(str(self.exchange_free), str(self.exchange_used), str(self.exchange_total))
269 elif self.exchange_free > 0:
270 exchange = " Exch: [✔{}]".format(str(self.exchange_free))
271 else:
272 exchange = " Exch: [❌{}]".format(str(self.exchange_used))
273 else:
274 exchange = ""
275
276 if self.margin_total > 0:
277 if self.margin_free != 0 and self.margin_borrowed != 0:
278 margin = " Margin: [✔{} + borrowed {} = {}]".format(str(self.margin_free), str(self.margin_borrowed), str(self.margin_total))
279 elif self.margin_free != 0:
280 margin = " Margin: [✔{}]".format(str(self.margin_free))
281 else:
282 margin = " Margin: [borrowed {}]".format(str(self.margin_borrowed))
283 elif self.margin_total < 0:
284 margin = " Margin: [{} @@ {}/{}]".format(str(self.margin_total),
285 str(self.margin_borrowed_base_price),
286 str(self.margin_lending_fees))
287 else:
288 margin = ""
289
290 if self.margin_total != 0 and self.exchange_total != 0:
291 total = " Total: [{}]".format(str(self.total))
292 else:
293 total = ""
294
295 return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")"
296
297 class Computation:
298 computations = {
299 "default": lambda x, y: x[y],
300 "average": lambda x, y: x["average"],
301 "bid": lambda x, y: x["bid"],
302 "ask": lambda x, y: x["ask"],
303 }
304
305 class Trade:
306 debug = False
307 trades = []
308
309 def __init__(self, value_from, value_to, currency, market=None):
310 # We have value_from of currency, and want to finish with value_to of
311 # that currency. value_* may not be in currency's terms
312 self.currency = currency
313 self.value_from = value_from
314 self.value_to = value_to
315 self.orders = []
316 self.market = market
317 assert self.value_from.currency == self.value_to.currency
318 if self.value_from != 0:
319 assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency
320 elif self.value_from.linked_to is None:
321 self.value_from.linked_to = Amount(self.currency, 0)
322 self.base_currency = self.value_from.currency
323
324 fees_cache = {}
325 @classmethod
326 def fetch_fees(cls, market):
327 if market.__class__ not in cls.fees_cache:
328 cls.fees_cache[market.__class__] = market.fetch_fees()
329 return cls.fees_cache[market.__class__]
330
331 ticker_cache = {}
332 ticker_cache_timestamp = time.time()
333 @classmethod
334 def get_ticker(cls, c1, c2, market, refresh=False):
335 def invert(ticker):
336 return {
337 "inverted": True,
338 "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2,
339 "original": ticker,
340 }
341 def augment_ticker(ticker):
342 ticker.update({
343 "inverted": False,
344 "average": (ticker["bid"] + ticker["ask"] ) / 2,
345 })
346
347 if time.time() - cls.ticker_cache_timestamp > 5:
348 cls.ticker_cache = {}
349 cls.ticker_cache_timestamp = time.time()
350 elif not refresh:
351 if (c1, c2, market.__class__) in cls.ticker_cache:
352 return cls.ticker_cache[(c1, c2, market.__class__)]
353 if (c2, c1, market.__class__) in cls.ticker_cache:
354 return invert(cls.ticker_cache[(c2, c1, market.__class__)])
355
356 try:
357 cls.ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2))
358 augment_ticker(cls.ticker_cache[(c1, c2, market.__class__)])
359 except ExchangeError:
360 try:
361 cls.ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1))
362 augment_ticker(cls.ticker_cache[(c2, c1, market.__class__)])
363 except ExchangeError:
364 cls.ticker_cache[(c1, c2, market.__class__)] = None
365 return cls.get_ticker(c1, c2, market)
366
367 @classmethod
368 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
369 cls.debug = cls.debug or debug
370 base_currency = sum(values_in_base.values()).currency
371 for currency in Balance.currencies():
372 if currency == base_currency:
373 continue
374 value_from = values_in_base.get(currency, Amount(base_currency, 0))
375 value_to = new_repartition.get(currency, Amount(base_currency, 0))
376 if value_from.value * value_to.value < 0:
377 trade_1 = cls(value_from, Amount(base_currency, 0), currency, market=market)
378 if only is None or trade_1.action == only:
379 cls.trades.append(trade_1)
380 trade_2 = cls(Amount(base_currency, 0), value_to, currency, market=market)
381 if only is None or trade_2.action == only:
382 cls.trades.append(trade_2)
383 else:
384 trade = cls(
385 value_from,
386 value_to,
387 currency,
388 market=market
389 )
390 if only is None or trade.action == only:
391 cls.trades.append(trade)
392 return cls.trades
393
394 @classmethod
395 def prepare_orders(cls, only=None, compute_value="default"):
396 for trade in cls.trades:
397 if only is None or trade.action == only:
398 trade.prepare_order(compute_value=compute_value)
399
400 @classmethod
401 def move_balances(cls, market):
402 needed_in_margin = {}
403 for trade in cls.trades:
404 if trade.trade_type == "short":
405 if trade.value_to.currency not in needed_in_margin:
406 needed_in_margin[trade.value_to.currency] = 0
407 needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
408 for currency, needed in needed_in_margin.items():
409 current_balance = Balance.known_balances[currency].margin_free
410 delta = (needed - current_balance).value
411 # FIXME: don't remove too much if there are open margin position
412 if delta > 0:
413 if cls.debug:
414 print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta))
415 else:
416 market.transfer_balance(currency, delta, "exchange", "margin")
417 elif delta < 0:
418 if cls.debug:
419 print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta))
420 else:
421 market.transfer_balance(currency, -delta, "margin", "exchange")
422
423 @property
424 def action(self):
425 if self.value_from == self.value_to:
426 return None
427 if self.base_currency == self.currency:
428 return None
429
430 if self.value_from < self.value_to:
431 return "acquire"
432 else:
433 return "dispose"
434
435 def order_action(self, inverted):
436 if (self.value_from < self.value_to) != inverted:
437 return "buy"
438 else:
439 return "sell"
440
441 @property
442 def trade_type(self):
443 if self.value_from + self.value_to < 0:
444 return "short"
445 else:
446 return "long"
447
448 @property
449 def filled_amount(self):
450 filled_amount = 0
451 for order in self.orders:
452 filled_amount += order.filled_amount
453 return filled_amount
454
455 def update_order(self, order, tick):
456 new_order = None
457 if tick in [0, 1, 3, 4, 6]:
458 print("{}, tick {}, waiting".format(order, tick))
459 elif tick == 2:
460 self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2)
461 new_order = self.orders[-1]
462 print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
463 elif tick ==5:
464 self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3)
465 new_order = self.orders[-1]
466 print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
467 elif tick >= 7:
468 if tick == 7:
469 print("{}, tick {}, fallbacking to market value".format(order, tick))
470 if (tick - 7) % 3 == 0:
471 self.prepare_order(compute_value="default")
472 new_order = self.orders[-1]
473 print("{}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))
474
475 if new_order is not None:
476 order.cancel()
477 new_order.run()
478
479 def prepare_order(self, compute_value="default"):
480 if self.action is None:
481 return
482 ticker = Trade.get_ticker(self.currency, self.base_currency, self.market)
483 inverted = ticker["inverted"]
484 if inverted:
485 ticker = ticker["original"]
486 rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
487 # 0.1
488
489 delta_in_base = abs(self.value_from - self.value_to)
490 # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)
491
492 if not inverted:
493 currency = self.base_currency
494 # BTC
495 if self.action == "dispose":
496 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
497 # At rate 1 Foo = 0.1 BTC
498 value_from = self.value_from.linked_to
499 # value_from = 100 FOO
500 value_to = self.value_to.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
501 # value_to = 10 FOO (1 BTC * 1/0.1)
502 delta = abs(value_to - value_from)
503 # delta = 90 FOO
504 # Action: "sell" "90 FOO" at rate "0.1" "BTC" on "market"
505
506 # Note: no rounding error possible: if we have value_to == 0, then delta == value_from
507 else:
508 delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate)
509 # I want to buy 9 / 0.1 FOO
510 # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market"
511 else:
512 currency = self.currency
513 # FOO
514 delta = delta_in_base
515 # sell:
516 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
517 # At rate 1 Foo = 0.1 BTC
518 # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market
519 # buy:
520 # I want to buy 9 / 0.1 FOO
521 # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market"
522 if self.value_to == 0:
523 rate = self.value_from.linked_to.value / self.value_from.value
524 # Recompute the rate to avoid any rounding error
525
526 close_if_possible = (self.value_to == 0)
527
528 if delta <= self.filled_amount:
529 print("Less to do than already filled: {} <= {}".format(delta,
530 self.filled_amount))
531 return
532
533 self.orders.append(Order(self.order_action(inverted),
534 delta - self.filled_amount, rate, currency, self.trade_type,
535 self.market, self, close_if_possible=close_if_possible))
536
537 @classmethod
538 def compute_value(cls, ticker, action, compute_value="default"):
539 if action == "buy":
540 action = "ask"
541 if action == "sell":
542 action = "bid"
543 if isinstance(compute_value, str):
544 compute_value = Computation.computations[compute_value]
545 return compute_value(ticker, action)
546
547 @classmethod
548 def all_orders(cls, state=None):
549 all_orders = sum(map(lambda v: v.orders, cls.trades), [])
550 if state is None:
551 return all_orders
552 else:
553 return list(filter(lambda o: o.status == state, all_orders))
554
555 @classmethod
556 def run_orders(cls):
557 for order in cls.all_orders(state="pending"):
558 order.run()
559
560 @classmethod
561 def follow_orders(cls, verbose=True, sleep=None):
562 if sleep is None:
563 sleep = 7 if cls.debug else 30
564 tick = 0
565 while len(cls.all_orders(state="open")) > 0:
566 time.sleep(sleep)
567 tick += 1
568 for order in cls.all_orders(state="open"):
569 if order.get_status() != "open":
570 if verbose:
571 print("finished {}".format(order))
572 else:
573 order.trade.update_order(order, tick)
574 if verbose:
575 print("All orders finished")
576
577 @classmethod
578 def update_all_orders_status(cls):
579 for order in cls.all_orders(state="open"):
580 order.get_status()
581
582 def __repr__(self):
583 return "Trade({} -> {} in {}, {})".format(
584 self.value_from,
585 self.value_to,
586 self.currency,
587 self.action)
588
589 @classmethod
590 def print_all_with_order(cls):
591 for trade in cls.trades:
592 trade.print_with_order()
593
594 def print_with_order(self):
595 print(self)
596 for order in self.orders:
597 print("\t", order, sep="")
598
599 class Order:
600 def __init__(self, action, amount, rate, base_currency, trade_type, market,
601 trade, close_if_possible=False):
602 self.action = action
603 self.amount = amount
604 self.rate = rate
605 self.base_currency = base_currency
606 self.market = market
607 self.trade_type = trade_type
608 self.results = []
609 self.mouvements = []
610 self.status = "pending"
611 self.trade = trade
612 self.close_if_possible = close_if_possible
613 self.debug = trade.debug
614
615 def __repr__(self):
616 return "Order({} {} {} at {} {} [{}]{})".format(
617 self.action,
618 self.trade_type,
619 self.amount,
620 self.rate,
621 self.base_currency,
622 self.status,
623 " ✂" if self.close_if_possible else "",
624 )
625
626 @property
627 def account(self):
628 if self.trade_type == "long":
629 return "exchange"
630 else:
631 return "margin"
632
633 @property
634 def pending(self):
635 return self.status == "pending"
636
637 @property
638 def finished(self):
639 return self.status == "closed" or self.status == "canceled" or self.status == "error"
640
641 @property
642 def id(self):
643 return self.results[0]["id"]
644
645 def run(self):
646 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
647 amount = round(self.amount, self.market.order_precision(symbol)).value
648
649 if self.debug:
650 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
651 symbol, self.action, amount, self.rate, self.account))
652 self.status = "open"
653 self.results.append({"debug": True, "id": -1})
654 else:
655 try:
656 self.results.append(self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account))
657 self.status = "open"
658 except Exception as e:
659 self.status = "error"
660 print("error when running market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
661 symbol, self.action, amount, self.rate, self.account))
662 self.error_message = str("{}: {}".format(e.__class__.__name__, e))
663 print(self.error_message)
664
665 def get_status(self):
666 if self.debug:
667 return self.status
668 # other states are "closed" and "canceled"
669 if self.status == "open":
670 self.fetch()
671 if self.status != "open":
672 self.mark_finished_order()
673 return self.status
674
675 def mark_finished_order(self):
676 if self.debug:
677 return
678 if self.status == "closed":
679 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible:
680 self.market.close_margin_position(self.amount.currency, self.base_currency)
681
682 fetch_cache_timestamp = None
683 def fetch(self, force=False):
684 if self.debug or (not force and self.fetch_cache_timestamp is not None
685 and time.time() - self.fetch_cache_timestamp < 10):
686 return
687 self.fetch_cache_timestamp = time.time()
688
689 self.results.append(self.market.fetch_order(self.id))
690 result = self.results[-1]
691 self.status = result["status"]
692 # Time at which the order started
693 self.timestamp = result["datetime"]
694 self.fetch_mouvements()
695
696 # FIXME: consider open order with dust remaining as closed
697
698 @property
699 def dust_amount_remaining(self):
700 return self.remaining_amount < 0.001
701
702 @property
703 def remaining_amount(self):
704 if self.status == "open":
705 self.fetch()
706 return self.amount - self.filled_amount
707
708 @property
709 def filled_amount(self):
710 if self.status == "open":
711 self.fetch()
712 filled_amount = Amount(self.amount.currency, 0)
713 for mouvement in self.mouvements:
714 filled_amount += mouvement.total
715 return filled_amount
716
717 def fetch_mouvements(self):
718 mouvements = self.market.privatePostReturnOrderTrades({"orderNumber": self.id})
719 self.mouvements = []
720
721 for mouvement_hash in mouvements:
722 self.mouvements.append(Mouvement(self.amount.currency,
723 self.base_currency, mouvement_hash))
724
725 def cancel(self):
726 if self.debug:
727 self.status = "canceled"
728 return
729 self.market.cancel_order(self.result['id'])
730 self.fetch()
731
732 class Mouvement:
733 def __init__(self, currency, base_currency, hash_):
734 self.currency = currency
735 self.base_currency = base_currency
736 self.id = hash_["id"]
737 self.action = hash_["type"]
738 self.fee_rate = D(hash_["fee"])
739 self.date = datetime.strptime(hash_["date"], '%Y-%m-%d %H:%M:%S')
740 self.rate = D(hash_["rate"])
741 self.total = Amount(currency, hash_["amount"])
742 # rate * total = total_in_base
743 self.total_in_base = Amount(base_currency, hash_["total"])
744
745 def print_orders(market, base_currency="BTC"):
746 Balance.prepare_trades(market, base_currency=base_currency, compute_value="average")
747 Trade.prepare_orders(compute_value="average")
748 for currency, balance in Balance.known_balances.items():
749 print(balance)
750 Trade.print_all_with_order()
751
752 def make_orders(market, base_currency="BTC"):
753 Balance.prepare_trades(market, base_currency=base_currency)
754 for trade in Trade.trades:
755 print(trade)
756 for order in trade.orders:
757 print("\t", order, sep="")
758 order.run()
759
760 def process_sell_all_sell(market, base_currency="BTC", debug=False):
761 Balance.prepare_trades_to_sell_all(market, debug=debug)
762 Trade.prepare_orders(compute_value="average")
763 print("------------------")
764 for currency, balance in Balance.known_balances.items():
765 print(balance)
766 print("------------------")
767 Trade.print_all_with_order()
768 print("------------------")
769 Trade.run_orders()
770 Trade.follow_orders()
771
772 def process_sell_all_buy(market, base_currency="BTC", debug=False):
773 Balance.prepare_trades(market, debug=debug)
774 Trade.prepare_orders()
775 print("------------------")
776 for currency, balance in Balance.known_balances.items():
777 print(balance)
778 print("------------------")
779 Trade.print_all_with_order()
780 print("------------------")
781 Trade.move_balances(market)
782 Trade.run_orders()
783 Trade.follow_orders()
784
785 if __name__ == '__main__':
786 print_orders(market)