3 # Put your poloniex api key in market.py
4 from market
import market
6 # FIXME: Améliorer le bid/ask
7 # FIXME: J'essayais d'utiliser plus de bitcoins que j'en avais à disposition
8 # FIXME: better compute moves to avoid rounding errors
10 def static_var(varname
, value
):
12 setattr(func
, varname
, value
)
17 URL
= "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json"
22 def repartition_pertenthousand(cls
, liquidity
="medium"):
23 cls
.parse_cryptoportfolio()
24 liquidities
= cls
.liquidities
[liquidity
]
25 last_date
= sorted(liquidities
.keys())[-1]
26 return liquidities
[last_date
]
29 def get_cryptoportfolio(cls
):
32 urllib3
.disable_warnings()
33 http
= urllib3
.PoolManager()
35 r
= http
.request("GET", cls
.URL
)
36 cls
.data
= json
.loads(r
.data
)
39 def parse_cryptoportfolio(cls
):
41 cls
.get_cryptoportfolio()
43 def filter_weights(weight_hash
):
44 if weight_hash
[1] == 0:
46 if weight_hash
[0] == "_row":
51 def clean_weights_(h
):
52 if type(h
[1][i
]) == str:
53 return [h
[0], h
[1][i
]]
55 return [h
[0], int(h
[1][i
] * 10000)]
58 def parse_weights(portfolio_hash
):
59 # FIXME: we'll need shorts at some point
60 assert all(map(lambda x
: x
== "long", portfolio_hash
["holding"]["direction"]))
61 weights_hash
= portfolio_hash
["weights"]
63 for i
in range(len(weights_hash
["_row"])):
64 weights
[weights_hash
["_row"][i
]] = dict(filter(
66 map(clean_weights(i
), weights_hash
.items())))
69 high_liquidity
= parse_weights(cls
.data
["portfolio_1"])
70 medium_liquidity
= parse_weights(cls
.data
["portfolio_2"])
73 "medium": medium_liquidity
,
74 "high": high_liquidity
,
80 def __init__(self
, currency
, value
, int_val
=None, linked_to
=None, ticker
=None):
81 self
.currency
= currency
83 self
._value
= int(value
* 10**self
.MAX_DIGITS
)
86 self
.linked_to
= linked_to
89 self
.ticker_cache
= {}
90 self
.ticker_cache_timestamp
= time
.time()
94 return self
._value
/ 10 ** self
.MAX_DIGITS
96 def in_currency(self
, other_currency
, market
, action
="average"):
97 if other_currency
== self
.currency
:
99 asset_ticker
= self
.get_ticker(other_currency
, market
)
100 if asset_ticker
is not None:
104 int_val
=int(self
._value
* asset_ticker
[action
]),
108 raise Exception("This asset is not available in the chosen market")
110 def get_ticker(self
, c2
, market
, refresh
=False):
115 # COMMENT: c'est pas dangereux de faire l'invert comme ca ? il peut pas y avoir d'asymétrie ?
116 # et je pense que ca simplifierai le code de pas avoir le inverted. Il me semble qu'il est là
117 # juste pour une histoire de cache.
119 # COMMENT: j'ai pas compris pourquoi tu prenais l'average ?
120 "average": (float(1/ticker
["bid"]) + float(1/ticker
["ask"]) ) / 2,
121 "notInverted": ticker
,
123 def augment_ticker(ticker
):
126 "average": (ticker
["bid"] + ticker
["ask"] ) / 2,
129 if time
.time() - self
.ticker_cache_timestamp
> 5:
130 self
.ticker_cache
= {}
131 self
.ticker_cache_timestamp
= time
.time()
133 if (c1
, c2
, market
.__class
__) in self
.ticker_cache
:
134 return self
.ticker_cache
[(c1
, c2
, market
.__class
__)]
135 if (c2
, c1
, market
.__class
__) in self
.ticker_cache
:
136 return invert(self
.ticker_cache
[(c2
, c1
, market
.__class
__)])
139 self
.ticker_cache
[(c1
, c2
, market
.__class
__)] = market
.fetch_ticker("{}/{}".format(c1
, c2
))
140 augment_ticker(self
.ticker_cache
[(c1
, c2
, market
.__class
__)])
141 except ccxt
.ExchangeError
:
143 self
.ticker_cache
[(c2
, c1
, market
.__class
__)] = market
.fetch_ticker("{}/{}".format(c2
, c1
))
144 augment_ticker(self
.ticker_cache
[(c2
, c1
, market
.__class
__)])
145 except ccxt
.ExchangeError
:
146 self
.ticker_cache
[(c1
, c2
, market
.__class
__)] = None
147 return self
.get_ticker(c2
, market
)
150 return Amount(self
.currency
, 0, int_val
=abs(self
._value
))
152 def __add__(self
, other
):
153 if other
.currency
!= self
.currency
and other
._value
* self
._value
!= 0:
154 raise Exception("Summing amounts must be done with same currencies")
155 return Amount(self
.currency
, 0, int_val
=self
._value
+ other
._value
)
157 def __radd__(self
, other
):
161 return self
.__add
__(other
)
163 def __sub__(self
, other
):
164 if other
.currency
!= self
.currency
and other
._value
* self
._value
!= 0:
165 raise Exception("Summing amounts must be done with same currencies")
166 return Amount(self
.currency
, 0, int_val
=self
._value
- other
._value
)
171 def __mul__(self
, value
):
172 if type(value
) != int and type(value
) != float:
173 raise TypeError("Amount may only be multiplied by numbers")
174 return Amount(self
.currency
, 0, int_val
=(self
._value
* value
))
176 def __rmul__(self
, value
):
177 return self
.__mul
__(value
)
179 def __floordiv__(self
, value
):
180 if type(value
) != int:
181 raise TypeError("Amount may only be multiplied by integers")
182 return Amount(self
.currency
, 0, int_val
=(self
._value
// value
))
184 def __truediv__(self
, value
):
185 return self
.__floordiv
__(value
)
187 def __lt__(self
, other
):
188 if self
.currency
!= other
.currency
:
189 raise Exception("Comparing amounts must be done with same currencies")
190 return self
._value
< other
._value
192 def __eq__(self
, other
):
194 return self
._value
== 0
195 if self
.currency
!= other
.currency
:
196 raise Exception("Comparing amounts must be done with same currencies")
197 return self
._value
== other
._value
200 if self
.linked_to
is None:
201 return "{:.8f} {}".format(self
.value
, self
.currency
)
203 return "{:.8f} {} [{}]".format(self
.value
, self
.currency
, self
.linked_to
)
206 if self
.linked_to
is None:
207 return "Amount({:.8f} {})".format(self
.value
, self
.currency
)
209 return "Amount({:.8f} {} -> {})".format(self
.value
, self
.currency
, repr(self
.linked_to
))
215 def __init__(self
, currency
, total_value
, free_value
, used_value
):
216 self
.currency
= currency
217 self
.total
= Amount(currency
, total_value
)
218 self
.free
= Amount(currency
, free_value
)
219 self
.used
= Amount(currency
, used_value
)
222 def in_currency(cls
, other_currency
, market
, action
="average", type="total"):
224 for currency
in cls
.known_balances
:
225 balance
= cls
.known_balances
[currency
]
226 other_currency_amount
= getattr(balance
, type)\
227 .in_currency(other_currency
, market
, action
=action
)
228 amounts
[currency
] = other_currency_amount
233 return cls
.known_balances
.keys()
236 def from_hash(cls
, currency
, hash_
):
237 return cls(currency
, hash_
["total"], hash_
["free"], hash_
["used"])
240 def _fill_balances(cls
, hash_
):
242 if key
in ["info", "free", "used", "total"]:
244 if hash_
[key
]["total"] > 0:
245 cls
.known_balances
[key
] = cls
.from_hash(key
, hash_
[key
])
248 def fetch_balances(cls
, market
):
249 cls
._fill
_balances
(market
.fetch_balance())
250 return cls
.known_balances
253 def dispatch_assets(cls
, amount
):
254 repartition_pertenthousand
= Portfolio
.repartition_pertenthousand()
255 sum_pertenthousand
= sum([v
for k
, v
in repartition_pertenthousand
.items()])
257 for currency
, ptt
in repartition_pertenthousand
.items():
258 amounts
[currency
] = ptt
* amount
/ sum_pertenthousand
259 if currency
not in cls
.known_balances
:
260 cls
.known_balances
[currency
] = cls(currency
, 0, 0, 0)
264 def prepare_trades(cls
, market
, base_currency
="BTC"):
265 cls
.fetch_balances(market
)
266 values_in_base
= cls
.in_currency(base_currency
, market
)
267 total_base_value
= sum(values_in_base
.values())
268 new_repartition
= cls
.dispatch_assets(total_base_value
)
269 Trade
.compute_trades(values_in_base
, new_repartition
, market
=market
)
272 return int(self
.total
)
275 return "Balance({} [{}/{}/{}])".format(self
.currency
, str(self
.free
), str(self
.used
), str(self
.total
))
280 def __init__(self
, value_from
, value_to
, currency
, market
=None):
281 # We have value_from of currency, and want to finish with value_to of
282 # that currency. value_* may not be in currency's terms
283 self
.currency
= currency
284 self
.value_from
= value_from
285 self
.value_to
= value_to
287 assert self
.value_from
.currency
== self
.value_to
.currency
288 assert self
.value_from
.linked_to
is not None and self
.value_from
.linked_to
.currency
== self
.currency
289 self
.base_currency
= self
.value_from
.currency
291 if market
is not None:
292 self
.prepare_order(market
)
295 def compute_trades(cls
, values_in_base
, new_repartition
, market
=None):
296 base_currency
= sum(values_in_base
.values()).currency
297 for currency
in Balance
.currencies():
298 if currency
== base_currency
:
300 cls
.trades
[currency
] = cls(
301 values_in_base
.get(currency
, Amount(base_currency
, 0)),
302 new_repartition
.get(currency
, Amount(base_currency
, 0)),
310 if self
.value_from
== self
.value_to
:
312 if self
.base_currency
== self
.currency
:
315 if self
.value_from
< self
.value_to
:
320 def ticker_action(self
, inverted
):
321 if self
.value_from
< self
.value_to
:
322 return "ask" if not inverted
else "bid"
324 return "bid" if not inverted
else "ask"
326 def prepare_order(self
, market
):
327 if self
.action
is None:
329 ticker
= self
.value_from
.ticker
330 inverted
= ticker
["inverted"]
333 value_from
= self
.value_from
.linked_to
334 value_to
= self
.value_to
.in_currency(self
.currency
, market
)
335 delta
= abs(value_to
- value_from
)
336 currency
= self
.base_currency
338 ticker
= ticker
["notInverted"]
339 delta
= abs(self
.value_to
- self
.value_from
)
340 currency
= self
.currency
342 rate
= ticker
[self
.ticker_action(inverted
)]
344 self
.orders
.append(Order(self
.ticker_action(inverted
), delta
, rate
, currency
))
348 return sum(map(lambda v
: v
.orders
, cls
.trades
.values()), [])
351 def follow_orders(cls
, market
):
352 orders
= cls
.all_orders()
354 while len(orders
) != len(finished_orders
):
357 if order
in finished_orders
:
359 if order
.get_status(market
) != "open":
360 finished_orders
.append(order
)
361 print("finished {}".format(order
))
362 print("All orders finished")
365 return "Trade({} -> {} in {}, {})".format(
374 def __init__(self
, action
, amount
, rate
, base_currency
):
378 self
.base_currency
= base_currency
380 self
.status
= "not run"
383 return "Order({} {} at {} {} [{}])".format(
391 def run(self
, market
):
392 symbol
= "{}/{}".format(self
.amount
.currency
, self
.base_currency
)
393 amount
= self
.amount
.value
396 print("market.create_order('{}', 'limit', '{}', {}, price={})".format(
397 symbol
, self
.action
, amount
, self
.rate
))
400 self
.result
= market
.create_order(symbol
, 'limit', self
.action
, amount
, price
=self
.rate
)
405 def get_status(self
, market
):
406 # other states are "closed" and "canceled"
407 if self
.status
== "open":
408 result
= market
.fetch_order(self
.result
['id'])
409 self
.status
= result
["status"]
412 @static_var("cache", {})
413 def fetch_fees(market
):
414 if market
.__class
__ not in fetch_fees
.cache
:
415 fetch_fees
.cache
[market
.__class
__] = market
.fetch_fees()
416 return fetch_fees
.cache
[market
.__class
__]
418 def print_orders(market
, base_currency
="BTC"):
419 Balance
.prepare_trades(market
, base_currency
=base_currency
)
420 for currency
, trade
in Trade
.trades
.items():
422 for order
in trade
.orders
:
423 print("\t", order
, sep
="")
425 def make_orders(market
, base_currency
="BTC"):
426 Balance
.prepare_trades(market
, base_currency
=base_currency
)
427 for currency
, trade
in Trade
.trades
.items():
429 for order
in trade
.orders
:
430 print("\t", order
, sep
="")
433 if __name__
== '__main__':