Skip to content

Commit 6fd8b7a

Browse files
authored
Merge pull request #10 from InjectiveLabs/f/update-utils
Update utils to use strings & not floats / add functions for derivatives
2 parents 26eafb5 + f3429cb commit 6fd8b7a

File tree

1 file changed

+165
-42
lines changed

1 file changed

+165
-42
lines changed

src/injective/utils.py

Lines changed: 165 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,222 @@
11
from decimal import Decimal
2+
from math import floor
3+
4+
import grpc
5+
6+
import injective.exchange_api.injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb
7+
import injective.exchange_api.injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc
8+
import injective.exchange_api.injective_derivative_exchange_rpc_pb2 as derivative_exchange_rpc_pb
9+
import injective.exchange_api.injective_derivative_exchange_rpc_pb2_grpc as derivative_exchange_rpc_grpc
10+
211
"""
3-
One thing you may need to pay more attention to is how to deal with decimals in injective exchange.
4-
As we all known, different crypto currecies require diffrent decimal precisions.
5-
Separately, ERC-20 tokens(e.g. INJ) have decimals of 18 or another number(like 6 for USDT and USDC).
6-
So in injective system that means ** having 1 INJ is 1e18 inj ** and that ** 1 USDT is actually 100000 peggy0xdac17f958d2ee523a2206206994597c13d831ec7**.
12+
One thing you may need to pay more attention to is how to deal with decimals on the Injective Exchange.
13+
Different cryptocurrencies may require diffrent decimal precisions. More specifically, ERC-20 tokens(e.g. INJ) have 18 decimals whereas USDT/USC have 6 decimals.
14+
So in our system that means ** having 1 INJ is 1e18 inj ** and that ** 1 USDT is actually 100000 peggy0xdac17f958d2ee523a2206206994597c13d831ec7**.
715
816
For spot markets, a price reflects the ** relative exchange rate ** between two tokens.
9-
If the tokens have the same decimal scale, that's great since the prices
10-
become interpretable e.g. USDT/USDC (both have 6 decimals e.g. for USDT https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#readContract)
17+
If the tokens have the same decimal scale, that's great since the prices become interpretable e.g. USDT/USDC (both have 6 decimals e.g. for USDT https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#readContract)
1118
or MATIC/INJ (both have 18 decimals) since the decimals cancel out.
12-
Prices however start to look wonky once you have exchanges between two tokens of different decimals, which unfortunately is most pairs with USDT or USDC denominations.
13-
As such, I've created some simple utility functions by keeping a hardcoded dictionary in injective-py and you can aslo achieve such utilities by yourself
19+
Prices however start to look wonky once you have exchanges between two tokens of different decimals, which unfortunately is most pairs with USDT or USDC denominations e.g. INJ/USDT.
20+
As such, I've created some simple utility functions by keeping a hardcoded dictionary in injective-py and you can also achieve such utilities by yourself
1421
(e.g. you can use external API like Alchemy's getTokenMetadata to fetch decimal of base and quote asset).
1522
16-
So for INJ/USDT of 6.9, the price you end up getting is 6.9*10 ^ (6 - 18) = 6.9e-12.
17-
Note that this market also happens to have a MinPriceTickSize of 1e-15.
18-
This makes sense since since it's defining the minimum price increment of the relative exchange of INJ to USDT.
23+
So for INJ/USDT of 6.9, the price you end up getting is 6.9*10 ^ (6 - 18) = 6.9e-12. Note that this market also happens to have a MinPriceTickSize of 1e-15.
24+
This makes sense since since it's defining th3e minimum price increment of the relative exchange of INJ to USDT.
25+
1926
Note that this market also happens to have a MinQuantityTickSize of 1e15.
2027
This also makes sense since it refers to the minimum INJ quantity tick size each order must have, which is 1e15/1e18 = 0.001 INJ.
28+
2129
"""
22-
def price_float_to_string(price, base_decimals, quote_decimals, precision=18) -> str:
23-
"""transfer price[float] to string which satisfies what injective exchange backend requires
2430

31+
async def spot_price_to_backend(endpoint, market, price, base_decimals, quote_decimals, precision=18) -> str:
32+
33+
"""
2534
Args:
35+
endpoint ([string]): the endpoint for the gRPC request
36+
market ([string]): the market_id of the pair
2637
price ([float]): normal price, you can read it directly in exchange front-end
27-
base_decimals ([int]): decimal of base asset
28-
quote_decimals ([int]): quote asset's decimal
38+
base_decimals ([int]): decimals of base asset
39+
quote_decimals ([int]): decimals of quote asset
2940
precision (int, optional): [description]. Defaults to 18.
41+
3042
Returns:
3143
str: relative price for base asset and quote asset. For INJ/USDT of 6.9, the price you end up getting is 6.9*10 ^ (6 - 18) = 6.9e-12.
3244
"""
33-
scale = Decimal(quote_decimals - base_decimals)
34-
exchange_price = Decimal(price) * pow(10, scale)
45+
46+
async with grpc.aio.insecure_channel(endpoint) as channel:
47+
spot_exchange_rpc = spot_exchange_rpc_grpc.InjectiveSpotExchangeRPCStub(channel)
48+
mresp = await spot_exchange_rpc.Market(spot_exchange_rpc_pb.MarketRequest(market_id=market))
49+
50+
scale_tick_size = int(base_decimals - quote_decimals)
51+
price_tick_size = float(mresp.market.min_price_tick_size) * pow(10, scale_tick_size)
52+
scale_price = Decimal(quote_decimals - base_decimals)
53+
exchange_price = floor_to(price, price_tick_size) *pow(10, scale_price)
3554
price_string = ("{:."+str(precision)+"f}").format(exchange_price)
3655
print("price string :{}".format(price_string))
3756
return price_string
3857

58+
async def derivative_price_to_backend(endpoint, market, price, quote_decimals, precision=18) -> str:
3959

40-
def quantity_float_to_string(quantity, base_decimals, precision=18) -> str:
41-
"""transfer quantity[float] to string which satisfies what injective exchange backend requires
60+
"""
61+
Args:
62+
endpoint ([string]): the endpoint for the gRPC request
63+
market ([string]): the market_id of the pair
64+
price ([float]): normal price, you can read it directly in exchange front-end
65+
quote_decimals ([int]): decimals of quote asset
66+
precision (int, optional): [description]. Defaults to 18.
4267
68+
Returns:
69+
str: relative price for the quote asset. For INJ/USDT of 6.9, the price you end up getting is 6.9*10 ^ (6) = 6.9e6.
70+
"""
71+
72+
async with grpc.aio.insecure_channel(endpoint) as channel:
73+
derivative_exchange_rpc = derivative_exchange_rpc_grpc.InjectiveDerivativeExchangeRPCStub(channel)
74+
mresp = await derivative_exchange_rpc.Market(derivative_exchange_rpc_pb.MarketRequest(market_id=market))
75+
76+
77+
scale = int(0 - quote_decimals)
78+
price_tick_size = float(mresp.market.min_price_tick_size) * pow(10, scale)
79+
exchange_price = floor_to(price, price_tick_size) * pow(10, quote_decimals)
80+
price_string = ("{:."+str(precision)+"f}").format(exchange_price)
81+
print("price string :{}".format(price_string))
82+
return price_string
83+
84+
async def derivative_margin_to_backend(endpoint, market, price, quantity, leverage, quote_decimals, precision=18) -> str:
85+
86+
"""
4387
Args:
44-
quantity ([type]): normal quantity, you can read it in exchange front-end
45-
base_decimals ([type]): decimal of base asset
88+
endpoint ([string]): the endpoint for the gRPC request
89+
market ([string]): the market_id of the pair
90+
price ([float]): normal price, you can read it directly in exchange front-end
91+
quantity ([float]): normal quantity, you can read it directly in exchange front-end
92+
leverage ([float]): normal leverage, you can read it directly in exchange front-end
93+
quote_decimals ([int]): decimals of quote asset
94+
precision (int, optional): [description]. Defaults to 18.
95+
96+
Returns:
97+
str: relative price for the quote asset. For INJ/USDT of 6.9, the price you end up getting is 6.9*10 ^ (6) = 6.9e6.
98+
"""
99+
100+
async with grpc.aio.insecure_channel(endpoint) as channel:
101+
derivative_exchange_rpc = derivative_exchange_rpc_grpc.InjectiveDerivativeExchangeRPCStub(channel)
102+
mresp = await derivative_exchange_rpc.Market(derivative_exchange_rpc_pb.MarketRequest(market_id=market))
103+
104+
scale = int(0 - quote_decimals)
105+
price_tick_size = float(mresp.market.min_price_tick_size) * pow(10, scale)
106+
margin = (price * quantity) / leverage
107+
exchange_margin = floor_to(margin, price_tick_size) * pow(10, quote_decimals)
108+
price_string = ("{:."+str(precision)+"f}").format(exchange_margin)
109+
print("margin string :{}".format(price_string))
110+
return price_string
111+
112+
113+
async def spot_quantity_to_backend(endpoint, market, quantity, base_decimals, precision=18) -> str:
114+
115+
"""
116+
Args:
117+
endpoint ([string]): the endpoint for the gRPC request
118+
market ([string]): the market_id of the pair
119+
quantity ([float]): normal quantity, you can read it in exchange front-end
120+
base_decimals ([int]): decimals of base asset
46121
precision (int, optional): [description]. Defaults to 18.
47122
48123
Returns:
49-
str: acutally quanity of base asset[data type: string] For 1 INJ, the quantity you end up is 1e18 inj
124+
str: actual quantity of base asset[data type: string] For 1 INJ, the quantity you end up getting is 1e18 inj
50125
"""
51-
scale = Decimal(base_decimals)
52-
exchange_quantity = Decimal(quantity) * pow(10, scale)
126+
127+
async with grpc.aio.insecure_channel(endpoint) as channel:
128+
spot_exchange_rpc = spot_exchange_rpc_grpc.InjectiveSpotExchangeRPCStub(channel)
129+
mresp = await spot_exchange_rpc.Market(spot_exchange_rpc_pb.MarketRequest(market_id=market))
130+
131+
scale_tick_size = float(0 - base_decimals)
132+
quantity_tick_size = float(mresp.market.min_quantity_tick_size) *pow(10, scale_tick_size)
133+
scale_quantity = Decimal(base_decimals)
134+
exchange_quantity = floor_to(quantity, quantity_tick_size) *pow(10, scale_quantity)
53135
quantity_string = ("{:."+str(precision)+"f}").format(exchange_quantity)
54136
print("quantity string:{}".format(quantity_string))
55137
return quantity_string
56138

57139

58-
def price_string_to_float(price_string, base_decimals, quote_decimals) -> float:
140+
async def derivative_quantity_to_backend(endpoint, market, quantity, quote_decimals, precision=18) -> str:
141+
59142
"""
60143
Args:
61-
price_string ([type]): price with string data type that injective-exchange backend returns
62-
base_decimals ([type]): decimal of base asset
63-
quote_decimals ([type]): decimal of quote asset
144+
endpoint ([string]): the endpoint for the gRPC request
145+
market ([string]): the market_id of the pair
146+
quantity ([float]): normal quantity, you can read it in exchange front-end
147+
quote_decimals ([int]): decimals of quote asset
148+
precision (int, optional): [description]. Defaults to 18.
149+
150+
Returns:
151+
str: actual quantity of quote asset[data type: string] For 1 USDT, the quantity you end up is 1.000000000000000000 USDT
152+
"""
153+
154+
async with grpc.aio.insecure_channel(endpoint) as channel:
155+
derivative_exchange_rpc = derivative_exchange_rpc_grpc.InjectiveDerivativeExchangeRPCStub(channel)
156+
mresp = await derivative_exchange_rpc.Market(derivative_exchange_rpc_pb.MarketRequest(market_id=market))
157+
158+
quantity_tick_size = float(mresp.market.min_quantity_tick_size)
159+
exchange_quantity = floor_to(quantity, quantity_tick_size)
160+
quantity_string = ("{:."+str(precision)+"f}").format(exchange_quantity)
161+
print("quantity string :{}".format(quantity_string))
162+
return quantity_string
163+
164+
def spot_price_from_backend(price_string, base_decimals, quote_decimals) -> float:
165+
166+
"""
167+
Args:
168+
price_string ([string]): price with string data type that injective-exchange backend returns
169+
base_decimals ([int]): decimal of base asset
170+
quote_decimals ([int]): decimal of quote asset
64171
65172
Returns:
66173
float: actual price what you can read directly from front-end.
67174
For 6.9e-12 inj/peggy0xdac17f958d2ee523a2206206994597c13d831ec7**, the price you end up getting is 6.9 INJ/USDT.
68175
"""
176+
69177
scale = float(base_decimals - quote_decimals)
70178
return float(price_string) * pow(10, scale)
71179

180+
async def spot_quantity_from_backend(endpoint, market, quantity_string, base_decimals) -> float:
72181

73-
def quantity_string_to_float(quantity_string, base_decimals) -> float:
74182
"""
75-
76183
Args:
77184
quantity_string ([type]): quantity string that injective-exchange backend returns
78185
base_decimals ([type]): decimal of base asset
186+
endpoint ([string]): the endpoint for the gRPC request
187+
market ([string]): the market_id of the pair
79188
80189
Returns:
81-
float: actually quantity, for 1e18 inj, you will get 1 INJ
190+
float: actual quantity, for 1e18 inj, you will get 1 INJ
82191
"""
192+
async with grpc.aio.insecure_channel(endpoint) as channel:
193+
spot_exchange_rpc = spot_exchange_rpc_grpc.InjectiveSpotExchangeRPCStub(channel)
194+
mresp = await spot_exchange_rpc.Market(spot_exchange_rpc_pb.MarketRequest(market_id=market))
195+
83196
scale = float(0 - base_decimals)
84-
return float(quantity_string) * pow(10, scale)
197+
quantity_tick_size = float(mresp.market.min_quantity_tick_size) *pow(10, scale)
198+
quantity = float(quantity_string) * pow(10, scale)
199+
return floor_to(quantity, quantity_tick_size)
85200

86201

87-
# test
88-
if __name__ == "__main__":
89-
assert "0.000000005000000000" == price_float_to_string(
90-
5000, 18, 6, 18), "result of get_price is wrong."
202+
def derivative_price_from_backend(price_string, quote_decimals=6) -> float:
203+
204+
"""
205+
Args:
206+
price_string ([string]): price with string data type that injective-exchange backend returns
207+
quote_decimals ([int]): decimals of quote asset. Defaults to 6
91208
92-
assert "1222000000000000000000.000000000000000000" == quantity_float_to_string(
93-
1222, 18, 18), "result of get_quantity is wrong."
209+
Returns:
210+
float: actual price which you can read directly from the front-end.
211+
For 6.9e6 inj/peggy0xdac17f958d2ee523a2206206994597c13d831ec7**, the price you end up getting is 6.9 INJ/USDT.
212+
"""
213+
214+
scale = float(0 - quote_decimals)
215+
return float(price_string) * pow(10, scale)
94216

95-
assert 5000 == price_string_to_float(price_float_to_string(
96-
5000, 18, 6, 18), 18, 6), "something is wrong."
97217

98-
assert 1222 == quantity_string_to_float(
99-
quantity_float_to_string(1222, 18, 18), 18), "something is wrong."
218+
def floor_to(value: float, target: float) -> str:
219+
value = Decimal(str(value))
220+
target = Decimal(str(target))
221+
result = Decimal(int(floor(value / target)) * target)
222+
return result

0 commit comments

Comments
 (0)