hive-nectar 0.2.9__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.
- hive_nectar-0.2.9.dist-info/METADATA +194 -0
- hive_nectar-0.2.9.dist-info/RECORD +87 -0
- hive_nectar-0.2.9.dist-info/WHEEL +4 -0
- hive_nectar-0.2.9.dist-info/entry_points.txt +2 -0
- hive_nectar-0.2.9.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +37 -0
- nectar/account.py +5076 -0
- nectar/amount.py +553 -0
- nectar/asciichart.py +303 -0
- nectar/asset.py +122 -0
- nectar/block.py +574 -0
- nectar/blockchain.py +1242 -0
- nectar/blockchaininstance.py +2590 -0
- nectar/blockchainobject.py +263 -0
- nectar/cli.py +5937 -0
- nectar/comment.py +1552 -0
- nectar/community.py +854 -0
- nectar/constants.py +95 -0
- nectar/discussions.py +1437 -0
- nectar/exceptions.py +152 -0
- nectar/haf.py +381 -0
- nectar/hive.py +630 -0
- nectar/imageuploader.py +114 -0
- nectar/instance.py +113 -0
- nectar/market.py +876 -0
- nectar/memo.py +542 -0
- nectar/message.py +379 -0
- nectar/nodelist.py +309 -0
- nectar/price.py +603 -0
- nectar/profile.py +74 -0
- nectar/py.typed +0 -0
- nectar/rc.py +333 -0
- nectar/snapshot.py +1024 -0
- nectar/storage.py +62 -0
- nectar/transactionbuilder.py +659 -0
- nectar/utils.py +630 -0
- nectar/version.py +3 -0
- nectar/vote.py +722 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +728 -0
- nectarapi/__init__.py +12 -0
- nectarapi/exceptions.py +126 -0
- nectarapi/graphenerpc.py +596 -0
- nectarapi/node.py +194 -0
- nectarapi/noderpc.py +79 -0
- nectarapi/openapi.py +107 -0
- nectarapi/py.typed +0 -0
- nectarapi/rpcutils.py +98 -0
- nectarapi/version.py +3 -0
- nectarbase/__init__.py +15 -0
- nectarbase/ledgertransactions.py +106 -0
- nectarbase/memo.py +242 -0
- nectarbase/objects.py +521 -0
- nectarbase/objecttypes.py +21 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1357 -0
- nectarbase/py.typed +0 -0
- nectarbase/signedtransactions.py +89 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +3 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +1121 -0
- nectargraphenebase/aes.py +49 -0
- nectargraphenebase/base58.py +197 -0
- nectargraphenebase/bip32.py +575 -0
- nectargraphenebase/bip38.py +110 -0
- nectargraphenebase/chains.py +15 -0
- nectargraphenebase/dictionary.py +2 -0
- nectargraphenebase/ecdsasig.py +309 -0
- nectargraphenebase/objects.py +130 -0
- nectargraphenebase/objecttypes.py +8 -0
- nectargraphenebase/operationids.py +5 -0
- nectargraphenebase/operations.py +25 -0
- nectargraphenebase/prefix.py +13 -0
- nectargraphenebase/py.typed +0 -0
- nectargraphenebase/signedtransactions.py +221 -0
- nectargraphenebase/types.py +557 -0
- nectargraphenebase/unsignedtransactions.py +288 -0
- nectargraphenebase/version.py +3 -0
- nectarstorage/__init__.py +57 -0
- nectarstorage/base.py +317 -0
- nectarstorage/exceptions.py +15 -0
- nectarstorage/interfaces.py +244 -0
- nectarstorage/masterpassword.py +237 -0
- nectarstorage/py.typed +0 -0
- nectarstorage/ram.py +27 -0
- nectarstorage/sqlite.py +343 -0
nectar/price.py
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
from nectar.instance import shared_blockchain_instance
|
|
6
|
+
|
|
7
|
+
from .amount import Amount, check_asset
|
|
8
|
+
from .asset import Asset
|
|
9
|
+
from .exceptions import InvalidAssetException
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .market import Market
|
|
13
|
+
from .utils import assets_from_string, formatTimeString
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Price(dict):
|
|
17
|
+
"""This class deals with all sorts of prices of any pair of assets to
|
|
18
|
+
simplify dealing with the tuple::
|
|
19
|
+
|
|
20
|
+
(quote, base)
|
|
21
|
+
|
|
22
|
+
each being an instance of :class:`nectar.amount.Amount`. The
|
|
23
|
+
amount themselves define the price.
|
|
24
|
+
|
|
25
|
+
.. note::
|
|
26
|
+
|
|
27
|
+
The price (floating) is derived as ``base/quote``
|
|
28
|
+
|
|
29
|
+
:param list args: Allows to deal with different representations of a price
|
|
30
|
+
:param Asset base: Base asset
|
|
31
|
+
:param Asset quote: Quote asset
|
|
32
|
+
:param Hive blockchain_instance: Hive instance
|
|
33
|
+
:returns: All data required to represent a price
|
|
34
|
+
:rtype: dictionary
|
|
35
|
+
|
|
36
|
+
Way to obtain a proper instance:
|
|
37
|
+
|
|
38
|
+
* ``args`` is a str with a price and two assets
|
|
39
|
+
* ``args`` can be a floating number and ``base`` and ``quote`` being instances of :class:`nectar.asset.Asset`
|
|
40
|
+
* ``args`` can be a floating number and ``base`` and ``quote`` being instances of ``str``
|
|
41
|
+
* ``args`` can be dict with keys ``price``, ``base``, and ``quote`` (*graphene balances*)
|
|
42
|
+
* ``args`` can be dict with keys ``base`` and ``quote``
|
|
43
|
+
* ``args`` can be dict with key ``receives`` (filled orders)
|
|
44
|
+
* ``args`` being a list of ``[quote, base]`` both being instances of :class:`nectar.amount.Amount`
|
|
45
|
+
* ``args`` being a list of ``[quote, base]`` both being instances of ``str`` (``amount symbol``)
|
|
46
|
+
* ``base`` and ``quote`` being instances of :class:`nectar.asset.Amount`
|
|
47
|
+
|
|
48
|
+
This allows instantiations like:
|
|
49
|
+
|
|
50
|
+
* ``Price("0.315 HBD/HIVE")``
|
|
51
|
+
* ``Price(0.315, base="HBD", quote="HIVE")``
|
|
52
|
+
* ``Price(0.315, base=Asset("HBD"), quote=Asset("HIVE"))``
|
|
53
|
+
* ``Price({"base": {"amount": 1, "asset_id": "HBD"}, "quote": {"amount": 10, "asset_id": "HBD"}})``
|
|
54
|
+
* ``Price(quote="10 HIVE", base="1 HBD")``
|
|
55
|
+
* ``Price("10 HIVE", "1 HBD")``
|
|
56
|
+
* ``Price(Amount("10 HIVE"), Amount("1 HBD"))``
|
|
57
|
+
* ``Price(1.0, "HBD/HIVE")``
|
|
58
|
+
|
|
59
|
+
Instances of this class can be used in regular mathematical expressions
|
|
60
|
+
(``+-*/%``) such as:
|
|
61
|
+
|
|
62
|
+
.. code-block:: python
|
|
63
|
+
|
|
64
|
+
>>> from nectar.price import Price
|
|
65
|
+
>>> from nectar import Hive
|
|
66
|
+
>>> hv = Hive("https://api.hive.blog")
|
|
67
|
+
>>> Price("0.3314 HBD/HIVE", blockchain_instance=hv) * 2
|
|
68
|
+
0.662804 HBD/HIVE
|
|
69
|
+
>>> Price(0.3314, "HBD", "HIVE", blockchain_instance=hv)
|
|
70
|
+
0.331402 HBD/HIVE
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
price: Optional[Union[str, Dict[str, Any], "Price"]] = None,
|
|
77
|
+
base: Optional[Union[str, Amount, Asset]] = None,
|
|
78
|
+
quote: Optional[Union[str, Amount, Asset]] = None,
|
|
79
|
+
base_asset: Optional[str] = None, # to identify sell/buy
|
|
80
|
+
blockchain_instance: Optional[Any] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Initialize a Price object representing a ratio between a base and quote asset.
|
|
84
|
+
|
|
85
|
+
This constructor accepts multiple input forms and normalizes them into internal
|
|
86
|
+
"base" and "quote" Amount entries. Supported usages:
|
|
87
|
+
- price: str like "X BASE/QUOTE" with no base/quote: parses symbols and creates
|
|
88
|
+
Amounts from the fractional representation of X.
|
|
89
|
+
- price: dict with "base" and "quote": loads Amounts directly (raises AssertionError
|
|
90
|
+
if a top-level "price" key is present).
|
|
91
|
+
- price: numeric (float/int/Decimal) with base and quote provided as Asset or
|
|
92
|
+
symbol strings: converts the numeric value to a Fraction and builds Amounts.
|
|
93
|
+
- price: str representing an Amount and base: when price is a string and base is
|
|
94
|
+
a symbol string, price and base are used to build quote/base Amounts.
|
|
95
|
+
- price and base as Amount instances: accepts Amount objects directly.
|
|
96
|
+
- price is None with base and quote as symbol strings or Amounts: loads assets
|
|
97
|
+
or Amounts respectively.
|
|
98
|
+
|
|
99
|
+
Parameters (not exhaustive):
|
|
100
|
+
- price: numeric, str, dict, or Amount — the price or a representation used to
|
|
101
|
+
derive base/quote Amounts.
|
|
102
|
+
- base: Asset, Amount, or str — identifies the base side (or a symbol string
|
|
103
|
+
used to parse both symbols when combined with a numeric price).
|
|
104
|
+
- quote: Asset, Amount, or str — identifies the quote side.
|
|
105
|
+
- base_asset: optional; used only as an identifier flag for buy/sell contexts.
|
|
106
|
+
- blockchain_instance: blockchain context used to construct Asset/Amount (omitted
|
|
107
|
+
from param listing as a shared service).
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
- AssertionError: if a dict `price` includes a top-level "price" key.
|
|
111
|
+
- ValueError: if the combination of inputs cannot be parsed into base and quote.
|
|
112
|
+
"""
|
|
113
|
+
self.blockchain = blockchain_instance or shared_blockchain_instance()
|
|
114
|
+
if price == "":
|
|
115
|
+
price = None
|
|
116
|
+
if price is not None and isinstance(price, str) and not base and not quote:
|
|
117
|
+
price, assets = price.split(" ")
|
|
118
|
+
base_symbol, quote_symbol = assets_from_string(assets)
|
|
119
|
+
base = Asset(base_symbol, blockchain_instance=self.blockchain)
|
|
120
|
+
quote = Asset(quote_symbol, blockchain_instance=self.blockchain)
|
|
121
|
+
frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
|
|
122
|
+
self["quote"] = Amount(
|
|
123
|
+
amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
|
|
124
|
+
)
|
|
125
|
+
self["base"] = Amount(
|
|
126
|
+
amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
elif price is not None and isinstance(price, dict) and "base" in price and "quote" in price:
|
|
130
|
+
if "price" in price:
|
|
131
|
+
raise AssertionError("You cannot provide a 'price' this way")
|
|
132
|
+
# Regular 'price' objects according to hive-core
|
|
133
|
+
# base_id = price["base"]["asset_id"]
|
|
134
|
+
# if price["base"]["asset_id"] == base_id:
|
|
135
|
+
self["base"] = Amount(price["base"], blockchain_instance=self.blockchain)
|
|
136
|
+
self["quote"] = Amount(price["quote"], blockchain_instance=self.blockchain)
|
|
137
|
+
# else:
|
|
138
|
+
# self["quote"] = Amount(price["base"], blockchain_instance=self.blockchain)
|
|
139
|
+
# self["base"] = Amount(price["quote"], blockchain_instance=self.blockchain)
|
|
140
|
+
|
|
141
|
+
elif price is not None and isinstance(base, Asset) and isinstance(quote, Asset):
|
|
142
|
+
if isinstance(price, Price):
|
|
143
|
+
frac = Fraction(float(price["price"])).limit_denominator(10 ** base["precision"])
|
|
144
|
+
elif isinstance(price, (int, float, str)):
|
|
145
|
+
frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
|
|
146
|
+
else:
|
|
147
|
+
raise ValueError("Unsupported price type")
|
|
148
|
+
self["quote"] = Amount(
|
|
149
|
+
amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
|
|
150
|
+
)
|
|
151
|
+
self["base"] = Amount(
|
|
152
|
+
amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
elif price is not None and isinstance(base, str) and isinstance(quote, str):
|
|
156
|
+
base = Asset(base, blockchain_instance=self.blockchain)
|
|
157
|
+
quote = Asset(quote, blockchain_instance=self.blockchain)
|
|
158
|
+
if isinstance(price, Price):
|
|
159
|
+
frac = Fraction(float(price["price"])).limit_denominator(10 ** base["precision"])
|
|
160
|
+
elif isinstance(price, (int, float, str)):
|
|
161
|
+
frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError("Unsupported price type")
|
|
164
|
+
self["quote"] = Amount(
|
|
165
|
+
amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
|
|
166
|
+
)
|
|
167
|
+
self["base"] = Amount(
|
|
168
|
+
amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
elif price is None and isinstance(base, str) and isinstance(quote, str):
|
|
172
|
+
self["quote"] = Amount(quote, blockchain_instance=self.blockchain)
|
|
173
|
+
self["base"] = Amount(base, blockchain_instance=self.blockchain)
|
|
174
|
+
elif price is not None and isinstance(price, str) and isinstance(base, str):
|
|
175
|
+
self["quote"] = Amount(price, blockchain_instance=self.blockchain)
|
|
176
|
+
self["base"] = Amount(base, blockchain_instance=self.blockchain)
|
|
177
|
+
# len(args) > 1
|
|
178
|
+
|
|
179
|
+
elif isinstance(price, Amount) and isinstance(base, Amount):
|
|
180
|
+
self["quote"], self["base"] = price, base
|
|
181
|
+
|
|
182
|
+
# len(args) == 0
|
|
183
|
+
elif price is None and isinstance(base, Amount) and isinstance(quote, Amount):
|
|
184
|
+
self["quote"] = quote
|
|
185
|
+
self["base"] = base
|
|
186
|
+
|
|
187
|
+
elif (
|
|
188
|
+
isinstance(price, float) or isinstance(price, int) or isinstance(price, Decimal)
|
|
189
|
+
) and isinstance(base, str):
|
|
190
|
+
base_symbol, quote_symbol = assets_from_string(base)
|
|
191
|
+
base = Asset(base_symbol, blockchain_instance=self.blockchain)
|
|
192
|
+
quote = Asset(quote_symbol, blockchain_instance=self.blockchain)
|
|
193
|
+
frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
|
|
194
|
+
self["quote"] = Amount(
|
|
195
|
+
amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
|
|
196
|
+
)
|
|
197
|
+
self["base"] = Amount(
|
|
198
|
+
amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
else:
|
|
202
|
+
raise ValueError("Couldn't parse 'Price'.")
|
|
203
|
+
|
|
204
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
205
|
+
dict.__setitem__(self, key, value)
|
|
206
|
+
if (
|
|
207
|
+
"quote" in self and "base" in self and self["base"] and self["quote"]
|
|
208
|
+
): # don't derive price for deleted Orders
|
|
209
|
+
dict.__setitem__(
|
|
210
|
+
self, "price", self._safedivide(self["base"]["amount"], self["quote"]["amount"])
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def copy(self) -> "Price":
|
|
214
|
+
return Price(
|
|
215
|
+
None,
|
|
216
|
+
base=self["base"].copy(),
|
|
217
|
+
quote=self["quote"].copy(),
|
|
218
|
+
blockchain_instance=self.blockchain,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def _safedivide(
|
|
222
|
+
self, a: Union[int, float, Decimal], b: Union[int, float, Decimal]
|
|
223
|
+
) -> Union[int, float, Decimal]:
|
|
224
|
+
if b != 0.0:
|
|
225
|
+
return float(a) / float(b)
|
|
226
|
+
else:
|
|
227
|
+
return float("inf")
|
|
228
|
+
|
|
229
|
+
def symbols(self) -> Tuple[str, str]:
|
|
230
|
+
return self["base"]["symbol"], self["quote"]["symbol"]
|
|
231
|
+
|
|
232
|
+
def as_base(self, base: Union[str, Asset]) -> "Price":
|
|
233
|
+
"""
|
|
234
|
+
Return a copy of this Price expressed with the given asset as the base.
|
|
235
|
+
|
|
236
|
+
If `base` matches the current base symbol this returns a shallow copy.
|
|
237
|
+
If `base` matches the current quote symbol this returns a copy with base and quote inverted.
|
|
238
|
+
Raises InvalidAssetException if `base` is neither the base nor the quote of this price.
|
|
239
|
+
|
|
240
|
+
Parameters:
|
|
241
|
+
base (str): Asset symbol to use as the base (e.g., "HIVE" or "HBD").
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Price: A new Price instance whose base asset is `base`.
|
|
245
|
+
"""
|
|
246
|
+
if base == self["base"]["symbol"]:
|
|
247
|
+
return self.copy()
|
|
248
|
+
elif base == self["quote"]["symbol"]:
|
|
249
|
+
return self.copy().invert()
|
|
250
|
+
else:
|
|
251
|
+
raise InvalidAssetException
|
|
252
|
+
|
|
253
|
+
def as_quote(self, quote: Union[str, Asset]) -> "Price":
|
|
254
|
+
"""
|
|
255
|
+
Return a Price instance expressed with the given quote asset symbol.
|
|
256
|
+
|
|
257
|
+
If `quote` matches the current quote symbol, returns a copy of this Price.
|
|
258
|
+
If `quote` matches the current base symbol, returns a copied, inverted Price.
|
|
259
|
+
A new object is always returned (the original is not modified).
|
|
260
|
+
|
|
261
|
+
Parameters:
|
|
262
|
+
quote (str): Asset symbol to use as the quote (e.g., "HBD" or "HIVE").
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Price: A Price object with `quote` as the quote asset.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
InvalidAssetException: If `quote` does not match either the current base or quote symbol.
|
|
269
|
+
"""
|
|
270
|
+
if quote == self["quote"]["symbol"]:
|
|
271
|
+
return self.copy()
|
|
272
|
+
elif quote == self["base"]["symbol"]:
|
|
273
|
+
return self.copy().invert()
|
|
274
|
+
else:
|
|
275
|
+
raise InvalidAssetException
|
|
276
|
+
|
|
277
|
+
def invert(self) -> "Price":
|
|
278
|
+
"""
|
|
279
|
+
Invert the price in place, swapping base and quote assets (e.g., HBD/HIVE -> HIVE/HBD).
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
self: The same Price instance after inversion.
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> from nectar.price import Price
|
|
286
|
+
>>> from nectar import Hive
|
|
287
|
+
>>> hv = Hive("https://api.hive.blog")
|
|
288
|
+
>>> Price("0.3314 HBD/HIVE", blockchain_instance=hv).invert()
|
|
289
|
+
3.017483 HIVE/HBD
|
|
290
|
+
"""
|
|
291
|
+
tmp = self["quote"]
|
|
292
|
+
self["quote"] = self["base"]
|
|
293
|
+
self["base"] = tmp
|
|
294
|
+
return self
|
|
295
|
+
|
|
296
|
+
def json(self) -> Dict[str, Any]:
|
|
297
|
+
return {"base": self["base"].json(), "quote": self["quote"].json()}
|
|
298
|
+
|
|
299
|
+
def __repr__(self) -> str:
|
|
300
|
+
return "{price:.{precision}f} {base}/{quote}".format(
|
|
301
|
+
price=self["price"],
|
|
302
|
+
base=self["base"]["symbol"],
|
|
303
|
+
quote=self["quote"]["symbol"],
|
|
304
|
+
precision=(self["base"]["asset"]["precision"] + self["quote"]["asset"]["precision"]),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def __float__(self) -> float:
|
|
308
|
+
return float(self["price"])
|
|
309
|
+
|
|
310
|
+
def _check_other(self, other: "Price") -> None:
|
|
311
|
+
if not other["base"]["symbol"] == self["base"]["symbol"]:
|
|
312
|
+
raise AssertionError()
|
|
313
|
+
if not other["quote"]["symbol"] == self["quote"]["symbol"]:
|
|
314
|
+
raise AssertionError()
|
|
315
|
+
|
|
316
|
+
def __mul__(self, other: Union["Price", Amount, float, int]) -> "Price":
|
|
317
|
+
a = self.copy()
|
|
318
|
+
if isinstance(other, Price):
|
|
319
|
+
# Rotate/invert other
|
|
320
|
+
if (
|
|
321
|
+
self["quote"]["symbol"] not in other.symbols()
|
|
322
|
+
and self["base"]["symbol"] not in other.symbols()
|
|
323
|
+
):
|
|
324
|
+
raise InvalidAssetException
|
|
325
|
+
|
|
326
|
+
# base/quote = a/b
|
|
327
|
+
# a/b * b/c = a/c
|
|
328
|
+
a = self.copy()
|
|
329
|
+
if self["quote"]["symbol"] == other["base"]["symbol"]:
|
|
330
|
+
a["base"] = Amount(
|
|
331
|
+
float(self["base"]) * float(other["base"]),
|
|
332
|
+
self["base"]["symbol"],
|
|
333
|
+
blockchain_instance=self.blockchain,
|
|
334
|
+
)
|
|
335
|
+
a["quote"] = Amount(
|
|
336
|
+
float(self["quote"]) * float(other["quote"]),
|
|
337
|
+
other["quote"]["symbol"],
|
|
338
|
+
blockchain_instance=self.blockchain,
|
|
339
|
+
)
|
|
340
|
+
# a/b * c/a = c/b
|
|
341
|
+
elif self["base"]["symbol"] == other["quote"]["symbol"]:
|
|
342
|
+
a["base"] = Amount(
|
|
343
|
+
float(self["base"]) * float(other["base"]),
|
|
344
|
+
other["base"]["symbol"],
|
|
345
|
+
blockchain_instance=self.blockchain,
|
|
346
|
+
)
|
|
347
|
+
a["quote"] = Amount(
|
|
348
|
+
float(self["quote"]) * float(other["quote"]),
|
|
349
|
+
self["quote"]["symbol"],
|
|
350
|
+
blockchain_instance=self.blockchain,
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
raise ValueError("Wrong rotation of prices")
|
|
354
|
+
elif isinstance(other, Amount):
|
|
355
|
+
check_asset(other["asset"], self["quote"]["asset"], self.blockchain)
|
|
356
|
+
a = other.copy() * self["price"]
|
|
357
|
+
a["asset"] = self["base"]["asset"].copy()
|
|
358
|
+
a["symbol"] = self["base"]["asset"]["symbol"]
|
|
359
|
+
else:
|
|
360
|
+
a["base"] *= other
|
|
361
|
+
return a
|
|
362
|
+
|
|
363
|
+
def __imul__(self, other: Union["Price", Amount, float, int]) -> "Price":
|
|
364
|
+
if isinstance(other, Price):
|
|
365
|
+
tmp = self * other
|
|
366
|
+
self["base"] = tmp["base"]
|
|
367
|
+
self["quote"] = tmp["quote"]
|
|
368
|
+
else:
|
|
369
|
+
self["base"] *= other
|
|
370
|
+
return self
|
|
371
|
+
|
|
372
|
+
def __div__(self, other: Union["Price", Amount, float, int]) -> Union["Price", float]:
|
|
373
|
+
a = self.copy()
|
|
374
|
+
if isinstance(other, Price):
|
|
375
|
+
# Rotate/invert other
|
|
376
|
+
if sorted(self.symbols()) == sorted(other.symbols()):
|
|
377
|
+
return float(self.as_base(self["base"]["symbol"])) / float(
|
|
378
|
+
other.as_base(self["base"]["symbol"])
|
|
379
|
+
)
|
|
380
|
+
elif self["quote"]["symbol"] in other.symbols():
|
|
381
|
+
other = other.as_base(self["quote"]["symbol"])
|
|
382
|
+
elif self["base"]["symbol"] in other.symbols():
|
|
383
|
+
other = other.as_base(self["base"]["symbol"])
|
|
384
|
+
else:
|
|
385
|
+
raise InvalidAssetException
|
|
386
|
+
a["base"] = Amount(
|
|
387
|
+
float(self["base"].amount / other["base"].amount),
|
|
388
|
+
other["quote"]["symbol"],
|
|
389
|
+
blockchain_instance=self.blockchain,
|
|
390
|
+
)
|
|
391
|
+
a["quote"] = Amount(
|
|
392
|
+
float(self["quote"].amount / other["quote"].amount),
|
|
393
|
+
self["quote"]["symbol"],
|
|
394
|
+
blockchain_instance=self.blockchain,
|
|
395
|
+
)
|
|
396
|
+
elif isinstance(other, Amount):
|
|
397
|
+
check_asset(other["asset"], self["quote"]["asset"], self.blockchain)
|
|
398
|
+
a = other.copy() / self["price"]
|
|
399
|
+
a["asset"] = self["base"]["asset"].copy()
|
|
400
|
+
a["symbol"] = self["base"]["asset"]["symbol"]
|
|
401
|
+
else:
|
|
402
|
+
a["base"] /= other
|
|
403
|
+
return a
|
|
404
|
+
|
|
405
|
+
def __idiv__(self, other: Union["Price", Amount, float, int]) -> "Price":
|
|
406
|
+
if isinstance(other, Price):
|
|
407
|
+
tmp = self / other
|
|
408
|
+
# tmp can be either Price or float, handle both cases
|
|
409
|
+
if isinstance(tmp, (int, float)):
|
|
410
|
+
# If division returned a float, we can't do in-place modification
|
|
411
|
+
# Convert to Price by updating the base amount
|
|
412
|
+
self["base"] = Amount(
|
|
413
|
+
float(tmp) * float(self["base"]),
|
|
414
|
+
self["base"]["symbol"],
|
|
415
|
+
blockchain_instance=self.blockchain,
|
|
416
|
+
)
|
|
417
|
+
else:
|
|
418
|
+
# tmp is a Price, do normal in-place update
|
|
419
|
+
self["base"] = tmp["base"]
|
|
420
|
+
self["quote"] = tmp["quote"]
|
|
421
|
+
else:
|
|
422
|
+
self["base"] /= other
|
|
423
|
+
return self
|
|
424
|
+
|
|
425
|
+
def __floordiv__(self, other: Any) -> "Price":
|
|
426
|
+
raise NotImplementedError("This is not possible as the price is a ratio")
|
|
427
|
+
|
|
428
|
+
def __ifloordiv__(self, other: Any) -> "Price":
|
|
429
|
+
raise NotImplementedError("This is not possible as the price is a ratio")
|
|
430
|
+
|
|
431
|
+
def __lt__(self, other: Union["Price", Amount, float, int]) -> bool:
|
|
432
|
+
if isinstance(other, Price):
|
|
433
|
+
self._check_other(other)
|
|
434
|
+
return self["price"] < other["price"]
|
|
435
|
+
else:
|
|
436
|
+
return self["price"] < float(other or 0)
|
|
437
|
+
|
|
438
|
+
def __le__(self, other: Union["Price", Amount, float, int]) -> bool:
|
|
439
|
+
if isinstance(other, Price):
|
|
440
|
+
self._check_other(other)
|
|
441
|
+
return self["price"] <= other["price"]
|
|
442
|
+
else:
|
|
443
|
+
return self["price"] <= float(other or 0)
|
|
444
|
+
|
|
445
|
+
def __eq__(self, other: object) -> bool:
|
|
446
|
+
if isinstance(other, Price):
|
|
447
|
+
self._check_other(other)
|
|
448
|
+
return self["price"] == other["price"]
|
|
449
|
+
if isinstance(other, (float, int)):
|
|
450
|
+
return self["price"] == float(other or 0)
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
def __ne__(self, other: object) -> bool:
|
|
454
|
+
if isinstance(other, Price):
|
|
455
|
+
self._check_other(other)
|
|
456
|
+
return self["price"] != other["price"]
|
|
457
|
+
if isinstance(other, (float, int)):
|
|
458
|
+
return self["price"] != float(other or 0)
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
def __ge__(self, other: Union["Price", Amount, float, int]) -> bool:
|
|
462
|
+
if isinstance(other, Price):
|
|
463
|
+
self._check_other(other)
|
|
464
|
+
return self["price"] >= other["price"]
|
|
465
|
+
else:
|
|
466
|
+
return self["price"] >= float(other or 0)
|
|
467
|
+
|
|
468
|
+
def __gt__(self, other: Union["Price", Amount, float, int]) -> bool:
|
|
469
|
+
if isinstance(other, Price):
|
|
470
|
+
self._check_other(other)
|
|
471
|
+
return self["price"] > other["price"]
|
|
472
|
+
else:
|
|
473
|
+
return self["price"] > float(other or 0)
|
|
474
|
+
|
|
475
|
+
__truediv__ = __div__
|
|
476
|
+
__truemul__ = __mul__
|
|
477
|
+
__str__ = __repr__
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def market(self) -> "Market":
|
|
481
|
+
"""Open the corresponding market
|
|
482
|
+
|
|
483
|
+
:returns: Instance of :class:`nectar.market.Market` for the
|
|
484
|
+
corresponding pair of assets.
|
|
485
|
+
"""
|
|
486
|
+
from .market import Market
|
|
487
|
+
|
|
488
|
+
return Market(
|
|
489
|
+
base=self["base"]["asset"],
|
|
490
|
+
quote=self["quote"]["asset"],
|
|
491
|
+
blockchain_instance=self.blockchain,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class Order(Price):
|
|
496
|
+
"""This class inherits :class:`nectar.price.Price` but has the ``base``
|
|
497
|
+
and ``quote`` Amounts not only be used to represent the price (as a
|
|
498
|
+
ratio of base and quote) but instead has those amounts represent the
|
|
499
|
+
amounts of an actual order!
|
|
500
|
+
|
|
501
|
+
:param Hive blockchain_instance: Hive instance
|
|
502
|
+
|
|
503
|
+
.. note::
|
|
504
|
+
|
|
505
|
+
If an order is marked as deleted, it will carry the
|
|
506
|
+
'deleted' key which is set to ``True`` and all other
|
|
507
|
+
data be ``None``.
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
def __init__(
|
|
511
|
+
self,
|
|
512
|
+
base: Union[Dict[str, Any], Amount],
|
|
513
|
+
quote: Optional[Amount] = None,
|
|
514
|
+
blockchain_instance: Optional[Any] = None,
|
|
515
|
+
**kwargs: Any,
|
|
516
|
+
) -> None:
|
|
517
|
+
self.blockchain = blockchain_instance or shared_blockchain_instance()
|
|
518
|
+
|
|
519
|
+
if isinstance(base, dict) and "sell_price" in base:
|
|
520
|
+
super().__init__(base["sell_price"], blockchain_instance=self.blockchain)
|
|
521
|
+
self["id"] = base.get("id")
|
|
522
|
+
elif isinstance(base, dict) and "min_to_receive" in base and "amount_to_sell" in base:
|
|
523
|
+
super().__init__(
|
|
524
|
+
Amount(base["min_to_receive"], blockchain_instance=self.blockchain),
|
|
525
|
+
Amount(base["amount_to_sell"], blockchain_instance=self.blockchain),
|
|
526
|
+
blockchain_instance=self.blockchain,
|
|
527
|
+
)
|
|
528
|
+
self["id"] = base.get("id")
|
|
529
|
+
elif isinstance(base, Amount) and isinstance(quote, Amount):
|
|
530
|
+
super().__init__(None, base=base, quote=quote, blockchain_instance=self.blockchain)
|
|
531
|
+
else:
|
|
532
|
+
raise ValueError("Unknown format to load Order")
|
|
533
|
+
|
|
534
|
+
def __repr__(self) -> str:
|
|
535
|
+
if "deleted" in self and self["deleted"]:
|
|
536
|
+
return "deleted order %s" % self["id"]
|
|
537
|
+
else:
|
|
538
|
+
t = ""
|
|
539
|
+
if "time" in self and self["time"]:
|
|
540
|
+
t += "(%s) " % self["time"]
|
|
541
|
+
if "type" in self and self["type"]:
|
|
542
|
+
t += "%s " % str(self["type"])
|
|
543
|
+
if "quote" in self and self["quote"]:
|
|
544
|
+
t += "%s " % str(self["quote"])
|
|
545
|
+
if "base" in self and self["base"]:
|
|
546
|
+
t += "%s " % str(self["base"])
|
|
547
|
+
return t + "@ " + Price.__repr__(self)
|
|
548
|
+
|
|
549
|
+
__str__ = __repr__
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class FilledOrder(Price):
|
|
553
|
+
"""This class inherits :class:`nectar.price.Price` but has the ``base``
|
|
554
|
+
and ``quote`` Amounts not only be used to represent the price (as a
|
|
555
|
+
ratio of base and quote) but instead has those amounts represent the
|
|
556
|
+
amounts of an actually filled order!
|
|
557
|
+
|
|
558
|
+
:param Hive blockchain_instance: Hive instance
|
|
559
|
+
|
|
560
|
+
.. note:: Instances of this class come with an additional ``date`` key
|
|
561
|
+
that shows when the order has been filled!
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
def __init__(
|
|
565
|
+
self, order: Dict[str, Any], blockchain_instance: Optional[Any] = None, **kwargs: Any
|
|
566
|
+
) -> None:
|
|
567
|
+
self.blockchain = blockchain_instance or shared_blockchain_instance()
|
|
568
|
+
if isinstance(order, dict) and "current_pays" in order and "open_pays" in order:
|
|
569
|
+
# filled orders from account history
|
|
570
|
+
if "op" in order:
|
|
571
|
+
order = order["op"]
|
|
572
|
+
|
|
573
|
+
super().__init__(
|
|
574
|
+
Amount(order["open_pays"], blockchain_instance=self.blockchain),
|
|
575
|
+
Amount(order["current_pays"], blockchain_instance=self.blockchain),
|
|
576
|
+
blockchain_instance=self.blockchain,
|
|
577
|
+
)
|
|
578
|
+
if "date" in order:
|
|
579
|
+
self["date"] = formatTimeString(order["date"])
|
|
580
|
+
|
|
581
|
+
else:
|
|
582
|
+
raise ValueError("Couldn't parse 'Price'.")
|
|
583
|
+
|
|
584
|
+
def json(self) -> Dict[str, Any]:
|
|
585
|
+
return {
|
|
586
|
+
"date": formatTimeString(self["date"]),
|
|
587
|
+
"current_pays": self["base"].json(),
|
|
588
|
+
"open_pays": self["quote"].json(),
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
def __repr__(self) -> str:
|
|
592
|
+
t = ""
|
|
593
|
+
if "date" in self and self["date"]:
|
|
594
|
+
t += "(%s) " % self["date"]
|
|
595
|
+
if "type" in self and self["type"]:
|
|
596
|
+
t += "%s " % str(self["type"])
|
|
597
|
+
if "quote" in self and self["quote"]:
|
|
598
|
+
t += "%s " % str(self["quote"])
|
|
599
|
+
if "base" in self and self["base"]:
|
|
600
|
+
t += "%s " % str(self["base"])
|
|
601
|
+
return t + "@ " + Price.__repr__(self)
|
|
602
|
+
|
|
603
|
+
__str__ = __repr__
|
nectar/profile.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DotDict(dict):
|
|
8
|
+
def __init__(self, *args: Any) -> None:
|
|
9
|
+
"""This class simplifies the use of "."-separated
|
|
10
|
+
keys when defining a nested dictionary:::
|
|
11
|
+
|
|
12
|
+
>>> from nectar.profile import Profile
|
|
13
|
+
>>> keys = ['profile.url', 'profile.img']
|
|
14
|
+
>>> values = ["http:", "foobar"]
|
|
15
|
+
>>> p = Profile(keys, values)
|
|
16
|
+
>>> print(p["profile"]["url"])
|
|
17
|
+
http:
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
if len(args) == 2:
|
|
21
|
+
for i, item in enumerate(args[0]):
|
|
22
|
+
t = self
|
|
23
|
+
parts = item.split(".")
|
|
24
|
+
for j, part in enumerate(parts):
|
|
25
|
+
if j < len(parts) - 1:
|
|
26
|
+
t = t.setdefault(part, {})
|
|
27
|
+
else:
|
|
28
|
+
t[part] = args[1][i]
|
|
29
|
+
elif len(args) == 1 and isinstance(args[0], dict):
|
|
30
|
+
for k, v in args[0].items():
|
|
31
|
+
self[k] = v
|
|
32
|
+
elif len(args) == 1 and isinstance(args[0], str):
|
|
33
|
+
for k, v in json.loads(args[0]).items():
|
|
34
|
+
self[k] = v
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Profile(DotDict):
|
|
38
|
+
"""This class is a template to model a user's on-chain
|
|
39
|
+
profile according to Hive profile metadata conventions.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, *args: Any) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Initialize a Profile by delegating to the DotDict initializer.
|
|
45
|
+
|
|
46
|
+
This constructor accepts the same arguments as DotDict.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(*args)
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
return json.dumps(self)
|
|
52
|
+
|
|
53
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Update the profile with mapping/iterable semantics while preserving nested-merge behavior for mappings.
|
|
56
|
+
"""
|
|
57
|
+
if args:
|
|
58
|
+
mapping = args[0]
|
|
59
|
+
if isinstance(mapping, Mapping):
|
|
60
|
+
for k, v in mapping.items():
|
|
61
|
+
if isinstance(v, Mapping):
|
|
62
|
+
self.setdefault(k, {}).update(v)
|
|
63
|
+
else:
|
|
64
|
+
self[k] = v
|
|
65
|
+
return
|
|
66
|
+
# Fallback to dict.update behavior
|
|
67
|
+
super().update(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
def remove(self, key: str) -> None:
|
|
70
|
+
parts = key.split(".")
|
|
71
|
+
if len(parts) > 1:
|
|
72
|
+
self[parts[0]].pop(".".join(parts[1:]))
|
|
73
|
+
else:
|
|
74
|
+
super().pop(parts[0], None)
|
nectar/py.typed
ADDED
|
File without changes
|