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