3 from decimal
import Decimal
as D
4 # Put your poloniex api key in market.py
5 from market
import market
10 URL
= "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json"
15 def repartition_pertenthousand(cls
, liquidity
="medium"):
16 cls
.parse_cryptoportfolio()
17 liquidities
= cls
.liquidities
[liquidity
]
18 last_date
= sorted(liquidities
.keys())[-1]
19 return liquidities
[last_date
]
22 def get_cryptoportfolio(cls
):
25 urllib3
.disable_warnings()
26 http
= urllib3
.PoolManager()
29 r
= http
.request("GET", cls
.URL
)
33 cls
.data
= json
.loads(r
.data
,
36 except json
.JSONDecodeError
:
40 def parse_cryptoportfolio(cls
):
42 cls
.get_cryptoportfolio()
44 def filter_weights(weight_hash
):
45 if weight_hash
[1] == 0:
47 if weight_hash
[0] == "_row":
52 def clean_weights_(h
):
53 if type(h
[1][i
]) == str:
54 return [h
[0], h
[1][i
]]
56 return [h
[0], int(h
[1][i
] * 10000)]
59 def parse_weights(portfolio_hash
):
60 # FIXME: we'll need shorts at some point
61 assert all(map(lambda x
: x
== "long", portfolio_hash
["holding"]["direction"]))
62 weights_hash
= portfolio_hash
["weights"]
64 for i
in range(len(weights_hash
["_row"])):
65 weights
[weights_hash
["_row"][i
]] = dict(filter(
67 map(clean_weights(i
), weights_hash
.items())))
70 high_liquidity
= parse_weights(cls
.data
["portfolio_1"])
71 medium_liquidity
= parse_weights(cls
.data
["portfolio_2"])
74 "medium": medium_liquidity
,
75 "high": high_liquidity
,
79 def __init__(self
, currency
, value
, linked_to
=None, ticker
=None, rate
=None):
80 self
.currency
= currency
82 self
.linked_to
= linked_to
86 self
.ticker_cache
= {}
87 self
.ticker_cache_timestamp
= time
.time()
89 def in_currency(self
, other_currency
, market
, rate
=None, action
=None, compute_value
="average"):
90 if other_currency
== self
.currency
:
98 asset_ticker
= Trade
.get_ticker(self
.currency
, other_currency
, market
)
99 if asset_ticker
is not None:
100 rate
= Trade
.compute_value(asset_ticker
, action
, compute_value
=compute_value
)
108 raise Exception("This asset is not available in the chosen market")
111 return Amount(self
.currency
, abs(self
.value
))
113 def __add__(self
, other
):
114 if other
.currency
!= self
.currency
and other
.value
* self
.value
!= 0:
115 raise Exception("Summing amounts must be done with same currencies")
116 return Amount(self
.currency
, self
.value
+ other
.value
)
118 def __radd__(self
, other
):
122 return self
.__add
__(other
)
124 def __sub__(self
, other
):
125 if other
.currency
!= self
.currency
and other
.value
* self
.value
!= 0:
126 raise Exception("Summing amounts must be done with same currencies")
127 return Amount(self
.currency
, self
.value
- other
.value
)
129 def __mul__(self
, value
):
130 if type(value
) != int and type(value
) != float and type(value
) != D
:
131 raise TypeError("Amount may only be multiplied by numbers")
132 return Amount(self
.currency
, self
.value
* value
)
134 def __rmul__(self
, value
):
135 return self
.__mul
__(value
)
137 def __floordiv__(self
, value
):
138 if type(value
) != int and type(value
) != float and type(value
) != D
:
139 raise TypeError("Amount may only be multiplied by integers")
140 return Amount(self
.currency
, self
.value
/ value
)
142 def __truediv__(self
, value
):
143 return self
.__floordiv
__(value
)
145 def __lt__(self
, other
):
146 if self
.currency
!= other
.currency
:
147 raise Exception("Comparing amounts must be done with same currencies")
148 return self
.value
< other
.value
150 def __eq__(self
, other
):
152 return self
.value
== 0
153 if self
.currency
!= other
.currency
:
154 raise Exception("Comparing amounts must be done with same currencies")
155 return self
.value
== other
.value
158 if self
.linked_to
is None:
159 return "{:.8f} {}".format(self
.value
, self
.currency
)
161 return "{:.8f} {} [{}]".format(self
.value
, self
.currency
, self
.linked_to
)
164 if self
.linked_to
is None:
165 return "Amount({:.8f} {})".format(self
.value
, self
.currency
)
167 return "Amount({:.8f} {} -> {})".format(self
.value
, self
.currency
, repr(self
.linked_to
))
172 def __init__(self
, currency
, total_value
, free_value
, used_value
):
173 self
.currency
= currency
174 self
.total
= Amount(currency
, total_value
)
175 self
.free
= Amount(currency
, free_value
)
176 self
.used
= Amount(currency
, used_value
)
179 def from_hash(cls
, currency
, hash_
):
180 return cls(currency
, hash_
["total"], hash_
["free"], hash_
["used"])
183 def in_currency(cls
, other_currency
, market
, compute_value
="average", type="total"):
185 for currency
in cls
.known_balances
:
186 balance
= cls
.known_balances
[currency
]
187 other_currency_amount
= getattr(balance
, type)\
188 .in_currency(other_currency
, market
, compute_value
=compute_value
)
189 amounts
[currency
] = other_currency_amount
194 return cls
.known_balances
.keys()
197 def _fill_balances(cls
, hash_
):
199 if key
in ["info", "free", "used", "total"]:
201 if hash_
[key
]["total"] > 0 or key
in cls
.known_balances
:
202 cls
.known_balances
[key
] = cls
.from_hash(key
, hash_
[key
])
205 def fetch_balances(cls
, market
):
206 cls
._fill
_balances
(market
.fetch_balance())
207 return cls
.known_balances
210 def dispatch_assets(cls
, amount
):
211 repartition_pertenthousand
= Portfolio
.repartition_pertenthousand()
212 sum_pertenthousand
= sum([v
for k
, v
in repartition_pertenthousand
.items()])
214 for currency
, ptt
in repartition_pertenthousand
.items():
215 amounts
[currency
] = ptt
* amount
/ sum_pertenthousand
216 if currency
not in cls
.known_balances
:
217 cls
.known_balances
[currency
] = cls(currency
, 0, 0, 0)
221 def prepare_trades(cls
, market
, base_currency
="BTC", compute_value
="average"):
222 cls
.fetch_balances(market
)
223 values_in_base
= cls
.in_currency(base_currency
, market
, compute_value
=compute_value
)
224 total_base_value
= sum(values_in_base
.values())
225 new_repartition
= cls
.dispatch_assets(total_base_value
)
226 # Recompute it in case we have new currencies
227 values_in_base
= cls
.in_currency(base_currency
, market
, compute_value
=compute_value
)
228 Trade
.compute_trades(values_in_base
, new_repartition
, market
=market
)
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
)
239 return "Balance({} [{}/{}/{}])".format(self
.currency
, str(self
.free
), str(self
.used
), str(self
.total
))
243 "default": lambda x
, y
: x
[y
],
244 "average": lambda x
, y
: x
["average"],
245 "bid": lambda x
, y
: x
["bid"],
246 "ask": lambda x
, y
: x
["ask"],
253 def __init__(self
, value_from
, value_to
, currency
, market
=None):
254 # We have value_from of currency, and want to finish with value_to of
255 # that currency. value_* may not be in currency's terms
256 self
.currency
= currency
257 self
.value_from
= value_from
258 self
.value_to
= value_to
261 assert self
.value_from
.currency
== self
.value_to
.currency
262 assert self
.value_from
.linked_to
is not None and self
.value_from
.linked_to
.currency
== self
.currency
263 self
.base_currency
= self
.value_from
.currency
267 def fetch_fees(cls
, market
):
268 if market
.__class
__ not in cls
.fees_cache
:
269 cls
.fees_cache
[market
.__class
__] = market
.fetch_fees()
270 return cls
.fees_cache
[market
.__class
__]
273 ticker_cache_timestamp
= time
.time()
275 def get_ticker(cls
, c1
, c2
, market
, refresh
=False):
279 "average": (1/ticker
["bid"] + 1/ticker
["ask"]) / 2,
282 def augment_ticker(ticker
):
285 "average": (ticker
["bid"] + ticker
["ask"] ) / 2,
288 if time
.time() - cls
.ticker_cache_timestamp
> 5:
289 cls
.ticker_cache
= {}
290 cls
.ticker_cache_timestamp
= time
.time()
292 if (c1
, c2
, market
.__class
__) in cls
.ticker_cache
:
293 return cls
.ticker_cache
[(c1
, c2
, market
.__class
__)]
294 if (c2
, c1
, market
.__class
__) in cls
.ticker_cache
:
295 return invert(cls
.ticker_cache
[(c2
, c1
, market
.__class
__)])
298 cls
.ticker_cache
[(c1
, c2
, market
.__class
__)] = market
.fetch_ticker("{}/{}".format(c1
, c2
))
299 augment_ticker(cls
.ticker_cache
[(c1
, c2
, market
.__class
__)])
300 except ccxt
.ExchangeError
:
302 cls
.ticker_cache
[(c2
, c1
, market
.__class
__)] = market
.fetch_ticker("{}/{}".format(c2
, c1
))
303 augment_ticker(cls
.ticker_cache
[(c2
, c1
, market
.__class
__)])
304 except ccxt
.ExchangeError
:
305 cls
.ticker_cache
[(c1
, c2
, market
.__class
__)] = None
306 return cls
.get_ticker(c1
, c2
, market
)
309 def compute_trades(cls
, values_in_base
, new_repartition
, only
=None, market
=None):
310 base_currency
= sum(values_in_base
.values()).currency
311 for currency
in Balance
.currencies():
312 if currency
== base_currency
:
315 values_in_base
.get(currency
, Amount(base_currency
, 0)),
316 new_repartition
.get(currency
, Amount(base_currency
, 0)),
320 if only
is None or trade
.action
== only
:
321 cls
.trades
[currency
] = trade
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
)
332 if self
.value_from
== self
.value_to
:
334 if self
.base_currency
== self
.currency
:
337 if self
.value_from
< self
.value_to
:
342 def order_action(self
, inverted
):
343 if self
.value_from
< self
.value_to
:
344 return "ask" if not inverted
else "bid"
346 return "bid" if not inverted
else "ask"
348 def prepare_order(self
, compute_value
="default"):
349 if self
.action
is None:
351 ticker
= self
.value_from
.ticker
352 inverted
= ticker
["inverted"]
355 value_from
= self
.value_from
.linked_to
356 value_to
= self
.value_to
.in_currency(self
.currency
, self
.market
, rate
=1/self
.value_from
.rate
)
357 delta
= abs(value_to
- value_from
)
358 currency
= self
.base_currency
360 ticker
= ticker
["original"]
361 delta
= abs(self
.value_to
- self
.value_from
)
362 currency
= self
.currency
364 rate
= Trade
.compute_value(ticker
, self
.order_action(inverted
), compute_value
=compute_value
)
366 self
.orders
.append(Order(self
.order_action(inverted
), delta
, rate
, currency
, self
.market
))
369 def compute_value(cls
, ticker
, action
, compute_value
="default"):
370 if type(compute_value
) == str:
371 compute_value
= Computation
.computations
[compute_value
]
372 return compute_value(ticker
, action
)
375 def all_orders(cls
, state
=None):
376 all_orders
= sum(map(lambda v
: v
.orders
, cls
.trades
.values()), [])
380 return list(filter(lambda o
: o
.status
== state
, all_orders
))
384 for order
in cls
.all_orders(state
="pending"):
388 def follow_orders(cls
, verbose
=True, sleep
=30):
389 orders
= cls
.all_orders()
391 while len(orders
) != len(finished_orders
):
394 if order
in finished_orders
:
396 if order
.get_status() != "open":
397 finished_orders
.append(order
)
399 print("finished {}".format(order
))
401 print("All orders finished")
404 return "Trade({} -> {} in {}, {})".format(
413 def __init__(self
, action
, amount
, rate
, base_currency
, market
):
417 self
.base_currency
= base_currency
420 self
.status
= "pending"
423 return "Order({} {} at {} {} [{}])".format(
433 return self
.status
== "pending"
437 return self
.status
== "closed" or self
.status
== "canceled"
440 symbol
= "{}/{}".format(self
.amount
.currency
, self
.base_currency
)
441 amount
= self
.amount
.value
444 print("market.create_order('{}', 'limit', '{}', {}, price={})".format(
445 symbol
, self
.action
, amount
, self
.rate
))
448 self
.result
= self
.market
.create_order(symbol
, 'limit', self
.action
, amount
, price
=self
.rate
)
453 def get_status(self
):
454 # other states are "closed" and "canceled"
455 if self
.status
== "open":
456 result
= self
.market
.fetch_order(self
.result
['id'])
457 self
.status
= result
["status"]
460 def print_orders(market
, base_currency
="BTC"):
461 Balance
.prepare_trades(market
, base_currency
=base_currency
, compute_value
="average")
462 Trade
.prepare_orders(compute_value
="average")
463 for currency
, balance
in Balance
.known_balances
.items():
465 for currency
, trade
in Trade
.trades
.items():
467 for order
in trade
.orders
:
468 print("\t", order
, sep
="")
470 def make_orders(market
, base_currency
="BTC"):
471 Balance
.prepare_trades(market
, base_currency
=base_currency
)
472 for currency
, trade
in Trade
.trades
.items():
474 for order
in trade
.orders
:
475 print("\t", order
, sep
="")
478 if __name__
== '__main__':