hive-nectar 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hive-nectar might be problematic. Click here for more details.

Files changed (86) hide show
  1. hive_nectar-0.0.2.dist-info/METADATA +182 -0
  2. hive_nectar-0.0.2.dist-info/RECORD +86 -0
  3. hive_nectar-0.0.2.dist-info/WHEEL +4 -0
  4. hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +32 -0
  7. nectar/account.py +4371 -0
  8. nectar/amount.py +475 -0
  9. nectar/asciichart.py +270 -0
  10. nectar/asset.py +82 -0
  11. nectar/block.py +446 -0
  12. nectar/blockchain.py +1178 -0
  13. nectar/blockchaininstance.py +2284 -0
  14. nectar/blockchainobject.py +221 -0
  15. nectar/blurt.py +563 -0
  16. nectar/cli.py +6285 -0
  17. nectar/comment.py +1217 -0
  18. nectar/community.py +513 -0
  19. nectar/constants.py +111 -0
  20. nectar/conveyor.py +309 -0
  21. nectar/discussions.py +1709 -0
  22. nectar/exceptions.py +149 -0
  23. nectar/hive.py +546 -0
  24. nectar/hivesigner.py +420 -0
  25. nectar/imageuploader.py +72 -0
  26. nectar/instance.py +129 -0
  27. nectar/market.py +1013 -0
  28. nectar/memo.py +449 -0
  29. nectar/message.py +357 -0
  30. nectar/nodelist.py +444 -0
  31. nectar/price.py +557 -0
  32. nectar/profile.py +65 -0
  33. nectar/rc.py +308 -0
  34. nectar/snapshot.py +726 -0
  35. nectar/steem.py +582 -0
  36. nectar/storage.py +53 -0
  37. nectar/transactionbuilder.py +622 -0
  38. nectar/utils.py +545 -0
  39. nectar/version.py +2 -0
  40. nectar/vote.py +557 -0
  41. nectar/wallet.py +472 -0
  42. nectar/witness.py +617 -0
  43. nectarapi/__init__.py +11 -0
  44. nectarapi/exceptions.py +123 -0
  45. nectarapi/graphenerpc.py +589 -0
  46. nectarapi/node.py +178 -0
  47. nectarapi/noderpc.py +229 -0
  48. nectarapi/rpcutils.py +97 -0
  49. nectarapi/version.py +2 -0
  50. nectarbase/__init__.py +14 -0
  51. nectarbase/ledgertransactions.py +75 -0
  52. nectarbase/memo.py +243 -0
  53. nectarbase/objects.py +429 -0
  54. nectarbase/objecttypes.py +22 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1297 -0
  57. nectarbase/signedtransactions.py +48 -0
  58. nectarbase/transactions.py +11 -0
  59. nectarbase/version.py +2 -0
  60. nectargrapheneapi/__init__.py +6 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +846 -0
  63. nectargraphenebase/aes.py +52 -0
  64. nectargraphenebase/base58.py +192 -0
  65. nectargraphenebase/bip32.py +494 -0
  66. nectargraphenebase/bip38.py +134 -0
  67. nectargraphenebase/chains.py +149 -0
  68. nectargraphenebase/dictionary.py +3 -0
  69. nectargraphenebase/ecdsasig.py +326 -0
  70. nectargraphenebase/objects.py +123 -0
  71. nectargraphenebase/objecttypes.py +6 -0
  72. nectargraphenebase/operationids.py +3 -0
  73. nectargraphenebase/operations.py +23 -0
  74. nectargraphenebase/prefix.py +11 -0
  75. nectargraphenebase/py23.py +38 -0
  76. nectargraphenebase/signedtransactions.py +201 -0
  77. nectargraphenebase/types.py +419 -0
  78. nectargraphenebase/unsignedtransactions.py +283 -0
  79. nectargraphenebase/version.py +2 -0
  80. nectarstorage/__init__.py +38 -0
  81. nectarstorage/base.py +306 -0
  82. nectarstorage/exceptions.py +16 -0
  83. nectarstorage/interfaces.py +237 -0
  84. nectarstorage/masterpassword.py +239 -0
  85. nectarstorage/ram.py +30 -0
  86. nectarstorage/sqlite.py +334 -0
nectar/market.py ADDED
@@ -0,0 +1,1013 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import random
4
+ from datetime import datetime, timedelta, timezone
5
+
6
+ from nectar.instance import shared_blockchain_instance
7
+ from nectarbase import operations
8
+
9
+ from .account import Account
10
+ from .amount import Amount
11
+ from .asset import Asset
12
+ from .price import FilledOrder, Order, Price
13
+ from .utils import (
14
+ addTzInfo,
15
+ assets_from_string,
16
+ formatTimeFromNow,
17
+ formatTimeString,
18
+ )
19
+
20
+ REQUEST_MODULE = None
21
+ if not REQUEST_MODULE:
22
+ try:
23
+ import requests
24
+
25
+ REQUEST_MODULE = "requests"
26
+ except ImportError:
27
+ REQUEST_MODULE = None
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ class Market(dict):
32
+ """This class allows to easily access Markets on the blockchain for trading, etc.
33
+
34
+ :param Steem blockchain_instance: Steem instance
35
+ :param Asset base: Base asset
36
+ :param Asset quote: Quote asset
37
+ :returns: Blockchain Market
38
+ :rtype: dictionary with overloaded methods
39
+
40
+ Instances of this class are dictionaries that come with additional
41
+ methods (see below) that allow dealing with a market and its
42
+ corresponding functions.
43
+
44
+ This class tries to identify **two** assets as provided in the
45
+ parameters in one of the following forms:
46
+
47
+ * ``base`` and ``quote`` are valid assets (according to :class:`nectar.asset.Asset`)
48
+ * ``base:quote`` separated with ``:``
49
+ * ``base/quote`` separated with ``/``
50
+ * ``base-quote`` separated with ``-``
51
+
52
+ .. note:: Throughout this library, the ``quote`` symbol will be
53
+ presented first (e.g. ``STEEM:SBD`` with ``STEEM`` being the
54
+ quote), while the ``base`` only refers to a secondary asset
55
+ for a trade. This means, if you call
56
+ :func:`nectar.market.Market.sell` or
57
+ :func:`nectar.market.Market.buy`, you will sell/buy **only
58
+ quote** and obtain/pay **only base**.
59
+
60
+ """
61
+
62
+ def __init__(self, base=None, quote=None, blockchain_instance=None, **kwargs):
63
+ """
64
+ Init Market
65
+
66
+ :param nectar.steem.Steem blockchain_instance: Steem instance
67
+ :param nectar.asset.Asset base: Base asset
68
+ :param nectar.asset.Asset quote: Quote asset
69
+ """
70
+ if blockchain_instance is None:
71
+ if kwargs.get("steem_instance"):
72
+ blockchain_instance = kwargs["steem_instance"]
73
+ elif kwargs.get("hive_instance"):
74
+ blockchain_instance = kwargs["hive_instance"]
75
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
76
+
77
+ if quote is None and isinstance(base, str):
78
+ quote_symbol, base_symbol = assets_from_string(base)
79
+ quote = Asset(quote_symbol, blockchain_instance=self.blockchain)
80
+ base = Asset(base_symbol, blockchain_instance=self.blockchain)
81
+ super(Market, self).__init__(
82
+ {"base": base, "quote": quote}, blockchain_instance=self.blockchain
83
+ )
84
+ elif base and quote:
85
+ quote = Asset(quote, blockchain_instance=self.blockchain)
86
+ base = Asset(base, blockchain_instance=self.blockchain)
87
+ super(Market, self).__init__(
88
+ {"base": base, "quote": quote}, blockchain_instance=self.blockchain
89
+ )
90
+ elif base is None and quote is None:
91
+ quote = Asset(self.blockchain.backed_token_symbol, blockchain_instance=self.blockchain)
92
+ base = Asset(self.blockchain.token_symbol, blockchain_instance=self.blockchain)
93
+ super(Market, self).__init__(
94
+ {"base": base, "quote": quote}, blockchain_instance=self.blockchain
95
+ )
96
+ else:
97
+ raise ValueError("Unknown Market config")
98
+
99
+ def get_string(self, separator=":"):
100
+ """Return a formated string that identifies the market, e.g. ``STEEM:SBD``
101
+
102
+ :param str separator: The separator of the assets (defaults to ``:``)
103
+ """
104
+ return "%s%s%s" % (self["quote"]["symbol"], separator, self["base"]["symbol"])
105
+
106
+ def __eq__(self, other):
107
+ if isinstance(other, str):
108
+ quote_symbol, base_symbol = assets_from_string(other)
109
+ return (
110
+ self["quote"]["symbol"] == quote_symbol and self["base"]["symbol"] == base_symbol
111
+ ) or (self["quote"]["symbol"] == base_symbol and self["base"]["symbol"] == quote_symbol)
112
+ elif isinstance(other, Market):
113
+ return (
114
+ self["quote"]["symbol"] == other["quote"]["symbol"]
115
+ and self["base"]["symbol"] == other["base"]["symbol"]
116
+ )
117
+
118
+ def ticker(self, raw_data=False):
119
+ """Returns the ticker for all markets.
120
+
121
+ Output Parameters:
122
+
123
+ * ``latest``: Price of the order last filled
124
+ * ``lowest_ask``: Price of the lowest ask
125
+ * ``highest_bid``: Price of the highest bid
126
+ * ``sbd_volume``: Volume of SBD
127
+ * ``steem_volume``: Volume of STEEM
128
+ * ``hbd_volume``: Volume of HBD
129
+ * ``hive_volume``: Volume of HIVE
130
+ * ``percent_change``: 24h change percentage (in %)
131
+
132
+ .. note::
133
+ Market is HIVE:HBD and prices are HBD per HIVE!
134
+
135
+ Sample Output:
136
+
137
+ .. code-block:: js
138
+
139
+ {
140
+ 'highest_bid': 0.30100226633322913,
141
+ 'latest': 0.0,
142
+ 'lowest_ask': 0.3249636958897082,
143
+ 'percent_change': 0.0,
144
+ 'sbd_volume': 108329611.0,
145
+ 'steem_volume': 355094043.0
146
+ }
147
+
148
+ """
149
+ data = {}
150
+ # Core Exchange rate
151
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
152
+ ticker = self.blockchain.rpc.get_ticker(api="market_history")
153
+
154
+ if raw_data:
155
+ return ticker
156
+
157
+ data["highest_bid"] = Price(
158
+ ticker["highest_bid"],
159
+ base=self["base"],
160
+ quote=self["quote"],
161
+ blockchain_instance=self.blockchain,
162
+ )
163
+ data["latest"] = Price(
164
+ ticker["latest"],
165
+ quote=self["quote"],
166
+ base=self["base"],
167
+ blockchain_instance=self.blockchain,
168
+ )
169
+ data["lowest_ask"] = Price(
170
+ ticker["lowest_ask"],
171
+ base=self["base"],
172
+ quote=self["quote"],
173
+ blockchain_instance=self.blockchain,
174
+ )
175
+ data["percent_change"] = float(ticker["percent_change"])
176
+ if "sbd_volume" in ticker:
177
+ data["sbd_volume"] = Amount(ticker["sbd_volume"], blockchain_instance=self.blockchain)
178
+ elif "hbd_volume" in ticker:
179
+ data["hbd_volume"] = Amount(ticker["hbd_volume"], blockchain_instance=self.blockchain)
180
+ if "steem_volume" in ticker:
181
+ data["steem_volume"] = Amount(
182
+ ticker["steem_volume"], blockchain_instance=self.blockchain
183
+ )
184
+ elif "hive_volume" in ticker:
185
+ data["hive_volume"] = Amount(ticker["hive_volume"], blockchain_instance=self.blockchain)
186
+
187
+ return data
188
+
189
+ def volume24h(self, raw_data=False):
190
+ """Returns the 24-hour volume for all markets, plus totals for primary currencies.
191
+
192
+ Sample output:
193
+
194
+ .. code-block:: js
195
+
196
+ {
197
+ "STEEM": 361666.63617,
198
+ "SBD": 1087.0
199
+ }
200
+
201
+ """
202
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
203
+ volume = self.blockchain.rpc.get_volume(api="market_history")
204
+ if raw_data:
205
+ return volume
206
+ if "sbd_volume" in volume and "steem_volume" in volume:
207
+ return {
208
+ self["base"]["symbol"]: Amount(
209
+ volume["sbd_volume"], blockchain_instance=self.blockchain
210
+ ),
211
+ self["quote"]["symbol"]: Amount(
212
+ volume["steem_volume"], blockchain_instance=self.blockchain
213
+ ),
214
+ }
215
+ elif "hbd_volume" in volume and "hive_volume" in volume:
216
+ return {
217
+ self["base"]["symbol"]: Amount(
218
+ volume["hbd_volume"], blockchain_instance=self.blockchain
219
+ ),
220
+ self["quote"]["symbol"]: Amount(
221
+ volume["hive_volume"], blockchain_instance=self.blockchain
222
+ ),
223
+ }
224
+
225
+ def orderbook(self, limit=25, raw_data=False):
226
+ """Returns the order book for SBD/STEEM market.
227
+
228
+ :param int limit: Limit the amount of orders (default: 25)
229
+
230
+ Sample output (raw_data=False):
231
+
232
+ .. code-block:: none
233
+
234
+ {
235
+ 'asks': [
236
+ 380.510 STEEM 460.291 SBD @ 1.209669 SBD/STEEM,
237
+ 53.785 STEEM 65.063 SBD @ 1.209687 SBD/STEEM
238
+ ],
239
+ 'bids': [
240
+ 0.292 STEEM 0.353 SBD @ 1.208904 SBD/STEEM,
241
+ 8.498 STEEM 10.262 SBD @ 1.207578 SBD/STEEM
242
+ ],
243
+ 'asks_date': [
244
+ datetime.datetime(2018, 4, 30, 21, 7, 24, tzinfo=<UTC>),
245
+ datetime.datetime(2018, 4, 30, 18, 12, 18, tzinfo=<UTC>)
246
+ ],
247
+ 'bids_date': [
248
+ datetime.datetime(2018, 4, 30, 21, 1, 21, tzinfo=<UTC>),
249
+ datetime.datetime(2018, 4, 30, 20, 38, 21, tzinfo=<UTC>)
250
+ ]
251
+ }
252
+
253
+ Sample output (raw_data=True):
254
+
255
+ .. code-block:: js
256
+
257
+ {
258
+ 'asks': [
259
+ {
260
+ 'order_price': {'base': '8.000 STEEM', 'quote': '9.618 SBD'},
261
+ 'real_price': '1.20225000000000004',
262
+ 'steem': 4565,
263
+ 'sbd': 5488,
264
+ 'created': '2018-04-30T21:12:45'
265
+ }
266
+ ],
267
+ 'bids': [
268
+ {
269
+ 'order_price': {'base': '10.000 SBD', 'quote': '8.333 STEEM'},
270
+ 'real_price': '1.20004800192007677',
271
+ 'steem': 8333,
272
+ 'sbd': 10000,
273
+ 'created': '2018-04-30T20:29:33'
274
+ }
275
+ ]
276
+ }
277
+
278
+ .. note:: Each bid is an instance of
279
+ class:`nectar.price.Order` and thus carries the keys
280
+ ``base``, ``quote`` and ``price``. From those you can
281
+ obtain the actual amounts for sale
282
+
283
+ """
284
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
285
+ if self.blockchain.rpc.get_use_appbase():
286
+ orders = self.blockchain.rpc.get_order_book({"limit": limit}, api="market_history")
287
+ else:
288
+ orders = self.blockchain.rpc.get_order_book(limit, api="database_api")
289
+ if raw_data:
290
+ return orders
291
+ asks = list(
292
+ [
293
+ Order(
294
+ Amount(x["order_price"]["quote"], blockchain_instance=self.blockchain),
295
+ Amount(x["order_price"]["base"], blockchain_instance=self.blockchain),
296
+ blockchain_instance=self.blockchain,
297
+ )
298
+ for x in orders["asks"]
299
+ ]
300
+ )
301
+ bids = list(
302
+ [
303
+ Order(
304
+ Amount(x["order_price"]["quote"], blockchain_instance=self.blockchain),
305
+ Amount(x["order_price"]["base"], blockchain_instance=self.blockchain),
306
+ blockchain_instance=self.blockchain,
307
+ ).invert()
308
+ for x in orders["bids"]
309
+ ]
310
+ )
311
+ asks_date = list([formatTimeString(x["created"]) for x in orders["asks"]])
312
+ bids_date = list([formatTimeString(x["created"]) for x in orders["bids"]])
313
+ data = {"asks": asks, "bids": bids, "asks_date": asks_date, "bids_date": bids_date}
314
+ return data
315
+
316
+ def recent_trades(self, limit=25, raw_data=False):
317
+ """Returns the order book for a given market. You may also
318
+ specify "all" to get the orderbooks of all markets.
319
+
320
+ :param int limit: Limit the amount of orders (default: 25)
321
+ :param bool raw_data: when False, FilledOrder objects will be
322
+ returned
323
+
324
+ Sample output (raw_data=False):
325
+
326
+ .. code-block:: none
327
+
328
+ [
329
+ (2018-04-30 21:00:54+00:00) 0.267 STEEM 0.323 SBD @ 1.209738 SBD/STEEM,
330
+ (2018-04-30 20:59:30+00:00) 0.131 STEEM 0.159 SBD @ 1.213740 SBD/STEEM,
331
+ (2018-04-30 20:55:45+00:00) 0.093 STEEM 0.113 SBD @ 1.215054 SBD/STEEM,
332
+ (2018-04-30 20:55:30+00:00) 26.501 STEEM 32.058 SBD @ 1.209690 SBD/STEEM,
333
+ (2018-04-30 20:55:18+00:00) 2.108 STEEM 2.550 SBD @ 1.209677 SBD/STEEM,
334
+ ]
335
+
336
+ Sample output (raw_data=True):
337
+
338
+ .. code-block:: js
339
+
340
+ [
341
+ {'date': '2018-04-30T21:02:45', 'current_pays': '0.235 SBD', 'open_pays': '0.194 STEEM'},
342
+ {'date': '2018-04-30T21:02:03', 'current_pays': '24.494 SBD', 'open_pays': '20.248 STEEM'},
343
+ {'date': '2018-04-30T20:48:30', 'current_pays': '175.464 STEEM', 'open_pays': '211.955 SBD'},
344
+ {'date': '2018-04-30T20:48:30', 'current_pays': '0.999 STEEM', 'open_pays': '1.207 SBD'},
345
+ {'date': '2018-04-30T20:47:54', 'current_pays': '0.273 SBD', 'open_pays': '0.225 STEEM'},
346
+ ]
347
+
348
+ .. note:: Each bid is an instance of
349
+ :class:`nectar.price.Order` and thus carries the keys
350
+ ``base``, ``quote`` and ``price``. From those you can
351
+ obtain the actual amounts for sale
352
+
353
+ """
354
+ self.blockchain.rpc.set_next_node_on_empty_reply(limit > 0)
355
+ if self.blockchain.rpc.get_use_appbase():
356
+ orders = self.blockchain.rpc.get_recent_trades({"limit": limit}, api="market_history")[
357
+ "trades"
358
+ ]
359
+ else:
360
+ orders = self.blockchain.rpc.get_recent_trades(limit, api="market_history")
361
+ if raw_data:
362
+ return orders
363
+ filled_order = list([FilledOrder(x, blockchain_instance=self.blockchain) for x in orders])
364
+ return filled_order
365
+
366
+ def trade_history(self, start=None, stop=None, intervall=None, limit=25, raw_data=False):
367
+ """Returns the trade history for the internal market
368
+
369
+ This function allows to fetch a fixed number of trades at fixed
370
+ intervall times to reduce the call duration time. E.g. it is possible to
371
+ receive the trades from the last 7 days, by fetching 100 trades each 6 hours.
372
+
373
+ When intervall is set to None, all trades are received between start and stop.
374
+ This can take a while.
375
+
376
+ :param datetime start: Start date
377
+ :param datetime stop: Stop date
378
+ :param timedelta intervall: Defines the intervall
379
+ :param int limit: Defines how many trades are fetched at each intervall point
380
+ :param bool raw_data: when True, the raw data are returned
381
+ """
382
+ utc = timezone.utc
383
+ if not stop:
384
+ stop = utc.localize(datetime.now())
385
+ if not start:
386
+ start = stop - timedelta(hours=1)
387
+ start = addTzInfo(start)
388
+ stop = addTzInfo(stop)
389
+ current_start = start
390
+ filled_order = []
391
+ fo = self.trades(start=current_start, stop=stop, limit=limit, raw_data=raw_data)
392
+ if intervall is None and len(fo) > 0:
393
+ current_start = fo[-1]["date"]
394
+ filled_order += fo
395
+ elif intervall is not None:
396
+ current_start += intervall
397
+ filled_order += [fo]
398
+ last_date = fo[-1]["date"]
399
+ while len(fo) > 0 and last_date < stop:
400
+ fo = self.trades(start=current_start, stop=stop, limit=limit, raw_data=raw_data)
401
+ if len(fo) == 0 or fo[-1]["date"] == last_date:
402
+ break
403
+ last_date = fo[-1]["date"]
404
+ if intervall is None:
405
+ current_start = last_date
406
+ filled_order += fo
407
+ else:
408
+ current_start += intervall
409
+ filled_order += [fo]
410
+ return filled_order
411
+
412
+ def trades(self, limit=100, start=None, stop=None, raw_data=False):
413
+ """Returns your trade history for a given market.
414
+
415
+ :param int limit: Limit the amount of orders (default: 100)
416
+ :param datetime start: start time
417
+ :param datetime stop: stop time
418
+
419
+ """
420
+ # FIXME, this call should also return whether it was a buy or
421
+ # sell
422
+ utc = timezone.utc
423
+ if not stop:
424
+ stop = utc.localize(datetime.now())
425
+ if not start:
426
+ start = stop - timedelta(hours=24)
427
+ start = addTzInfo(start)
428
+ stop = addTzInfo(stop)
429
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
430
+ if self.blockchain.rpc.get_use_appbase():
431
+ orders = self.blockchain.rpc.get_trade_history(
432
+ {"start": formatTimeString(start), "end": formatTimeString(stop), "limit": limit},
433
+ api="market_history",
434
+ )["trades"]
435
+ else:
436
+ orders = self.blockchain.rpc.get_trade_history(
437
+ formatTimeString(start), formatTimeString(stop), limit, api="market_history"
438
+ )
439
+ if raw_data:
440
+ return orders
441
+ filled_order = list([FilledOrder(x, blockchain_instance=self.blockchain) for x in orders])
442
+ return filled_order
443
+
444
+ def market_history_buckets(self):
445
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
446
+ ret = self.blockchain.rpc.get_market_history_buckets(api="market_history")
447
+ if self.blockchain.rpc.get_use_appbase():
448
+ return ret["bucket_sizes"]
449
+ else:
450
+ return ret
451
+
452
+ def market_history(self, bucket_seconds=300, start_age=3600, end_age=0, raw_data=False):
453
+ """Return the market history (filled orders).
454
+
455
+ :param int bucket_seconds: Bucket size in seconds (see
456
+ `returnMarketHistoryBuckets()`)
457
+ :param int start_age: Age (in seconds) of the start of the
458
+ window (default: 1h/3600)
459
+ :param int end_age: Age (in seconds) of the end of the window
460
+ (default: now/0)
461
+ :param bool raw_data: (optional) returns raw data if set True
462
+
463
+ Example:
464
+
465
+ .. code-block:: js
466
+
467
+ {
468
+ 'close_sbd': 2493387,
469
+ 'close_steem': 7743431,
470
+ 'high_sbd': 1943872,
471
+ 'high_steem': 5999610,
472
+ 'id': '7.1.5252',
473
+ 'low_sbd': 534928,
474
+ 'low_steem': 1661266,
475
+ 'open': '2016-07-08T11:25:00',
476
+ 'open_sbd': 534928,
477
+ 'open_steem': 1661266,
478
+ 'sbd_volume': 9714435,
479
+ 'seconds': 300,
480
+ 'steem_volume': 30088443
481
+ }
482
+
483
+ """
484
+ buckets = self.market_history_buckets()
485
+ if bucket_seconds < 5 and bucket_seconds >= 0:
486
+ bucket_seconds = buckets[bucket_seconds]
487
+ else:
488
+ if bucket_seconds not in buckets:
489
+ raise ValueError("You need select the bucket_seconds from " + str(buckets))
490
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
491
+ if self.blockchain.rpc.get_use_appbase():
492
+ history = self.blockchain.rpc.get_market_history(
493
+ {
494
+ "bucket_seconds": bucket_seconds,
495
+ "start": formatTimeFromNow(-start_age - end_age),
496
+ "end": formatTimeFromNow(-end_age),
497
+ },
498
+ api="market_history",
499
+ )["buckets"]
500
+ else:
501
+ history = self.blockchain.rpc.get_market_history(
502
+ bucket_seconds,
503
+ formatTimeFromNow(-start_age - end_age),
504
+ formatTimeFromNow(-end_age),
505
+ api="market_history",
506
+ )
507
+ if raw_data:
508
+ return history
509
+ new_history = []
510
+ for h in history:
511
+ if "open" in h and isinstance(h.get("open"), str):
512
+ h["open"] = formatTimeString(h.get("open", "1970-01-01T00:00:00"))
513
+ new_history.append(h)
514
+ return new_history
515
+
516
+ def accountopenorders(self, account=None, raw_data=False):
517
+ """Returns open Orders
518
+
519
+ :param Account account: Account name or instance of Account to show orders for in this market
520
+ :param bool raw_data: (optional) returns raw data if set True,
521
+ or a list of Order() instances if False (defaults to False)
522
+ """
523
+ if not account:
524
+ if "default_account" in self.blockchain.config:
525
+ account = self.blockchain.config["default_account"]
526
+ if not account:
527
+ raise ValueError("You need to provide an account")
528
+ account = Account(account, full=True, blockchain_instance=self.blockchain)
529
+
530
+ r = []
531
+ # orders = account["limit_orders"]
532
+ if not self.blockchain.is_connected():
533
+ return None
534
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
535
+ if self.blockchain.rpc.get_use_appbase():
536
+ orders = self.blockchain.rpc.find_limit_orders(
537
+ {"account": account["name"]}, api="database"
538
+ )["orders"]
539
+ else:
540
+ orders = self.blockchain.rpc.get_open_orders(account["name"])
541
+ if raw_data:
542
+ return orders
543
+ for o in orders:
544
+ order = {}
545
+ order["order"] = Order(
546
+ Amount(o["sell_price"]["base"], blockchain_instance=self.blockchain),
547
+ Amount(o["sell_price"]["quote"], blockchain_instance=self.blockchain),
548
+ blockchain_instance=self.blockchain,
549
+ )
550
+ order["orderid"] = o["orderid"]
551
+ order["created"] = formatTimeString(o["created"])
552
+ r.append(order)
553
+ return r
554
+
555
+ def buy(
556
+ self,
557
+ price,
558
+ amount,
559
+ expiration=None,
560
+ killfill=False,
561
+ account=None,
562
+ orderid=None,
563
+ returnOrderId=False,
564
+ ):
565
+ """Places a buy order in a given market
566
+
567
+ :param float price: price denoted in ``base``/``quote``
568
+ :param number amount: Amount of ``quote`` to buy
569
+ :param number expiration: (optional) expiration time of the order in seconds (defaults to 7 days)
570
+ :param bool killfill: flag that indicates if the order shall be killed if it is not filled (defaults to False)
571
+ :param string account: Account name that executes that order
572
+ :param string returnOrderId: If set to "head" or "irreversible" the call will wait for the tx to appear in
573
+ the head/irreversible block and add the key "orderid" to the tx output
574
+
575
+ Prices/Rates are denoted in 'base', i.e. the SBD_STEEM market
576
+ is priced in STEEM per SBD.
577
+
578
+ **Example:** in the SBD_STEEM market, a price of 300 means
579
+ a SBD is worth 300 STEEM
580
+
581
+ .. note::
582
+
583
+ All prices returned are in the **reversed** orientation as the
584
+ market. I.e. in the STEEM/SBD market, prices are SBD per STEEM.
585
+ That way you can multiply prices with `1.05` to get a +5%.
586
+
587
+ .. warning::
588
+
589
+ Since buy orders are placed as
590
+ limit-sell orders for the base asset,
591
+ you may end up obtaining more of the
592
+ buy asset than you placed the order
593
+ for. Example:
594
+
595
+ * You place and order to buy 10 SBD for 100 STEEM/SBD
596
+ * This means that you actually place a sell order for 1000 STEEM in order to obtain **at least** 10 SBD
597
+ * If an order on the market exists that sells SBD for cheaper, you will end up with more than 10 SBD
598
+ """
599
+ if not expiration:
600
+ expiration = self.blockchain.config["order-expiration"]
601
+ if not account:
602
+ if "default_account" in self.blockchain.config:
603
+ account = self.blockchain.config["default_account"]
604
+ if not account:
605
+ raise ValueError("You need to provide an account")
606
+ account = Account(account, blockchain_instance=self.blockchain)
607
+
608
+ if isinstance(price, Price):
609
+ price = price.as_base(self["base"]["symbol"])
610
+
611
+ if isinstance(amount, Amount):
612
+ amount = Amount(amount, blockchain_instance=self.blockchain)
613
+ if not amount["asset"]["symbol"] == self["quote"]["symbol"]:
614
+ raise AssertionError(
615
+ "Price: {} does not match amount: {}".format(str(price), str(amount))
616
+ )
617
+ elif isinstance(amount, str):
618
+ amount = Amount(amount, blockchain_instance=self.blockchain)
619
+ else:
620
+ amount = Amount(amount, self["quote"]["symbol"], blockchain_instance=self.blockchain)
621
+ order = operations.Limit_order_create(
622
+ **{
623
+ "owner": account["name"],
624
+ "orderid": orderid or random.getrandbits(32),
625
+ "amount_to_sell": Amount(
626
+ float(amount) * float(price),
627
+ self["base"]["symbol"],
628
+ blockchain_instance=self.blockchain,
629
+ ),
630
+ "min_to_receive": Amount(
631
+ float(amount), self["quote"]["symbol"], blockchain_instance=self.blockchain
632
+ ),
633
+ "expiration": formatTimeFromNow(expiration),
634
+ "fill_or_kill": killfill,
635
+ "prefix": self.blockchain.prefix,
636
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
637
+ }
638
+ )
639
+
640
+ if returnOrderId:
641
+ # Make blocking broadcasts
642
+ prevblocking = self.blockchain.blocking
643
+ self.blockchain.blocking = returnOrderId
644
+
645
+ tx = self.blockchain.finalizeOp(order, account["name"], "active")
646
+
647
+ if returnOrderId:
648
+ tx["orderid"] = tx["operation_results"][0][1]
649
+ self.blockchain.blocking = prevblocking
650
+
651
+ return tx
652
+
653
+ def sell(
654
+ self,
655
+ price,
656
+ amount,
657
+ expiration=None,
658
+ killfill=False,
659
+ account=None,
660
+ orderid=None,
661
+ returnOrderId=False,
662
+ ):
663
+ """Places a sell order in a given market
664
+
665
+ :param float price: price denoted in ``base``/``quote``
666
+ :param number amount: Amount of ``quote`` to sell
667
+ :param number expiration: (optional) expiration time of the order in seconds (defaults to 7 days)
668
+ :param bool killfill: flag that indicates if the order shall be killed if it is not filled (defaults to False)
669
+ :param string account: Account name that executes that order
670
+ :param string returnOrderId: If set to "head" or "irreversible" the call will wait for the tx to appear in
671
+ the head/irreversible block and add the key "orderid" to the tx output
672
+
673
+ Prices/Rates are denoted in 'base', i.e. the SBD_STEEM market
674
+ is priced in STEEM per SBD.
675
+
676
+ **Example:** in the SBD_STEEM market, a price of 300 means
677
+ a SBD is worth 300 STEEM
678
+
679
+ .. note::
680
+
681
+ All prices returned are in the **reversed** orientation as the
682
+ market. I.e. in the STEEM/SBD market, prices are SBD per STEEM.
683
+ That way you can multiply prices with `1.05` to get a +5%.
684
+ """
685
+ if not expiration:
686
+ expiration = self.blockchain.config["order-expiration"]
687
+ if not account:
688
+ if "default_account" in self.blockchain.config:
689
+ account = self.blockchain.config["default_account"]
690
+ if not account:
691
+ raise ValueError("You need to provide an account")
692
+ account = Account(account, blockchain_instance=self.blockchain)
693
+ if isinstance(price, Price):
694
+ price = price.as_base(self["base"]["symbol"])
695
+
696
+ if isinstance(amount, Amount):
697
+ amount = Amount(amount, blockchain_instance=self.blockchain)
698
+ if not amount["asset"]["symbol"] == self["quote"]["symbol"]:
699
+ raise AssertionError(
700
+ "Price: {} does not match amount: {}".format(str(price), str(amount))
701
+ )
702
+ elif isinstance(amount, str):
703
+ amount = Amount(amount, blockchain_instance=self.blockchain)
704
+ else:
705
+ amount = Amount(amount, self["quote"]["symbol"], blockchain_instance=self.blockchain)
706
+ order = operations.Limit_order_create(
707
+ **{
708
+ "owner": account["name"],
709
+ "orderid": orderid or random.getrandbits(32),
710
+ "amount_to_sell": Amount(
711
+ float(amount), self["quote"]["symbol"], blockchain_instance=self.blockchain
712
+ ),
713
+ "min_to_receive": Amount(
714
+ float(amount) * float(price),
715
+ self["base"]["symbol"],
716
+ blockchain_instance=self.blockchain,
717
+ ),
718
+ "expiration": formatTimeFromNow(expiration),
719
+ "fill_or_kill": killfill,
720
+ "prefix": self.blockchain.prefix,
721
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
722
+ }
723
+ )
724
+ if returnOrderId:
725
+ # Make blocking broadcasts
726
+ prevblocking = self.blockchain.blocking
727
+ self.blockchain.blocking = returnOrderId
728
+
729
+ tx = self.blockchain.finalizeOp(order, account["name"], "active")
730
+
731
+ if returnOrderId:
732
+ tx["orderid"] = tx["operation_results"][0][1]
733
+ self.blockchain.blocking = prevblocking
734
+
735
+ return tx
736
+
737
+ def cancel(self, orderNumbers, account=None, **kwargs):
738
+ """Cancels an order you have placed in a given market. Requires
739
+ only the "orderNumbers".
740
+
741
+ :param orderNumbers: A single order number or a list of order numbers
742
+ :type orderNumbers: int, list
743
+ """
744
+ if not account:
745
+ if "default_account" in self.blockchain.config:
746
+ account = self.blockchain.config["default_account"]
747
+ if not account:
748
+ raise ValueError("You need to provide an account")
749
+ account = Account(account, full=False, blockchain_instance=self.blockchain)
750
+
751
+ if not isinstance(orderNumbers, (list, set, tuple)):
752
+ orderNumbers = {orderNumbers}
753
+
754
+ op = []
755
+ for order in orderNumbers:
756
+ op.append(
757
+ operations.Limit_order_cancel(
758
+ **{"owner": account["name"], "orderid": order, "prefix": self.blockchain.prefix}
759
+ )
760
+ )
761
+ return self.blockchain.finalizeOp(op, account["name"], "active", **kwargs)
762
+
763
+ @staticmethod
764
+ def _weighted_average(values, weights):
765
+ """Calculates a weighted average"""
766
+ if not (len(values) == len(weights) and len(weights) > 0):
767
+ raise AssertionError("Length of both array must be the same and greater than zero!")
768
+ return sum(x * y for x, y in zip(values, weights)) / sum(weights)
769
+
770
+ @staticmethod
771
+ def btc_usd_ticker(verbose=False):
772
+ """Returns the BTC/USD price from bitfinex, gdax, kraken, okcoin and bitstamp. The mean price is
773
+ weighted by the exchange volume.
774
+ """
775
+ prices = {}
776
+ responses = []
777
+ urls = [
778
+ # "https://api.bitfinex.com/v1/pubticker/BTCUSD",
779
+ # "https://api.gdax.com/products/BTC-USD/ticker",
780
+ # "https://api.kraken.com/0/public/Ticker?pair=XBTUSD",
781
+ # "https://www.okcoin.com/api/v1/ticker.do?symbol=btc_usd",
782
+ # "https://www.bitstamp.net/api/v2/ticker/btcusd/",
783
+ "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_vol=true",
784
+ ]
785
+ cnt = 0
786
+ while len(prices) == 0 and cnt < 5:
787
+ cnt += 1
788
+ try:
789
+ responses = list(requests.get(u, timeout=30) for u in urls)
790
+ except Exception as e:
791
+ log.debug(str(e))
792
+
793
+ for r in [
794
+ x
795
+ for x in responses
796
+ if hasattr(x, "status_code") and x.status_code == 200 and x.json()
797
+ ]:
798
+ try:
799
+ if "bitfinex" in r.url:
800
+ data = r.json()
801
+ prices["bitfinex"] = {
802
+ "price": float(data["last_price"]),
803
+ "volume": float(data["volume"]),
804
+ }
805
+ elif "gdax" in r.url:
806
+ data = r.json()
807
+ prices["gdax"] = {
808
+ "price": float(data["price"]),
809
+ "volume": float(data["volume"]),
810
+ }
811
+ elif "kraken" in r.url:
812
+ data = r.json()["result"]["XXBTZUSD"]["p"]
813
+ prices["kraken"] = {"price": float(data[0]), "volume": float(data[1])}
814
+ elif "okcoin" in r.url:
815
+ data = r.json()["ticker"]
816
+ prices["okcoin"] = {
817
+ "price": float(data["last"]),
818
+ "volume": float(data["vol"]),
819
+ }
820
+ elif "bitstamp" in r.url:
821
+ data = r.json()
822
+ prices["bitstamp"] = {
823
+ "price": float(data["last"]),
824
+ "volume": float(data["volume"]),
825
+ }
826
+ elif "coingecko" in r.url:
827
+ data = r.json()["bitcoin"]
828
+ if "usd_24h_vol" in data:
829
+ volume = float(data["usd_24h_vol"])
830
+ else:
831
+ volume = 1
832
+ prices["coingecko"] = {"price": float(data["usd"]), "volume": volume}
833
+ except KeyError as e:
834
+ log.info(str(e))
835
+
836
+ if verbose:
837
+ print(prices)
838
+
839
+ if len(prices) == 0:
840
+ raise RuntimeError("Obtaining BTC/USD prices has failed from all sources.")
841
+
842
+ # vwap
843
+ return Market._weighted_average(
844
+ [x["price"] for x in prices.values()], [x["volume"] for x in prices.values()]
845
+ )
846
+
847
+ @staticmethod
848
+ def steem_btc_ticker():
849
+ """Returns the STEEM/BTC price from bittrex, binance, huobi and upbit. The mean price is
850
+ weighted by the exchange volume.
851
+ """
852
+ prices = {}
853
+ responses = []
854
+ urls = [
855
+ # "https://poloniex.com/public?command=returnTicker",
856
+ # "https://bittrex.com/api/v1.1/public/getmarketsummary?market=BTC-STEEM",
857
+ # "https://api.binance.com/api/v1/ticker/24hr",
858
+ # "https://api.huobi.pro/market/history/kline?period=1day&size=1&symbol=steembtc",
859
+ # "https://crix-api.upbit.com/v1/crix/trades/ticks?code=CRIX.UPBIT.BTC-STEEM&count=1",
860
+ "https://api.coingecko.com/api/v3/simple/price?ids=steem&vs_currencies=btc&include_24hr_vol=true",
861
+ ]
862
+ headers = {
863
+ "Content-type": "application/x-www-form-urlencoded",
864
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36",
865
+ }
866
+ cnt = 0
867
+ while len(prices) == 0 and cnt < 5:
868
+ cnt += 1
869
+ try:
870
+ responses = list(requests.get(u, headers=headers, timeout=30) for u in urls)
871
+ except Exception as e:
872
+ log.debug(str(e))
873
+
874
+ for r in [
875
+ x
876
+ for x in responses
877
+ if hasattr(x, "status_code") and x.status_code == 200 and x.json()
878
+ ]:
879
+ try:
880
+ if "poloniex" in r.url:
881
+ data = r.json()["BTC_STEEM"]
882
+ prices["poloniex"] = {
883
+ "price": float(data["last"]),
884
+ "volume": float(data["baseVolume"]),
885
+ }
886
+ elif "bittrex" in r.url:
887
+ data = r.json()["result"][0]
888
+ price = (data["Bid"] + data["Ask"]) / 2
889
+ prices["bittrex"] = {"price": price, "volume": data["BaseVolume"]}
890
+ elif "binance" in r.url:
891
+ data = [x for x in r.json() if x["symbol"] == "STEEMBTC"][0]
892
+ prices["binance"] = {
893
+ "price": float(data["lastPrice"]),
894
+ "volume": float(data["quoteVolume"]),
895
+ }
896
+ elif "huobi" in r.url:
897
+ data = r.json()["data"][-1]
898
+ prices["huobi"] = {
899
+ "price": float(data["close"]),
900
+ "volume": float(data["vol"]),
901
+ }
902
+ elif "upbit" in r.url:
903
+ data = r.json()[-1]
904
+ prices["upbit"] = {
905
+ "price": float(data["tradePrice"]),
906
+ "volume": float(data["tradeVolume"]),
907
+ }
908
+ elif "coingecko" in r.url:
909
+ data = r.json()["steem"]
910
+ if "usd_24h_vol" in data:
911
+ volume = float(data["usd_24h_vol"])
912
+ else:
913
+ volume = 1
914
+ prices["coingecko"] = {"price": float(data["btc"]), "volume": volume}
915
+ except KeyError as e:
916
+ log.info(str(e))
917
+
918
+ if len(prices) == 0:
919
+ raise RuntimeError("Obtaining STEEM/BTC prices has failed from all sources.")
920
+
921
+ return Market._weighted_average(
922
+ [x["price"] for x in prices.values()], [x["volume"] for x in prices.values()]
923
+ )
924
+
925
+ def steem_usd_implied(self):
926
+ """Returns the current STEEM/USD market price"""
927
+ return self.steem_btc_ticker() * self.btc_usd_ticker()
928
+
929
+ @staticmethod
930
+ def hive_btc_ticker():
931
+ """Returns the HIVE/BTC price from bittrex and upbit. The mean price is
932
+ weighted by the exchange volume.
933
+ """
934
+ prices = {}
935
+ responses = []
936
+ urls = [
937
+ # "https://bittrex.com/api/v1.1/public/getmarketsummary?market=BTC-HIVE",
938
+ # "https://api.binance.com/api/v1/ticker/24hr",
939
+ # "https://api.probit.com/api/exchange/v1/ticker?market_ids=HIVE-USDT",
940
+ "https://api.coingecko.com/api/v3/simple/price?ids=hive&vs_currencies=btc&include_24hr_vol=true",
941
+ ]
942
+ headers = {
943
+ "Content-type": "application/x-www-form-urlencoded",
944
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36",
945
+ }
946
+ cnt = 0
947
+ while len(prices) == 0 and cnt < 5:
948
+ cnt += 1
949
+ try:
950
+ responses = list(requests.get(u, headers=headers, timeout=30) for u in urls)
951
+ except Exception as e:
952
+ log.debug(str(e))
953
+
954
+ for r in [
955
+ x
956
+ for x in responses
957
+ if hasattr(x, "status_code") and x.status_code == 200 and x.json()
958
+ ]:
959
+ try:
960
+ if "poloniex" in r.url:
961
+ data = r.json()["BTC_HIVE"]
962
+ prices["poloniex"] = {
963
+ "price": float(data["last"]),
964
+ "volume": float(data["baseVolume"]),
965
+ }
966
+ elif "bittrex" in r.url:
967
+ data = r.json()["result"][0]
968
+ price = (data["Bid"] + data["Ask"]) / 2
969
+ prices["bittrex"] = {"price": price, "volume": data["BaseVolume"]}
970
+ elif "binance" in r.url:
971
+ data = [x for x in r.json() if x["symbol"] == "HIVEBTC"][0]
972
+ prices["binance"] = {
973
+ "price": float(data["lastPrice"]),
974
+ "volume": float(data["quoteVolume"]),
975
+ }
976
+ elif "huobi" in r.url:
977
+ data = r.json()["data"][-1]
978
+ prices["huobi"] = {
979
+ "price": float(data["close"]),
980
+ "volume": float(data["vol"]),
981
+ }
982
+ elif "upbit" in r.url:
983
+ data = r.json()[-1]
984
+ prices["upbit"] = {
985
+ "price": float(data["tradePrice"]),
986
+ "volume": float(data["tradeVolume"]),
987
+ }
988
+ elif "probit" in r.url:
989
+ data = r.json()["data"]
990
+ prices["huobi"] = {
991
+ "price": float(data["last"]),
992
+ "volume": float(data["base_volume"]),
993
+ }
994
+ elif "coingecko" in r.url:
995
+ data = r.json()["hive"]
996
+ if "btc_24h_vol":
997
+ volume = float(data["btc_24h_vol"])
998
+ else:
999
+ volume = 1
1000
+ prices["coingecko"] = {"price": float(data["btc"]), "volume": volume}
1001
+ except KeyError as e:
1002
+ log.info(str(e))
1003
+
1004
+ if len(prices) == 0:
1005
+ raise RuntimeError("Obtaining HIVE/BTC prices has failed from all sources.")
1006
+
1007
+ return Market._weighted_average(
1008
+ [x["price"] for x in prices.values()], [x["volume"] for x in prices.values()]
1009
+ )
1010
+
1011
+ def hive_usd_implied(self):
1012
+ """Returns the current HIVE/USD market price"""
1013
+ return self.hive_btc_ticker() * self.btc_usd_ticker()