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,294 @@
1
+ from decimal import Decimal
2
+ from formulae import (PRECISION_18, PRECISION_2, PRECISION_6, GetCurrentRolloverFee,
3
+ GetTradeFundingFee, GetTradeLiquidationPrice, GetTradeRolloverFee,
4
+ GetFundingRate, GetPriceImpact, CurrentTradeProfitRaw,
5
+ CurrentTotalProfitRaw, CurrentTotalProfitP)
6
+ from typing import Dict, Union
7
+
8
+
9
+ def get_liq_price(trade_details, pair_info, block_number):
10
+ current_funding_fee = GetTradeFundingFee(trade_details['funding'], pair_info['accFundingLong'] if trade_details['isBuy']
11
+ else pair_info['accFundingShort'], trade_details['collateral'], trade_details['leverage'])
12
+
13
+ current_rollover_fee = GetCurrentRolloverFee(
14
+ pair_info['accRollover'], pair_info['lastRolloverBlock'], pair_info['rolloverFeePerBlock'], block_number)
15
+
16
+ trade_rollover_fee = GetTradeRolloverFee(
17
+ trade_details['rollover'], current_rollover_fee, trade_details['collateral'], trade_details['leverage'])
18
+
19
+ liq_price = GetTradeLiquidationPrice(trade_details['openPrice'], trade_details['isBuy'], Decimal(trade_details['collateral']) / PRECISION_6, Decimal(
20
+ trade_details['leverage']) / PRECISION_2, Decimal(trade_rollover_fee)/PRECISION_6, Decimal(current_funding_fee) / PRECISION_6)
21
+
22
+ return liq_price / PRECISION_18
23
+
24
+
25
+ def get_funding_fee_long_short(pair_info, block_number):
26
+ funding_rate_raw = GetFundingRate(
27
+ pair_info['accFundingLong'],
28
+ pair_info['accFundingShort'],
29
+ pair_info['lastFundingRate'],
30
+ pair_info['lastFundingVelocity'],
31
+ pair_info['maxFundingFeePerBlock'],
32
+ pair_info['lastFundingBlock'],
33
+ str(block_number),
34
+ pair_info['longOI'],
35
+ pair_info['shortOI']
36
+ )
37
+
38
+ # Convert latest funding rate to decimal
39
+ latest_rate = Decimal(
40
+ funding_rate_raw['latestFundingRate']) / PRECISION_18 # Fixed key name
41
+
42
+ # Convert OI values to decimal
43
+ long_oi = Decimal(pair_info['longOI']) / PRECISION_18
44
+ short_oi = Decimal(pair_info['shortOI']) / PRECISION_18
45
+
46
+ if funding_rate_raw['longsPay']:
47
+ # If longs pay, they get negative rate
48
+ long_rate = -latest_rate
49
+ # Shorts receive proportional to OI ratio
50
+ short_rate = latest_rate * long_oi / \
51
+ short_oi if short_oi > 0 else Decimal('0')
52
+ else:
53
+ # If shorts pay, they get negative rate
54
+ short_rate = -latest_rate
55
+ # Longs receive proportional to OI ratio
56
+ long_rate = latest_rate * short_oi / \
57
+ long_oi if long_oi > 0 else Decimal('0')
58
+
59
+ return float(long_rate), float(short_rate)
60
+
61
+
62
+ def get_trade_pnl(trade_details, pair_info, price_data, block_number):
63
+ """
64
+ Calculate PNL and related metrics for a trade.
65
+ """
66
+ if not trade_details or not price_data or not block_number or not pair_info:
67
+ return {
68
+ 'pnl': 0,
69
+ 'pnl_raw': '0',
70
+ 'pnl_percent': 0,
71
+ 'rollover': 0,
72
+ 'funding': 0,
73
+ 'total_profit': 0,
74
+ 'net_pnl': 0,
75
+ 'net_value': 0,
76
+ 'liquidation_price': 0
77
+ }
78
+
79
+ # Calculate current rollover fee
80
+ current_rollover_raw = GetCurrentRolloverFee(
81
+ pair_info['accRollover'],
82
+ pair_info['lastRolloverBlock'],
83
+ pair_info['rolloverFeePerBlock'],
84
+ str(block_number)
85
+ )
86
+
87
+ # Calculate rollover for this trade
88
+ rollover_raw = GetTradeRolloverFee(
89
+ trade_details['rollover'],
90
+ current_rollover_raw,
91
+ trade_details['collateral'],
92
+ trade_details['leverage']
93
+ )
94
+
95
+ # Get funding rate
96
+ funding_rate_raw = GetFundingRate(
97
+ pair_info['accFundingLong'],
98
+ pair_info['accFundingShort'],
99
+ pair_info['lastFundingRate'],
100
+ pair_info['lastFundingVelocity'],
101
+ pair_info['maxFundingFeePerBlock'],
102
+ pair_info['lastFundingBlock'],
103
+ str(block_number),
104
+ pair_info['longOI'],
105
+ pair_info['shortOI']
106
+ )
107
+
108
+ # Calculate funding fee
109
+ funding_raw = GetTradeFundingFee(
110
+ trade_details['funding'],
111
+ funding_rate_raw['accFundingLong'] if trade_details['isBuy'] else funding_rate_raw['accFundingShort'],
112
+ trade_details['collateral'],
113
+ trade_details['leverage']
114
+ )
115
+
116
+ # Calculate liquidation price
117
+ liquidation_price = GetTradeLiquidationPrice(
118
+ trade_details['openPrice'],
119
+ trade_details['isBuy'],
120
+ trade_details['collateral'],
121
+ trade_details['leverage'],
122
+ str(rollover_raw),
123
+ str(funding_raw)
124
+ )
125
+ liquidation_price = Decimal(liquidation_price) / PRECISION_18
126
+
127
+ # Calculate price impact
128
+ price_impact_raw = GetPriceImpact(
129
+ str(int(Decimal(str(price_data['mid'])) * PRECISION_18)),
130
+ str(int(Decimal(str(price_data['bid'])) * PRECISION_18)),
131
+ str(int(Decimal(str(price_data['ask'])) * PRECISION_18)),
132
+ pair_info['spreadP'],
133
+ False,
134
+ trade_details['isBuy'],
135
+ True,
136
+ str(Decimal(trade_details['collateral']) *
137
+ Decimal(trade_details['leverage']) / PRECISION_2),
138
+ pair_info['tradeSizeRef']
139
+ )
140
+ price_after_impact = price_impact_raw['priceAfterImpact']
141
+
142
+ # Calculate PNL
143
+ pnl_raw = CurrentTradeProfitRaw(
144
+ trade_details['openPrice'],
145
+ price_after_impact,
146
+ trade_details['isBuy'],
147
+ trade_details['leverage'],
148
+ trade_details['collateral']
149
+ )
150
+
151
+ # Calculate total profit
152
+ total_profit_raw = CurrentTotalProfitRaw(
153
+ trade_details['openPrice'],
154
+ price_after_impact,
155
+ trade_details['isBuy'],
156
+ trade_details['leverage'],
157
+ trade_details['collateral'],
158
+ str(rollover_raw),
159
+ str(funding_raw)
160
+ )
161
+
162
+ # Calculate PNL percentage
163
+ pnl_percent_raw = CurrentTotalProfitP(
164
+ str(total_profit_raw), trade_details['collateral'])
165
+
166
+ # Convert values to proper decimals
167
+ pnl = Decimal(pnl_raw) / PRECISION_6
168
+ pnl_percent = Decimal(pnl_percent_raw) / PRECISION_6
169
+ net_pnl = Decimal(total_profit_raw) / PRECISION_6
170
+ total_profit = Decimal(total_profit_raw) / PRECISION_6
171
+ funding = Decimal(funding_raw) / PRECISION_6
172
+ rollover = Decimal(rollover_raw) / PRECISION_6
173
+ net_value = net_pnl + (Decimal(trade_details['collateral']) / PRECISION_6)
174
+ price_impact = Decimal(price_after_impact) / PRECISION_18
175
+
176
+ return {
177
+ 'pnl': float(pnl),
178
+ 'pnl_raw': str(pnl_raw),
179
+ 'pnl_percent': float(pnl_percent),
180
+ 'rollover': float(rollover),
181
+ 'funding': float(funding),
182
+ 'funding_raw': str(funding_raw),
183
+ 'rollover_raw': str(rollover_raw),
184
+ 'total_profit': float(total_profit),
185
+ 'net_pnl': float(net_pnl),
186
+ 'net_value': float(net_value),
187
+ 'liquidation_price': float(liquidation_price),
188
+ 'price_impact': float(price_impact)
189
+ }
190
+
191
+
192
+ def ceil_div(a: int, b: int) -> int:
193
+ """Implements ceiling division for integers"""
194
+ return -((-a) // b)
195
+
196
+
197
+ def GetFundingRate(
198
+ acc_per_oi_long: str,
199
+ acc_per_oi_short: str,
200
+ last_funding_rate: str,
201
+ last_velocity: str,
202
+ max_funding_fee_per_block: str,
203
+ last_update_block: str,
204
+ latest_block: str,
205
+ oi_long: str,
206
+ oi_short: str,
207
+ ) -> Dict[str, Union[str, bool]]:
208
+ # Convert string inputs to integers (similar to BigNumber.from)
209
+ acc_per_oi_long_bn = int(acc_per_oi_long)
210
+ acc_per_oi_short_bn = int(acc_per_oi_short)
211
+ last_funding_rate_bn = int(last_funding_rate)
212
+ last_velocity_bn = int(last_velocity)
213
+ max_funding_fee_per_block_bn = int(max_funding_fee_per_block)
214
+ last_update_block_bn = int(last_update_block)
215
+ latest_block_bn = int(latest_block)
216
+ oi_long_bn = int(oi_long)
217
+ oi_short_bn = int(oi_short)
218
+
219
+ value_long = acc_per_oi_long_bn
220
+ value_short = acc_per_oi_short_bn
221
+ fr = 0
222
+
223
+ abs_last_funding_rate = abs(last_funding_rate_bn)
224
+ abs_last_velocity = abs(last_velocity_bn)
225
+
226
+ num_blocks = latest_block_bn - last_update_block_bn
227
+ new_funding_rate = last_funding_rate_bn + (last_velocity_bn * num_blocks)
228
+ abs_new_funding_rate = abs(new_funding_rate)
229
+
230
+ num_blocks_to_charge = num_blocks
231
+
232
+ accumulated_funding_rate_change = 0
233
+ longs_pay = False
234
+ funding_rate_to_use = 0
235
+
236
+ if abs_new_funding_rate > max_funding_fee_per_block_bn:
237
+ num_blocks_to_limit = (
238
+ max_funding_fee_per_block_bn - abs_last_funding_rate) // abs_last_velocity
239
+
240
+ if (new_funding_rate * last_funding_rate_bn) < 0:
241
+ num_blocks_to_charge = num_blocks_to_charge + \
242
+ (2 * last_funding_rate_bn) // last_velocity_bn
243
+
244
+ accumulated_funding_rate_change = (
245
+ abs_last_funding_rate +
246
+ (ceil_div(num_blocks_to_limit, 2) * abs_last_velocity)
247
+ ) * num_blocks_to_limit + (
248
+ (num_blocks_to_charge - num_blocks_to_limit) *
249
+ max_funding_fee_per_block_bn
250
+ )
251
+
252
+ if new_funding_rate > 0:
253
+ longs_pay = True
254
+ fr = max_funding_fee_per_block_bn
255
+ else:
256
+ longs_pay = False
257
+ fr = -max_funding_fee_per_block_bn
258
+ else:
259
+ funding_rate_to_use = abs_new_funding_rate if abs_new_funding_rate > abs_last_funding_rate else abs_last_funding_rate
260
+
261
+ if (last_funding_rate_bn * new_funding_rate) < 0:
262
+ num_blocks_to_charge = num_blocks_to_charge - \
263
+ (2 * funding_rate_to_use) // abs_last_velocity
264
+
265
+ longs_pay = (new_funding_rate + last_funding_rate_bn) >= 0
266
+
267
+ accumulated_funding_rate_change = (
268
+ funding_rate_to_use +
269
+ (ceil_div(num_blocks_to_charge, 2) * abs_last_velocity)
270
+ ) * num_blocks_to_charge
271
+
272
+ fr = new_funding_rate
273
+
274
+ if longs_pay:
275
+ value_long = value_long + \
276
+ (accumulated_funding_rate_change if oi_long_bn > 0 else 0)
277
+
278
+ if oi_short_bn != 0:
279
+ value_short = value_short - \
280
+ (accumulated_funding_rate_change * oi_long_bn) // oi_short_bn
281
+ else:
282
+ value_short = value_short + \
283
+ (accumulated_funding_rate_change if oi_short_bn > 0 else 0)
284
+
285
+ if oi_long_bn != 0:
286
+ value_long = value_long - \
287
+ (accumulated_funding_rate_change * oi_short_bn) // oi_long_bn
288
+
289
+ return {
290
+ 'accFundingLong': str(value_long),
291
+ 'accFundingShort': str(value_short),
292
+ 'latestFundingRate': str(fr),
293
+ 'longsPay': longs_pay,
294
+ }
@@ -0,0 +1,320 @@
1
+ import os
2
+ import traceback
3
+ from enum import Enum
4
+
5
+ from .abi import usdc_abi, ostium_trading_abi, ostium_trading_storage_abi
6
+ from web3 import Web3
7
+ from .utils import convert_to_scaled_integer, fromErrorCodeToMessage, get_tp_sl_prices, to_base_units
8
+
9
+
10
+ class OpenOrderType(Enum):
11
+ MARKET = 0
12
+ LIMIT = 1
13
+ STOP = 2
14
+
15
+
16
+ class Ostium:
17
+ def __init__(self, w3: Web3, usdc_address: str, ostium_trading_storage_address: str, ostium_trading_address: str) -> None:
18
+ self.web3 = w3
19
+
20
+ self.usdc_address = usdc_address
21
+ self.ostium_trading_storage_address = ostium_trading_storage_address
22
+ self.ostium_trading_address = ostium_trading_address
23
+
24
+ # Create contract instance
25
+ self.usdc_contract = self.web3.eth.contract(
26
+ address=self.usdc_address, abi=usdc_abi)
27
+ self.ostium_trading_storage_contract = self.web3.eth.contract(
28
+ address=self.ostium_trading_storage_address, abi=ostium_trading_storage_abi)
29
+ self.ostium_trading_contract = self.web3.eth.contract(
30
+ address=self.ostium_trading_address, abi=ostium_trading_abi)
31
+
32
+ def get_block_number(self):
33
+ return self.web3.eth.get_block('latest')['number']
34
+
35
+ def get_nonce(self, address):
36
+ return self.web3.eth.get_transaction_count(address)
37
+
38
+ def perform_trade(self, trade_params, pvt_key, at_price):
39
+ account = self.web3.eth.account.from_key(pvt_key)
40
+
41
+ amount = to_base_units(trade_params['collateral'], decimals=6)
42
+ self.__approve(account, amount)
43
+ try:
44
+ # Log input trade parameters for verification
45
+ print("Final trade parameters being sent:", trade_params)
46
+
47
+ tp_price, sl_price = get_tp_sl_prices(trade_params)
48
+ # Define trade parameters and execute trade
49
+ trade = {
50
+ 'collateral': convert_to_scaled_integer(trade_params['collateral'], precision=5, scale=6),
51
+ # Example open price, adjust as needed
52
+ 'openPrice': convert_to_scaled_integer(at_price),
53
+ 'tp': convert_to_scaled_integer(tp_price),
54
+ 'sl': convert_to_scaled_integer(sl_price),
55
+ 'trader': account.address,
56
+ 'leverage': to_base_units(trade_params['leverage'], decimals=2),
57
+ 'pairIndex': int(trade_params['asset_type']),
58
+ 'index': 0,
59
+ 'buy': trade_params['direction']
60
+ }
61
+
62
+ order_type = OpenOrderType.MARKET.value # assume market order
63
+
64
+ if 'order_type' in trade_params:
65
+ if trade_params['order_type'] == 'LIMIT':
66
+ order_type = OpenOrderType.LIMIT.value
67
+ elif trade_params['order_type'] == 'STOP':
68
+ order_type = OpenOrderType.STOP.value
69
+ elif trade_params['order_type'] == 'MARKET':
70
+ pass
71
+ else:
72
+ raise Exception('Invalid order type')
73
+
74
+ # Log the structured trade object to be sent
75
+ # print("Structured trade object:", trade)
76
+
77
+ trade_tx = self.ostium_trading_contract.functions.openTrade(
78
+ trade, order_type, 9000).build_transaction({'from': account.address})
79
+ trade_tx['nonce'] = self.get_nonce(account.address)
80
+
81
+ signed_tx = self.web3.eth.account.sign_transaction(
82
+ trade_tx, private_key=account.key)
83
+ trade_tx_hash = self.web3.eth.send_raw_transaction(
84
+ signed_tx.raw_transaction)
85
+ # print('Trade TX Hash:', trade_tx_hash.hex())
86
+
87
+ # Wait for the trade transaction to be mined
88
+ trade_receipt = self.web3.eth.wait_for_transaction_receipt(
89
+ trade_tx_hash)
90
+ print('Trade Receipt:', trade_receipt)
91
+ return trade_receipt
92
+ except Exception as e:
93
+ reason_string, suggestion = fromErrorCodeToMessage(e)
94
+ print(
95
+ f"An error ({str(e)}) occurred during the trading process - parsed as {reason_string}")
96
+ # traceback.print_exc() # This prints the full traceback
97
+ raise Exception(
98
+ f'{reason_string}\n\n{suggestion}' if suggestion != None else reason_string)
99
+
100
+ # Cancel Limit and Stop orders
101
+
102
+ def cancel_limit_order(self, pairID, index, pvt_key):
103
+ account = self.web3.eth.account.from_key(pvt_key)
104
+
105
+ trade_tx = self.ostium_trading_contract.functions.cancelOpenLimitOrder(
106
+ int(pairID), int(index)).build_transaction({'from': account.address})
107
+ trade_tx['nonce'] = self.get_nonce(account.address)
108
+
109
+ signed_tx = self.web3.eth.account.sign_transaction(
110
+ trade_tx, private_key=account.key)
111
+ trade_tx_hash = self. web3.eth.send_raw_transaction(
112
+ signed_tx.raw_transaction)
113
+ print('Cancel Limit Order TX Hash:', trade_tx_hash.hex())
114
+
115
+ # Wait for the trade transaction to be mined
116
+ trade_receipt = self.web3.eth.wait_for_transaction_receipt(
117
+ trade_tx_hash)
118
+ print('Cancel Limit Order Receipt:', trade_receipt)
119
+ return trade_receipt
120
+
121
+ def close_trade(self, pairID, index, pvt_key):
122
+ account = self.web3.eth.account.from_key(pvt_key)
123
+
124
+ trade_tx = self.ostium_trading_contract.functions.closeTradeMarket(
125
+ int(pairID), int(index)).build_transaction({'from': account.address})
126
+ trade_tx['nonce'] = self.get_nonce(account.address)
127
+
128
+ signed_tx = self.web3.eth.account.sign_transaction(
129
+ trade_tx, private_key=account.key)
130
+ trade_tx_hash = self. web3.eth.send_raw_transaction(
131
+ signed_tx.raw_transaction)
132
+ print('Trade TX Hash:', trade_tx_hash.hex())
133
+
134
+ # Wait for the trade transaction to be mined
135
+ trade_receipt = self.web3.eth.wait_for_transaction_receipt(
136
+ trade_tx_hash)
137
+ print('Trade Receipt:', trade_receipt)
138
+ return trade_receipt
139
+
140
+ def add_collateral(self, pairID, index, collateral, pvt_key):
141
+ try:
142
+ account = self.web3.eth.account.from_key(pvt_key)
143
+ amount = to_base_units(collateral, decimals=6)
144
+ self.__approve(account, amount)
145
+
146
+ add_collateral_tx = self.ostium_trading_contract.functions.topUpCollateral(
147
+ int(pairID), int(index), amount).build_transaction({'from': account.address})
148
+ add_collateral_tx['nonce'] = self.get_nonce(account.address)
149
+
150
+ signed_tx = self.web3.eth.account.sign_transaction(
151
+ add_collateral_tx, private_key=account.key)
152
+ add_collateral_tx_hash = self.web3.eth.send_raw_transaction(
153
+ signed_tx.raw_transaction)
154
+ print('Add Collateral TX Hash:', add_collateral_tx_hash.hex())
155
+ # Wait for the trade transaction to be mined
156
+ add_collateral_receipt = self.web3.eth.wait_for_transaction_receipt(
157
+ add_collateral_tx_hash)
158
+ print('Add Collateral Receipt:', add_collateral_receipt)
159
+ return add_collateral_receipt
160
+
161
+ except Exception as e:
162
+ print("An error occurred during the add collateral process:")
163
+ traceback.print_exc() # This prints the full traceback
164
+ raise e # Optionally re-raise the exception if you want it to propagate
165
+
166
+ def update_tp(self, pairID, index, tp, pvt_key):
167
+ try:
168
+ account = self.web3.eth.account.from_key(pvt_key)
169
+
170
+ tp_value = to_base_units(tp, decimals=18)
171
+
172
+ update_tp_tx = self.ostium_trading_contract.functions.updateTp(
173
+ int(pairID), int(index), tp_value).build_transaction({'from': account.address})
174
+ update_tp_tx['nonce'] = self.get_nonce(account.address)
175
+
176
+ signed_tx = self.web3.eth.account.sign_transaction(
177
+ update_tp_tx, private_key=account.key)
178
+ update_tp_tx_hash = self.web3.eth.send_raw_transaction(
179
+ signed_tx.raw_transaction)
180
+ print('Update TP TX Hash:', update_tp_tx_hash.hex())
181
+ except Exception as e:
182
+ print("An error occurred during the update tp process:")
183
+ traceback.print_exc() # This prints the full traceback
184
+ raise e # Optionally re-raise the exception if you want it to propagate
185
+
186
+ def update_sl(self, pairID, index, sl, pvt_key):
187
+ try:
188
+ account = self.web3.eth.account.from_key(pvt_key)
189
+ sl_value = to_base_units(sl, decimals=18)
190
+
191
+ update_sl_tx = self.ostium_trading_contract.functions.updateSl(
192
+ int(pairID), int(index), sl_value).build_transaction({'from': account.address})
193
+ update_sl_tx['nonce'] = self.get_nonce(account.address)
194
+
195
+ signed_tx = self.web3.eth.account.sign_transaction(
196
+ update_sl_tx, private_key=account.key)
197
+ update_sl_tx_hash = self.web3.eth.send_raw_transaction(
198
+ signed_tx.raw_transaction)
199
+ print('Update SL TX Hash:', update_sl_tx_hash.hex())
200
+ except Exception as e:
201
+ print(f"An error occurred during the update sl process: {e}")
202
+ reason_string, suggestion = fromErrorCodeToMessage(str(e))
203
+ print(
204
+ f"An error occurred during the update sl process: {reason_string}")
205
+ # traceback.print_exc() # This prints the full traceback
206
+ # Optionally re-raise the exception if you want it to propagate
207
+ raise Exception(
208
+ f'{reason_string}\n\n{suggestion}' if suggestion != None else reason_string)
209
+
210
+ def __approve(self, account, collateral):
211
+ # Approve the transaction
212
+ # Approval tx should only be done if sufficient amount is not already approved.
213
+
214
+ allowance = self.usdc_contract.functions.allowance(
215
+ account.address, self.ostium_trading_storage_address).call()
216
+
217
+ if allowance < collateral:
218
+ approve_tx = self.usdc_contract.functions.approve(
219
+ self.ostium_trading_storage_address,
220
+ self.web3.to_wei(1000000, 'mwei')
221
+ ).build_transaction({'from': account.address})
222
+
223
+ approve_tx['nonce'] = self.get_nonce(account.address)
224
+
225
+ signed_tx = self.web3.eth.account.sign_transaction(
226
+ approve_tx, private_key=account.key)
227
+ approve_tx_hash = self.web3.eth.send_raw_transaction(
228
+ signed_tx.raw_transaction)
229
+ print('Approval TX Hash:', approve_tx_hash.hex())
230
+
231
+ # Ensure the approval transaction is mined
232
+ approve_receipt = self.web3.eth.wait_for_transaction_receipt(
233
+ approve_tx_hash)
234
+ print('Approval Receipt:', approve_receipt)
235
+
236
+ def withdraw(self, amount, receiving_address, pvt_key):
237
+ try:
238
+ account = self.web3.eth.account.from_key(pvt_key)
239
+ amount_in_base_units = to_base_units(amount, decimals=6)
240
+
241
+ if not self.web3.is_address(receiving_address):
242
+ raise ValueError("Invalid Arbitrum address format")
243
+
244
+ # Use USDCs' transfer function to send funds to the receiving address
245
+ transfer_tx = self.usdc_contract.functions.transfer(
246
+ receiving_address,
247
+ amount_in_base_units
248
+ ).build_transaction({'from': account.address})
249
+
250
+ transfer_tx['nonce'] = self.get_nonce(account.address)
251
+
252
+ signed_tx = self.web3.eth.account.sign_transaction(
253
+ transfer_tx, private_key=account.key)
254
+ transfer_tx_hash = self.web3.eth.send_raw_transaction(
255
+ signed_tx.raw_transaction)
256
+ print('Transfer TX Hash:', transfer_tx_hash.hex())
257
+
258
+ transfer_receipt = self.web3.eth.wait_for_transaction_receipt(
259
+ transfer_tx_hash)
260
+ print('Transfer Receipt:', transfer_receipt)
261
+ return transfer_receipt
262
+
263
+ except Exception as e:
264
+ reason_string, suggestion = fromErrorCodeToMessage(str(e))
265
+ print(
266
+ f"An error occurred during the transfer process: {reason_string}")
267
+ raise Exception(
268
+ f'{reason_string}\n\n{suggestion}' if suggestion != None else reason_string)
269
+
270
+ def update_limit_order(self, pair_id, index, pvt_key, price=None, tp=None, sl=None):
271
+ try:
272
+ print('update_limit_order called with pair_id', pair_id, 'index',
273
+ index, 'pvt_key', pvt_key, 'price', price, 'tp', tp, 'sl', sl)
274
+ account = self.web3.eth.account.from_key(pvt_key)
275
+
276
+ # Get existing order details (tbd why read from storage)
277
+ existing_order = self.ostium_trading_storage_contract.functions.getOpenLimitOrder(
278
+ account.address,
279
+ int(pair_id),
280
+ int(index)
281
+ ).call()
282
+
283
+ print('existing_order', existing_order)
284
+ # Use existing values if new values are not provided
285
+ price_value = convert_to_scaled_integer(
286
+ price) if price is not None else existing_order[1] # openPrice
287
+ tp_value = convert_to_scaled_integer(
288
+ tp) if tp is not None else existing_order[2] # tp
289
+ sl_value = convert_to_scaled_integer(
290
+ sl) if sl is not None else existing_order[3] # sl
291
+
292
+ print('calling updateOpenLimitOrder with price_value',
293
+ price_value, 'tp_value', tp_value, 'sl_value', sl_value)
294
+ trade_tx = self.ostium_trading_contract.functions.updateOpenLimitOrder(
295
+ int(pair_id),
296
+ int(index),
297
+ price_value,
298
+ tp_value,
299
+ sl_value
300
+ ).build_transaction({'from': account.address})
301
+
302
+ trade_tx['nonce'] = self.get_nonce(account.address)
303
+
304
+ signed_tx = self.web3.eth.account.sign_transaction(
305
+ trade_tx, private_key=account.key)
306
+ trade_tx_hash = self.web3.eth.send_raw_transaction(
307
+ signed_tx.raw_transaction)
308
+ print('Update Limit Order TX Hash:', trade_tx_hash.hex())
309
+
310
+ trade_receipt = self.web3.eth.wait_for_transaction_receipt(
311
+ trade_tx_hash)
312
+ print('Update Limit Order Receipt:', trade_receipt)
313
+ return trade_receipt
314
+
315
+ except Exception as e:
316
+ reason_string, suggestion = fromErrorCodeToMessage(str(e))
317
+ print(
318
+ f"An error occurred during the update limit order process: {reason_string}")
319
+ raise Exception(
320
+ f'{reason_string}\n\n{suggestion}' if suggestion != None else reason_string)
@@ -0,0 +1,37 @@
1
+ import aiohttp
2
+ import json
3
+ from typing import Tuple
4
+
5
+
6
+ class Price:
7
+ def __init__(self):
8
+ self.base_url = "https://listener.ostium.io"
9
+
10
+ async def get_latest_prices(self):
11
+ """
12
+ Fetches the latest prices from the Ostium price listener service.
13
+ Returns a dictionary of price data.
14
+ """
15
+ async with aiohttp.ClientSession() as session:
16
+ async with session.get(f"{self.base_url}/PricePublish/latest-prices") as response:
17
+ if response.status == 200:
18
+ return await response.json()
19
+ else:
20
+ raise Exception(
21
+ f"Failed to fetch prices: {response.status}")
22
+
23
+ async def get_price(self, from_asset: str, to_asset: str) -> Tuple[float, bool]:
24
+ """
25
+ Get the latest price and market status for a specific asset pair.
26
+ Args:
27
+ from_asset: The base asset symbol (e.g., "BTC")
28
+ to_asset: The quote asset symbol (e.g., "USD")
29
+ Returns:
30
+ Tuple[float, bool]: The latest price and market open status for the asset pair
31
+ """
32
+ prices = await self.get_latest_prices()
33
+ for price_data in prices:
34
+ if (price_data.get('from') == from_asset and
35
+ price_data.get('to') == to_asset):
36
+ return float(price_data.get('mid', 0)), price_data.get('isMarketOpen', False)
37
+ raise ValueError(f"No price found for pair: {from_asset}/{to_asset}")
@@ -0,0 +1,33 @@
1
+ from typing import Optional
2
+ from web3 import Web3
3
+ from .config import NetworkConfig
4
+ from .ostium import Ostium
5
+ # from .formulae import Formulae
6
+ from .subgraph import SubgraphClient
7
+ from .price import Price
8
+ from .balance import Balance
9
+
10
+
11
+ class OstiumSDK:
12
+ def __init__(
13
+ self,
14
+ network_config: NetworkConfig,
15
+ custom_rpc_url: Optional[str] = None,
16
+ custom_graph_url: Optional[str] = None
17
+ ):
18
+ self.config = network_config
19
+ self.w3 = Web3(Web3.HTTPProvider(
20
+ custom_rpc_url or network_config.rpc_url
21
+ ))
22
+ self.ostium = Ostium(
23
+ self.w3,
24
+ network_config.contracts["usdc"],
25
+ network_config.contracts["trading"],
26
+ network_config.contracts["tradingStorage"]
27
+ )
28
+ # self.formulae = Formulae()
29
+ self.subgraph = SubgraphClient(
30
+ custom_graph_url or network_config.graph_url
31
+ )
32
+ self.price = Price()
33
+ self.balance = Balance(self.w3, network_config.contracts["usdc"])