ostium-python-sdk 0.1.3__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.
@@ -0,0 +1,54 @@
1
+ from datetime import datetime
2
+ from decimal import Decimal
3
+ import time
4
+ from .abi import usdc_abi
5
+ from web3 import Web3
6
+
7
+ REFRESH_BALANCE_SECONDS_INTERVAL = 60 * 5
8
+
9
+
10
+ class Balance:
11
+ def __init__(self, w3: Web3, usdc_address: str) -> None:
12
+ self.web3 = w3
13
+ self.usdc_address = usdc_address
14
+ self.usdc_contract = self.web3.eth.contract(
15
+ address=self.usdc_address, abi=usdc_abi)
16
+ # Format: {address: {'ether': value, 'usdc': value, 'last_refresh': timestamp}}
17
+ self.balances = {}
18
+
19
+ def get_balance(self, address, refresh=False):
20
+ if address not in self.balances:
21
+ self.balances[address] = {'ether': None,
22
+ 'usdc': None, 'last_refresh': None}
23
+
24
+ balance_info = self.balances[address]
25
+ if balance_info['last_refresh'] is None:
26
+ too_old = True
27
+ else:
28
+ too_old = time.time() - \
29
+ balance_info['last_refresh'] > REFRESH_BALANCE_SECONDS_INTERVAL
30
+
31
+ if (refresh or too_old or balance_info['ether'] is None or balance_info['usdc'] is None):
32
+ self.read_balances(address)
33
+
34
+ return self.balances[address]['ether'], self.balances[address]['usdc']
35
+
36
+ def read_balances(self, address):
37
+ # print(f'actual [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] {address} - reading balances')
38
+ start_time = time.time()
39
+ self.balances[address] = {
40
+ 'ether': Decimal(self.get_ether_balance(address)),
41
+ 'usdc': Decimal(self.get_usdc_balance(address)),
42
+ 'last_refresh': start_time
43
+ }
44
+ end_time = time.time()
45
+
46
+ def get_usdc_balance(self, address):
47
+ balance = self.usdc_contract.functions.balanceOf(address).call()
48
+ balance = Web3.to_wei(balance, 'szabo')
49
+ balance = Web3.from_wei(balance, 'ether')
50
+ return balance
51
+
52
+ def get_ether_balance(self, address):
53
+ ret = Web3.from_wei(self.web3.eth.get_balance(address), 'ether')
54
+ return ret
@@ -0,0 +1,33 @@
1
+ from dataclasses import dataclass
2
+ from typing import Dict, Optional
3
+
4
+
5
+ @dataclass
6
+ class NetworkConfig:
7
+ rpc_url: str
8
+ graph_url: str
9
+ contracts: Dict[str, str]
10
+
11
+ @classmethod
12
+ def mainnet(cls) -> 'NetworkConfig':
13
+ return cls(
14
+ rpc_url="https://arb-mainnet.g.alchemy.com/v2/-dfuyiAmKg9seY_LJcy61q60GZELrNhX",
15
+ graph_url="https://subgraph.satsuma-prod.com/391a61815d32/ostium/ost-prod/api",
16
+ contracts={
17
+ "usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
18
+ "trading": "0x6D0bA1f9996DBD8885827e1b2e8f6593e7702411",
19
+ "tradingStorage": "0xcCd5891083A8acD2074690F65d3024E7D13d66E7"
20
+ }
21
+ )
22
+
23
+ @classmethod
24
+ def testnet(cls) -> 'NetworkConfig':
25
+ return cls(
26
+ rpc_url="https://arb-sepolia.g.alchemy.com/v2/xussaWLkWxKtZjCIlUjAYiMGV6_dRO-8",
27
+ graph_url="https://subgraph.satsuma-prod.com/391a61815d32/ostium/ost-sep-final/api",
28
+ contracts={
29
+ "usdc": "0xe73B11Fb1e3eeEe8AF2a23079A4410Fe1B370548",
30
+ "trading": "0x2A9B9c988393f46a2537B0ff11E98c2C15a95afe",
31
+ "tradingStorage": "0x0b9F5243B29938668c9Cfbd7557A389EC7Ef88b8"
32
+ }
33
+ )
@@ -0,0 +1,11 @@
1
+
2
+ # Constants
3
+ from decimal import Decimal
4
+
5
+ PRECISION_2 = Decimal('100')
6
+ MAX_PROFIT_P = Decimal('900') # 900% * PRECISION_6
7
+ MAX_STOP_LOSS_P = Decimal('85')
8
+ PRECISION_6 = Decimal('1000000')
9
+ PRECISION_12 = Decimal('1000000000000')
10
+ PRECISION_18 = Decimal('1000000000000000000')
11
+ LIQ_THRESHOLD_P = Decimal('90') # -90% (of collateral)
@@ -0,0 +1,352 @@
1
+ from decimal import Decimal
2
+ from .constants import MAX_PROFIT_P, MAX_STOP_LOSS_P, PRECISION_2, PRECISION_6, PRECISION_12, PRECISION_18, LIQ_THRESHOLD_P
3
+
4
+ #
5
+ # This is a copy-cat of formulae repo originally written in TypeScript
6
+ #
7
+
8
+ #
9
+ # GetStopLossPrice and GetTakeProfitPrice are consolidated into one function.
10
+ # bool is_tp is True if we want to calculate the take profit price, False if we want to calculate the stop loss price
11
+ #
12
+
13
+
14
+ def GetTakeProfitPrice(is_tp: bool, open_price: Decimal, leverage: Decimal, long: bool, profit_p: Decimal) -> Decimal:
15
+ """
16
+ Calculate the take profit / stop loss price based on desired profit percentage (aka: MAX_PROFIT_P=900 or MAX_STOP_LOSS_P=85).
17
+
18
+ Args:
19
+ open_price (Decimal): The opening price of the trade
20
+ profit_p (Decimal): The desired profit percentage
21
+ leverage (Decimal): The leverage amount
22
+ long (bool): Whether this is a long position
23
+ profit_p (Decimal): The desired profit percentage - 900% for Tp or 85% for SL
24
+
25
+ Returns:
26
+ str: The Tp / SL price as a string
27
+ """
28
+
29
+ open_price = Decimal(open_price)
30
+ profit_p = Decimal(profit_p)
31
+ leverage = Decimal(leverage)
32
+
33
+ price_diff = (open_price * profit_p) / (leverage * Decimal('100'))
34
+
35
+ if (is_tp):
36
+ price = open_price + price_diff if long else open_price - price_diff
37
+ else:
38
+ price = open_price - price_diff if long else open_price + price_diff
39
+
40
+ return Decimal(price if price > 0 else '0')
41
+
42
+
43
+ def CurrentTradeProfitP(open_price: str, current_price: str, long: bool, leverage: str) -> str:
44
+ """
45
+ Calculate the current trade profit percentage.
46
+
47
+ Args:
48
+ open_price (str): The opening price of the trade
49
+ current_price (str): The current price
50
+ long (bool): Whether this is a long position
51
+ leverage (str): The leverage amount
52
+
53
+ Returns:
54
+ str: The profit percentage as a string
55
+ """
56
+ try:
57
+
58
+ open_price_d = Decimal(open_price)
59
+ current_price_d = Decimal(current_price)
60
+ leverage_d = Decimal(leverage)
61
+
62
+ if long:
63
+ price_diff = current_price_d - open_price_d
64
+ else:
65
+ price_diff = open_price_d - current_price_d
66
+
67
+ profit_p = (price_diff / open_price_d) * (leverage_d)
68
+
69
+ if profit_p > MAX_PROFIT_P:
70
+ return (MAX_PROFIT_P)
71
+ else:
72
+ return (profit_p)
73
+ except Exception as e:
74
+ return str(e)
75
+
76
+
77
+ def GetTradeLiquidationPrice(
78
+ open_price: str,
79
+ long: bool,
80
+ collateral: str,
81
+ leverage: str,
82
+ rollover_fee: str,
83
+ funding_fee: str
84
+ ) -> Decimal:
85
+ try:
86
+ open_price = Decimal(open_price)
87
+ collateral = Decimal(collateral)
88
+ rollover_fee = Decimal(rollover_fee)
89
+ funding_fee = Decimal(funding_fee)
90
+ leverage = Decimal(leverage)
91
+
92
+ liq_price_distance = (
93
+ open_price *
94
+ (collateral * LIQ_THRESHOLD_P / 100 - rollover_fee - funding_fee) /
95
+ collateral /
96
+ leverage
97
+ )
98
+
99
+ # print('# liq_price_distance', liq_price_distance)
100
+ liq_price = open_price - liq_price_distance if long else open_price + liq_price_distance
101
+
102
+ liq_price = liq_price if liq_price > 0 else 0
103
+ # print('---> liq_price', liq_price)
104
+ return liq_price
105
+
106
+ except Exception as error:
107
+ raise Exception(f"Unable to compute Liquidation Price: {error}")
108
+
109
+
110
+ def GetCurrentRolloverFee(
111
+ acc_rollover: str,
112
+ last_rollover_block: str,
113
+ rollover_fee_per_block: str,
114
+ latest_block: str
115
+ ) -> Decimal:
116
+ try:
117
+ acc_rollover = Decimal(acc_rollover)
118
+ last_rollover_block = Decimal(last_rollover_block)
119
+ rollover_fee_per_block = Decimal(rollover_fee_per_block)
120
+ latest_block = Decimal(latest_block)
121
+ current_fee = acc_rollover + \
122
+ (latest_block - last_rollover_block) * rollover_fee_per_block
123
+ return current_fee
124
+
125
+ except Exception as error:
126
+ raise Exception(f"Unable to compute Current Rollover Fee: {error}")
127
+
128
+
129
+ def GetTradeRolloverFee(
130
+ trade_rollover: str,
131
+ current_rollover: str,
132
+ collateral: str,
133
+ leverage: str
134
+ ) -> Decimal:
135
+ try:
136
+ current_rollover = Decimal(current_rollover)
137
+ trade_rollover = Decimal(trade_rollover)
138
+ collateral = Decimal(collateral)
139
+ leverage = Decimal(leverage)
140
+
141
+ rollover_fee = (
142
+ (current_rollover - trade_rollover) *
143
+ collateral *
144
+ leverage /
145
+ PRECISION_18 /
146
+ PRECISION_2
147
+ )
148
+ return rollover_fee
149
+
150
+ except Exception as error:
151
+ raise Exception(f"Unable to compute Trade Rollover Fee: {error}")
152
+
153
+
154
+ def GetTradeFundingFee(
155
+ trade_funding: str,
156
+ current_funding: str,
157
+ collateral: str,
158
+ leverage: str
159
+ ) -> Decimal:
160
+ try:
161
+ current_funding = Decimal(current_funding)
162
+ trade_funding = Decimal(trade_funding)
163
+ collateral = Decimal(collateral)
164
+ leverage = Decimal(leverage)
165
+
166
+ funding_fee = (
167
+ (current_funding - trade_funding) *
168
+ collateral *
169
+ leverage /
170
+ PRECISION_18 /
171
+ PRECISION_2
172
+ )
173
+ return funding_fee
174
+
175
+ except Exception as error:
176
+ raise Exception(f"Unable to compute Trade Funding Fee: {error}")
177
+
178
+
179
+ def GetFundingRate(
180
+ acc_funding_long: str,
181
+ acc_funding_short: str,
182
+ last_funding_rate: str,
183
+ last_funding_velocity: str,
184
+ max_funding_fee_per_block: str,
185
+ last_funding_block: str,
186
+ latest_block: str,
187
+ long_oi: str,
188
+ short_oi: str
189
+ ) -> dict:
190
+ try:
191
+ acc_funding_long = Decimal(acc_funding_long)
192
+ acc_funding_short = Decimal(acc_funding_short)
193
+ last_funding_rate = Decimal(last_funding_rate)
194
+ last_funding_velocity = Decimal(last_funding_velocity)
195
+ max_funding_fee_per_block = Decimal(max_funding_fee_per_block)
196
+ last_funding_block = Decimal(last_funding_block)
197
+ latest_block = Decimal(latest_block)
198
+ long_oi = Decimal(long_oi)
199
+ short_oi = Decimal(short_oi)
200
+
201
+ block_diff = latest_block - last_funding_block
202
+
203
+ # Calculate skew
204
+ total_oi = long_oi + short_oi
205
+ skew = Decimal('0')
206
+ if total_oi > 0:
207
+ skew = (long_oi - short_oi) / total_oi
208
+
209
+ # Calculate funding rate
210
+ funding_rate = last_funding_rate + last_funding_velocity * block_diff
211
+
212
+ # Cap funding rate
213
+ if funding_rate > max_funding_fee_per_block:
214
+ funding_rate = max_funding_fee_per_block
215
+ elif funding_rate < -max_funding_fee_per_block:
216
+ funding_rate = -max_funding_fee_per_block
217
+
218
+ # Calculate accumulated funding
219
+ funding_long = acc_funding_long + funding_rate * block_diff
220
+ funding_short = acc_funding_short - funding_rate * block_diff
221
+
222
+ return {
223
+ 'accFundingLong': str(funding_long),
224
+ 'accFundingShort': str(funding_short),
225
+ 'skew': str(skew)
226
+ }
227
+
228
+ except Exception as error:
229
+ raise Exception(f"Unable to compute Funding Rate: {error}")
230
+
231
+
232
+ def GetPriceImpact(
233
+ mid_price: str,
234
+ bid_price: str,
235
+ ask_price: str,
236
+ spread_p: str,
237
+ is_limit: bool,
238
+ is_buy: bool,
239
+ is_close: bool,
240
+ trade_size: str,
241
+ trade_size_ref: str
242
+ ) -> dict:
243
+ try:
244
+ mid_price = Decimal(mid_price)
245
+ bid_price = Decimal(bid_price)
246
+ ask_price = Decimal(ask_price)
247
+ spread_p = Decimal(spread_p)
248
+ trade_size = Decimal(trade_size)
249
+ trade_size_ref = Decimal(trade_size_ref)
250
+
251
+ # Calculate base spread
252
+ base_spread = (ask_price - bid_price) / mid_price
253
+
254
+ # Calculate impact spread
255
+ impact_spread = base_spread * (trade_size / trade_size_ref)
256
+
257
+ # Calculate price after impact
258
+ price_after_impact = mid_price
259
+ if not is_limit:
260
+ if is_buy:
261
+ price_after_impact = mid_price * \
262
+ (Decimal('1') + impact_spread / Decimal('2'))
263
+ else:
264
+ price_after_impact = mid_price * \
265
+ (Decimal('1') - impact_spread / Decimal('2'))
266
+
267
+ return {
268
+ 'priceAfterImpact': str(price_after_impact),
269
+ 'impactSpread': str(impact_spread)
270
+ }
271
+
272
+ except Exception as error:
273
+ raise Exception(f"Unable to compute Price Impact: {error}")
274
+
275
+
276
+ def CurrentTradeProfitRaw(
277
+ open_price: str,
278
+ current_price: str,
279
+ is_buy: bool,
280
+ leverage: str,
281
+ collateral: str
282
+ ) -> str:
283
+ try:
284
+ open_price = Decimal(open_price)
285
+ current_price = Decimal(current_price)
286
+ leverage = Decimal(leverage)
287
+ collateral = Decimal(collateral)
288
+
289
+ # Calculate price difference based on position direction
290
+ if is_buy:
291
+ price_diff = current_price - open_price
292
+ else:
293
+ price_diff = open_price - current_price
294
+
295
+ # Calculate profit
296
+ profit = (price_diff * collateral * leverage) / \
297
+ (open_price * PRECISION_2)
298
+
299
+ return str(profit)
300
+
301
+ except Exception as error:
302
+ raise Exception(f"Unable to compute Current Trade Profit Raw: {error}")
303
+
304
+
305
+ def CurrentTotalProfitRaw(
306
+ open_price: str,
307
+ current_price: str,
308
+ is_buy: bool,
309
+ leverage: str,
310
+ collateral: str,
311
+ rollover_fee: str,
312
+ funding_fee: str
313
+ ) -> str:
314
+ try:
315
+ # Get trade profit
316
+ trade_profit = Decimal(CurrentTradeProfitRaw(
317
+ open_price,
318
+ current_price,
319
+ is_buy,
320
+ leverage,
321
+ collateral
322
+ ))
323
+
324
+ # Subtract fees
325
+ total_profit = trade_profit - \
326
+ Decimal(rollover_fee) - Decimal(funding_fee)
327
+
328
+ return str(total_profit)
329
+
330
+ except Exception as error:
331
+ raise Exception(f"Unable to compute Current Total Profit Raw: {error}")
332
+
333
+
334
+ def CurrentTotalProfitP(total_profit: str, collateral: str) -> str:
335
+ try:
336
+ total_profit = Decimal(total_profit)
337
+ collateral = Decimal(collateral)
338
+
339
+ # Calculate profit percentage
340
+ profit_percentage = (total_profit * PRECISION_6) / collateral
341
+
342
+ # Cap at MAX_PROFIT_P if needed
343
+ if profit_percentage > MAX_PROFIT_P:
344
+ return str(MAX_PROFIT_P)
345
+
346
+ return str(profit_percentage)
347
+
348
+ except Exception as error:
349
+ raise Exception(
350
+ f"Unable to compute Current Total Profit Percentage: {error}")
351
+
352
+ # given desired TP percentage, like 35, 50, 75, 100, 500 and 900 which is max: gives you the TP price