ostium-python-sdk 0.1.42__tar.gz → 0.2.0__tar.gz

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.
Files changed (35) hide show
  1. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/PKG-INFO +33 -8
  2. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/README.md +19 -7
  3. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/balance.py +6 -2
  4. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/faucet.py +9 -3
  5. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/formulae.py +0 -3
  6. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/formulae_wrapper.py +7 -6
  7. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/ostium.py +47 -34
  8. ostium_python_sdk-0.2.0/ostium_python_sdk/price.py +47 -0
  9. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/sdk.py +49 -6
  10. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/subgraph.py +10 -6
  11. ostium_python_sdk-0.2.0/ostium_python_sdk/utils.py +188 -0
  12. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk.egg-info/PKG-INFO +33 -8
  13. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/setup.py +1 -1
  14. ostium_python_sdk-0.1.42/ostium_python_sdk/price.py +0 -37
  15. ostium_python_sdk-0.1.42/ostium_python_sdk/utils.py +0 -604
  16. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/MANIFEST.in +0 -0
  17. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/__init__.py +0 -0
  18. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/abi/__init__.py +0 -0
  19. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/abi/abi.py +0 -0
  20. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/abi/faucet_abi.py +0 -0
  21. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/config.py +0 -0
  22. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/constants.py +0 -0
  23. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk/exceptions.py +0 -0
  24. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk.egg-info/SOURCES.txt +0 -0
  25. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk.egg-info/dependency_links.txt +0 -0
  26. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk.egg-info/requires.txt +0 -0
  27. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/ostium_python_sdk.egg-info/top_level.txt +0 -0
  28. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/pyproject.toml +0 -0
  29. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/requirements-dev.txt +0 -0
  30. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/requirements.txt +0 -0
  31. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/setup.cfg +0 -0
  32. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/tests/__init__.py +0 -0
  33. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/tests/test_slippage.py +0 -0
  34. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/tests/test_trade_get_tp_price.py +0 -0
  35. {ostium_python_sdk-0.1.42 → ostium_python_sdk-0.2.0}/tests/test_trade_liquidation_price.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ostium-python-sdk
3
- Version: 0.1.42
3
+ Version: 0.2.0
4
4
  Summary: A python based SDK developed for interacting with Ostium, a leveraged trading application for trading currencies, commodities, indices, crypto and more.
5
5
  Home-page: https://github.com/0xOstium/ostium-python-sdk
6
6
  Author: ami@ostium.io
@@ -100,8 +100,6 @@ Developed using:
100
100
  You can instantiate the SDK with the following parameters.
101
101
  Ostium Platform is deployed on Arbitrum. You can use the testnet or mainnet config via the `NetworkConfig` class, see below for an example.
102
102
 
103
-
104
-
105
103
  ```python
106
104
  from dotenv import load_dotenv
107
105
  from ostium_python_sdk import OstiumSDK, NetworkConfig
@@ -118,14 +116,12 @@ rpc_url = os.getenv('RPC_URL')
118
116
  if not rpc_url:
119
117
  raise ValueError("RPC_URL not found in .env file")
120
118
 
121
- # Initialize SDK
119
+ # Initialize SDK (default: verbose=False for quiet operation)
122
120
  configTestnet = NetworkConfig.testnet()
123
121
  sdk = OstiumSDK(configTestnet, private_key)
124
122
 
125
- # NOTE: When you are ready to go live, you can switch to mainnet usage as simple as specifying the below init code:
126
- # configMainnet = NetworkConfig.mainnet()
127
- # sdk = OstiumSDK(configMainnet, private_key)
128
- #
123
+ # For verbose mode with detailed logging:
124
+ sdk = OstiumSDK(configTestnet, private_key, verbose=True)
129
125
  ```
130
126
 
131
127
  <b>NOTE:</b> create a .env file with PRIVATE_KEY and RPC_URL to use the SDK. An RPC URL is required to use the SDK. You can get one by signing up for a free account at https://www.alchemy.com/ and creating an app.
@@ -275,6 +271,8 @@ trade_params = {
275
271
  'asset_type': 0, # 0 for BTC, see pair_details above for other asset types
276
272
  'direction': True, # True for Long, False for Short
277
273
  'order_type': 'MARKET' # 'MARKET', 'LIMIT', or 'STOP'
274
+ #'tp': 0, # Take Profit price - if not specified or Zero means no TP
275
+ #'sl': 0, # Stop Loss price - if not specified or Zero means no SL
278
276
  }
279
277
 
280
278
  try:
@@ -329,6 +327,20 @@ except Exception as e:
329
327
 
330
328
  ```
331
329
 
330
+
331
+ **NOTE:** Use SDK method `get_open_trade_metrics` every so often while trade is open to get the trade's metrics such as:
332
+
333
+ - Funding fee
334
+ - Roll over fee
335
+ - Unrealized Pnl and Pnl Percent
336
+ - Total Profit
337
+ - Liquidation Price
338
+
339
+ ```python
340
+ metrics = await sdk.get_open_trade_metrics(pair_id, trade_index)
341
+ print(metrics)
342
+ ```
343
+
332
344
  ### Create a Short ETH Limit Order
333
345
 
334
346
  This example shows how to create a short ETH limit order, 10% above the current ETHUSD price. So if price goes up 10% we order a Short ETH trade.
@@ -490,6 +502,19 @@ All notable changes to the Ostium Python SDK will be documented in this file.
490
502
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
491
503
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
492
504
 
505
+ ## [0.2.0] - 2025-02-20
506
+
507
+ ### Added
508
+
509
+ - Added sdk method `get_open_trade_metrics` to get open trade metrics such as:
510
+ - Funding fee
511
+ - Roll over fee
512
+ - Unrealized Pnl and Pnl Percent
513
+ - Total Profit
514
+ - Liquidation Price
515
+
516
+ - Added verbose mode to sdk (default is False, set to True to see debug logs)
517
+
493
518
  ## [0.1.36] - 2025-01-14
494
519
 
495
520
  ### Added
@@ -61,8 +61,6 @@ Developed using:
61
61
  You can instantiate the SDK with the following parameters.
62
62
  Ostium Platform is deployed on Arbitrum. You can use the testnet or mainnet config via the `NetworkConfig` class, see below for an example.
63
63
 
64
-
65
-
66
64
  ```python
67
65
  from dotenv import load_dotenv
68
66
  from ostium_python_sdk import OstiumSDK, NetworkConfig
@@ -79,14 +77,12 @@ rpc_url = os.getenv('RPC_URL')
79
77
  if not rpc_url:
80
78
  raise ValueError("RPC_URL not found in .env file")
81
79
 
82
- # Initialize SDK
80
+ # Initialize SDK (default: verbose=False for quiet operation)
83
81
  configTestnet = NetworkConfig.testnet()
84
82
  sdk = OstiumSDK(configTestnet, private_key)
85
83
 
86
- # NOTE: When you are ready to go live, you can switch to mainnet usage as simple as specifying the below init code:
87
- # configMainnet = NetworkConfig.mainnet()
88
- # sdk = OstiumSDK(configMainnet, private_key)
89
- #
84
+ # For verbose mode with detailed logging:
85
+ sdk = OstiumSDK(configTestnet, private_key, verbose=True)
90
86
  ```
91
87
 
92
88
  <b>NOTE:</b> create a .env file with PRIVATE_KEY and RPC_URL to use the SDK. An RPC URL is required to use the SDK. You can get one by signing up for a free account at https://www.alchemy.com/ and creating an app.
@@ -236,6 +232,8 @@ trade_params = {
236
232
  'asset_type': 0, # 0 for BTC, see pair_details above for other asset types
237
233
  'direction': True, # True for Long, False for Short
238
234
  'order_type': 'MARKET' # 'MARKET', 'LIMIT', or 'STOP'
235
+ #'tp': 0, # Take Profit price - if not specified or Zero means no TP
236
+ #'sl': 0, # Stop Loss price - if not specified or Zero means no SL
239
237
  }
240
238
 
241
239
  try:
@@ -290,6 +288,20 @@ except Exception as e:
290
288
 
291
289
  ```
292
290
 
291
+
292
+ **NOTE:** Use SDK method `get_open_trade_metrics` every so often while trade is open to get the trade's metrics such as:
293
+
294
+ - Funding fee
295
+ - Roll over fee
296
+ - Unrealized Pnl and Pnl Percent
297
+ - Total Profit
298
+ - Liquidation Price
299
+
300
+ ```python
301
+ metrics = await sdk.get_open_trade_metrics(pair_id, trade_index)
302
+ print(metrics)
303
+ ```
304
+
293
305
  ### Create a Short ETH Limit Order
294
306
 
295
307
  This example shows how to create a short ETH limit order, 10% above the current ETHUSD price. So if price goes up 10% we order a Short ETH trade.
@@ -8,14 +8,19 @@ REFRESH_BALANCE_SECONDS_INTERVAL = 60 * 5
8
8
 
9
9
 
10
10
  class Balance:
11
- def __init__(self, w3: Web3, usdc_address: str) -> None:
11
+ def __init__(self, w3: Web3, usdc_address: str, verbose=False) -> None:
12
12
  self.web3 = w3
13
13
  self.usdc_address = usdc_address
14
+ self.verbose = verbose
14
15
  self.usdc_contract = self.web3.eth.contract(
15
16
  address=self.usdc_address, abi=usdc_abi)
16
17
  # Format: {address: {'ether': value, 'usdc': value, 'last_refresh': timestamp}}
17
18
  self.balances = {}
18
19
 
20
+ def log(self, message):
21
+ if self.verbose:
22
+ print(message)
23
+
19
24
  def get_balance(self, address, refresh=False):
20
25
  if address not in self.balances:
21
26
  self.balances[address] = {'ether': None,
@@ -34,7 +39,6 @@ class Balance:
34
39
  return self.balances[address]['ether'], self.balances[address]['usdc']
35
40
 
36
41
  def read_balances(self, address):
37
- # print(f'actual [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] {address} - reading balances')
38
42
  start_time = time.time()
39
43
  self.balances[address] = {
40
44
  'ether': Decimal(self.get_ether_balance(address)),
@@ -8,8 +8,9 @@ from ostium_python_sdk.abi.faucet_abi import faucet_abi # Import the ABI
8
8
 
9
9
 
10
10
  class Faucet:
11
- def __init__(self, w3: Web3, private_key: str) -> None:
11
+ def __init__(self, w3: Web3, private_key: str, verbose=False) -> None:
12
12
  self.web3 = w3
13
+ self.verbose = verbose
13
14
  self.private_key = private_key
14
15
  self.faucet_address = "0x6830C550814105d8B27bDAEC0DB391cAa7B967c8"
15
16
  self.faucet_contract = self.web3.eth.contract(
@@ -17,6 +18,10 @@ class Faucet:
17
18
  abi=faucet_abi
18
19
  )
19
20
 
21
+ def log(self, message):
22
+ if self.verbose:
23
+ print(message)
24
+
20
25
  def _format_waiting_time(self, next_request_time: int) -> str:
21
26
  """Format the waiting time into a human-readable string"""
22
27
  current_time = int(time.time())
@@ -51,6 +56,7 @@ class Faucet:
51
56
 
52
57
  def request_tokens(self) -> dict:
53
58
  account = self._get_account()
59
+ self.log("Requesting tokens from faucet")
54
60
  """
55
61
  Request testnet USDC tokens from the faucet.
56
62
  Raises:
@@ -114,10 +120,10 @@ class Faucet:
114
120
  def get_token_amount(self) -> int:
115
121
  """Get the amount of tokens that will be received from the faucet"""
116
122
  try:
117
- print(f"Debug - Calling tokenAmount() function") # Add debug print
123
+ self.log("Calling tokenAmount() function") # Add debug print
118
124
  return self.faucet_contract.functions.tokenAmount().call()
119
125
  except Exception as e:
120
- print(f"Debug - Error details: {str(e)}") # Add debug print
126
+ self.log(f"Error details: {str(e)}") # Add debug print
121
127
  raise Exception(f"Failed to get token amount: {str(e)}")
122
128
 
123
129
  def get_next_request_time(self, address: str) -> int:
@@ -96,11 +96,8 @@ def GetTradeLiquidationPrice(
96
96
  leverage
97
97
  )
98
98
 
99
- # print('# liq_price_distance', liq_price_distance)
100
99
  liq_price = open_price - liq_price_distance if long else open_price + liq_price_distance
101
-
102
100
  liq_price = liq_price if liq_price > 0 else 0
103
- # print('---> liq_price', liq_price)
104
101
  return liq_price
105
102
 
106
103
  except Exception as error:
@@ -1,8 +1,8 @@
1
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)
2
+ from .formulae import (PRECISION_18, PRECISION_2, PRECISION_6, GetCurrentRolloverFee,
3
+ GetTradeFundingFee, GetTradeLiquidationPrice, GetTradeRolloverFee,
4
+ GetFundingRate, GetPriceImpact, CurrentTradeProfitRaw,
5
+ CurrentTotalProfitRaw, CurrentTotalProfitP)
6
6
  from typing import Dict, Union
7
7
 
8
8
 
@@ -59,11 +59,11 @@ def get_funding_fee_long_short(pair_info, block_number):
59
59
  return float(long_rate), float(short_rate)
60
60
 
61
61
 
62
- def get_trade_pnl(trade_details, pair_info, price_data, block_number):
62
+ def get_trade_metrics(trade_details, price_data, block_number):
63
63
  """
64
64
  Calculate PNL and related metrics for a trade.
65
65
  """
66
- if not trade_details or not price_data or not block_number or not pair_info:
66
+ if not trade_details or not price_data or not block_number:
67
67
  return {
68
68
  'pnl': 0,
69
69
  'pnl_raw': '0',
@@ -76,6 +76,7 @@ def get_trade_pnl(trade_details, pair_info, price_data, block_number):
76
76
  'liquidation_price': 0
77
77
  }
78
78
 
79
+ pair_info = trade_details['pair']
79
80
  # Calculate current rollover fee
80
81
  current_rollover_raw = GetCurrentRolloverFee(
81
82
  pair_info['accRollover'],
@@ -2,6 +2,7 @@ import os
2
2
  import traceback
3
3
  from enum import Enum
4
4
  from ostium_python_sdk.constants import PRECISION_2
5
+ from ostium_python_sdk.formulae_wrapper import get_trade_metrics
5
6
  from web3 import Web3
6
7
  from .abi.abi import usdc_abi, ostium_trading_abi, ostium_trading_storage_abi
7
8
  from .utils import convert_to_scaled_integer, fromErrorCodeToMessage, get_tp_sl_prices, to_base_units
@@ -15,8 +16,9 @@ class OpenOrderType(Enum):
15
16
 
16
17
 
17
18
  class Ostium:
18
- def __init__(self, w3: Web3, usdc_address: str, ostium_trading_storage_address: str, ostium_trading_address: str, private_key: str) -> None:
19
+ def __init__(self, w3: Web3, usdc_address: str, ostium_trading_storage_address: str, ostium_trading_address: str, private_key: str, verbose=False) -> None:
19
20
  self.web3 = w3
21
+ self.verbose = verbose
20
22
  self.private_key = private_key
21
23
  self.usdc_address = usdc_address
22
24
  self.ostium_trading_storage_address = ostium_trading_storage_address
@@ -32,12 +34,20 @@ class Ostium:
32
34
 
33
35
  self.slippage_percentage = 2 # 2%
34
36
 
37
+ def log(self, message):
38
+ if self.verbose:
39
+ print(message)
40
+
35
41
  def set_slippage_percentage(self, slippage_percentage):
36
42
  self.slippage_percentage = slippage_percentage
37
43
 
38
44
  def get_slippage_percentage(self):
39
45
  return self.slippage_percentage
40
46
 
47
+ def get_public_address(self):
48
+ public_address = self._get_account().address
49
+ return public_address
50
+
41
51
  def _get_account(self) -> Account:
42
52
  self._check_private_key()
43
53
  """Get account from stored private key"""
@@ -55,12 +65,13 @@ class Ostium:
55
65
  "Private key is required for Ostium platform write-operations")
56
66
 
57
67
  def perform_trade(self, trade_params, at_price):
68
+ self.log(f"Performing trade with params: {trade_params}")
58
69
  account = self._get_account()
59
70
  amount = to_base_units(trade_params['collateral'], decimals=6)
60
71
  self.__approve(account, amount)
61
72
 
62
73
  try:
63
- print("Final trade parameters being sent:", trade_params)
74
+ self.log(f"Final trade parameters being sent: {trade_params}")
64
75
  tp_price, sl_price = get_tp_sl_prices(trade_params)
65
76
 
66
77
  trade = {
@@ -97,50 +108,52 @@ class Ostium:
97
108
  signed_tx.raw_transaction)
98
109
  trade_receipt = self.web3.eth.wait_for_transaction_receipt(
99
110
  trade_tx_hash)
100
- print('Trade Receipt:', trade_receipt)
111
+ self.log(f"Trade Receipt: {trade_receipt}")
101
112
  return trade_receipt
102
113
 
103
114
  except Exception as e:
104
- reason_string, suggestion = fromErrorCodeToMessage(e)
115
+ reason_string, suggestion = fromErrorCodeToMessage(
116
+ e, verbose=self.verbose)
105
117
  print(
106
118
  f"An error ({str(e)}) occurred during the trading process - parsed as {reason_string}")
107
119
  raise Exception(
108
120
  f'{reason_string}\n\n{suggestion}' if suggestion != None else reason_string)
109
121
 
110
- def cancel_limit_order(self, pairID, index):
122
+ def cancel_limit_order(self, pair_id, trade_index):
111
123
  account = self._get_account()
112
124
 
113
125
  trade_tx = self.ostium_trading_contract.functions.cancelOpenLimitOrder(
114
- int(pairID), int(index)).build_transaction({'from': account.address})
126
+ int(pair_id), int(trade_index)).build_transaction({'from': account.address})
115
127
  trade_tx['nonce'] = self.get_nonce(account.address)
116
128
 
117
129
  signed_tx = self.web3.eth.account.sign_transaction(
118
130
  trade_tx, private_key=self.private_key)
119
131
  trade_tx_hash = self.web3.eth.send_raw_transaction(
120
132
  signed_tx.raw_transaction)
121
- print('Cancel Limit Order TX Hash:', trade_tx_hash.hex())
133
+ self.log(f"Cancel Limit Order TX Hash: {trade_tx_hash.hex()}")
122
134
 
123
135
  trade_receipt = self.web3.eth.wait_for_transaction_receipt(
124
136
  trade_tx_hash)
125
- print('Cancel Limit Order Receipt:', trade_receipt)
137
+ self.log(f"Cancel Limit Order Receipt: {trade_receipt}")
126
138
  return trade_receipt
127
139
 
128
- def close_trade(self, pairID, index):
140
+ def close_trade(self, pair_id, trade_index):
141
+ self.log(f"Closing trade for pair {pair_id}, index {trade_index}")
129
142
  account = self._get_account()
130
143
 
131
144
  trade_tx = self.ostium_trading_contract.functions.closeTradeMarket(
132
- int(pairID), int(index)).build_transaction({'from': account.address})
145
+ int(pair_id), int(trade_index)).build_transaction({'from': account.address})
133
146
  trade_tx['nonce'] = self.get_nonce(account.address)
134
147
 
135
148
  signed_tx = self.web3.eth.account.sign_transaction(
136
149
  trade_tx, private_key=self.private_key)
137
150
  trade_tx_hash = self.web3.eth.send_raw_transaction(
138
151
  signed_tx.raw_transaction)
139
- print('Trade TX Hash:', trade_tx_hash.hex())
152
+ self.log(f"Trade TX Hash: {trade_tx_hash.hex()}")
140
153
 
141
154
  trade_receipt = self.web3.eth.wait_for_transaction_receipt(
142
155
  trade_tx_hash)
143
- print('Trade Receipt:', trade_receipt)
156
+ self.log(f"Trade Receipt: {trade_receipt}")
144
157
  return trade_receipt
145
158
 
146
159
  def add_collateral(self, pairID, index, collateral):
@@ -157,11 +170,11 @@ class Ostium:
157
170
  add_collateral_tx, private_key=self.private_key)
158
171
  add_collateral_tx_hash = self.web3.eth.send_raw_transaction(
159
172
  signed_tx.raw_transaction)
160
- print('Add Collateral TX Hash:', add_collateral_tx_hash.hex())
173
+ self.log(f"Add Collateral TX Hash: {add_collateral_tx_hash.hex()}")
161
174
 
162
175
  add_collateral_receipt = self.web3.eth.wait_for_transaction_receipt(
163
176
  add_collateral_tx_hash)
164
- print('Add Collateral Receipt:', add_collateral_receipt)
177
+ self.log(f"Add Collateral Receipt: {add_collateral_receipt}")
165
178
  return add_collateral_receipt
166
179
 
167
180
  except Exception as e:
@@ -169,20 +182,22 @@ class Ostium:
169
182
  traceback.print_exc()
170
183
  raise e
171
184
 
172
- def update_tp(self, pairID, index, tp):
185
+ def update_tp(self, pair_id, trade_index, tp_price):
186
+ self.log(
187
+ f"Updating TP for pair {pair_id}, index {trade_index} to {tp_price}")
173
188
  account = self._get_account()
174
189
  try:
175
- tp_value = to_base_units(tp, decimals=18)
190
+ tp_value = to_base_units(tp_price, decimals=18)
176
191
 
177
192
  update_tp_tx = self.ostium_trading_contract.functions.updateTp(
178
- int(pairID), int(index), tp_value).build_transaction({'from': account.address})
193
+ int(pair_id), int(trade_index), tp_value).build_transaction({'from': account.address})
179
194
  update_tp_tx['nonce'] = self.get_nonce(account.address)
180
195
 
181
196
  signed_tx = self.web3.eth.account.sign_transaction(
182
197
  update_tp_tx, private_key=self.private_key)
183
198
  update_tp_tx_hash = self.web3.eth.send_raw_transaction(
184
199
  signed_tx.raw_transaction)
185
- print('Update TP TX Hash:', update_tp_tx_hash.hex())
200
+ self.log(f"Update TP TX Hash: {update_tp_tx_hash.hex()}")
186
201
 
187
202
  except Exception as e:
188
203
  print("An error occurred during the update tp process:")
@@ -202,10 +217,11 @@ class Ostium:
202
217
  update_sl_tx, private_key=self.private_key)
203
218
  update_sl_tx_hash = self.web3.eth.send_raw_transaction(
204
219
  signed_tx.raw_transaction)
205
- print('Update SL TX Hash:', update_sl_tx_hash.hex())
220
+ self.log(f"Update SL TX Hash: {update_sl_tx_hash.hex()}")
206
221
 
207
222
  except Exception as e:
208
- reason_string, suggestion = fromErrorCodeToMessage(str(e))
223
+ reason_string, suggestion = fromErrorCodeToMessage(
224
+ str(e), verbose=self.verbose)
209
225
  print(
210
226
  f"An error occurred during the update sl process: {reason_string}")
211
227
  raise Exception(
@@ -227,11 +243,11 @@ class Ostium:
227
243
  approve_tx, private_key=self.private_key)
228
244
  approve_tx_hash = self.web3.eth.send_raw_transaction(
229
245
  signed_tx.raw_transaction)
230
- print('Approval TX Hash:', approve_tx_hash.hex())
246
+ self.log(f"Approval TX Hash: {approve_tx_hash.hex()}")
231
247
 
232
248
  approve_receipt = self.web3.eth.wait_for_transaction_receipt(
233
249
  approve_tx_hash)
234
- print('Approval Receipt:', approve_receipt)
250
+ self.log(f"Approval Receipt: {approve_receipt}")
235
251
 
236
252
  def withdraw(self, amount, receiving_address):
237
253
  account = self._get_account()
@@ -253,15 +269,16 @@ class Ostium:
253
269
  transfer_tx, private_key=self.private_key)
254
270
  transfer_tx_hash = self.web3.eth.send_raw_transaction(
255
271
  signed_tx.raw_transaction)
256
- print('Transfer TX Hash:', transfer_tx_hash.hex())
272
+ self.log(f"Transfer TX Hash: {transfer_tx_hash.hex()}")
257
273
 
258
274
  transfer_receipt = self.web3.eth.wait_for_transaction_receipt(
259
275
  transfer_tx_hash)
260
- print('Transfer Receipt:', transfer_receipt)
276
+ self.log(f"Transfer Receipt: {transfer_receipt}")
261
277
  return transfer_receipt
262
278
 
263
279
  except Exception as e:
264
- reason_string, suggestion = fromErrorCodeToMessage(str(e))
280
+ reason_string, suggestion = fromErrorCodeToMessage(
281
+ str(e), verbose=self.verbose)
265
282
  print(
266
283
  f"An error occurred during the transfer process: {reason_string}")
267
284
  raise Exception(
@@ -269,10 +286,7 @@ class Ostium:
269
286
 
270
287
  def update_limit_order(self, pair_id, index, pvt_key, price=None, tp=None, sl=None):
271
288
  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
289
  account = self.web3.eth.account.from_key(pvt_key)
275
-
276
290
  # Get existing order details (tbd why read from storage)
277
291
  existing_order = self.ostium_trading_storage_contract.functions.getOpenLimitOrder(
278
292
  account.address,
@@ -280,7 +294,7 @@ class Ostium:
280
294
  int(index)
281
295
  ).call()
282
296
 
283
- print('existing_order', existing_order)
297
+ self.log(f"existing_order {existing_order}")
284
298
  # Use existing values if new values are not provided
285
299
  price_value = convert_to_scaled_integer(
286
300
  price) if price is not None else existing_order[1] # openPrice
@@ -289,8 +303,6 @@ class Ostium:
289
303
  sl_value = convert_to_scaled_integer(
290
304
  sl) if sl is not None else existing_order[3] # sl
291
305
 
292
- print('calling updateOpenLimitOrder with price_value',
293
- price_value, 'tp_value', tp_value, 'sl_value', sl_value)
294
306
  trade_tx = self.ostium_trading_contract.functions.updateOpenLimitOrder(
295
307
  int(pair_id),
296
308
  int(index),
@@ -305,15 +317,16 @@ class Ostium:
305
317
  trade_tx, private_key=account.key)
306
318
  trade_tx_hash = self.web3.eth.send_raw_transaction(
307
319
  signed_tx.raw_transaction)
308
- print('Update Limit Order TX Hash:', trade_tx_hash.hex())
320
+ self.log(f"Update Limit Order TX Hash: {trade_tx_hash.hex()}")
309
321
 
310
322
  trade_receipt = self.web3.eth.wait_for_transaction_receipt(
311
323
  trade_tx_hash)
312
- print('Update Limit Order Receipt:', trade_receipt)
324
+ self.log(f"Update Limit Order Receipt: {trade_receipt}")
313
325
  return trade_receipt
314
326
 
315
327
  except Exception as e:
316
- reason_string, suggestion = fromErrorCodeToMessage(str(e))
328
+ reason_string, suggestion = fromErrorCodeToMessage(
329
+ str(e), verbose=self.verbose)
317
330
  print(
318
331
  f"An error occurred during the update limit order process: {reason_string}")
319
332
  raise Exception(
@@ -0,0 +1,47 @@
1
+ import aiohttp
2
+ from typing import Tuple
3
+
4
+
5
+ class Price:
6
+ def __init__(self, verbose=False):
7
+ self.verbose = verbose
8
+ self.base_url = "https://listener.ostium.io"
9
+
10
+ def log(self, message):
11
+ if self.verbose:
12
+ print(message)
13
+
14
+ # Returns a list of price data, e.g: [{'feed_id': '0x00039d9e45394f473ab1f050a1b963e6b05351e52d71e507509ada0c95ed75b8', 'bid': 97241.43864211132, 'mid': 97243.36503172085, 'ask': 97245.2739217016, 'isMarketOpen': True, 'from': 'BTC', 'to': 'USD', 'timestampSeconds': 1740043714}, ...]
15
+ async def get_latest_prices(self):
16
+ """
17
+ Fetches the latest prices from the Ostium price listener service.
18
+ Returns a dictionary of price data.
19
+ """
20
+ async with aiohttp.ClientSession() as session:
21
+ async with session.get(f"{self.base_url}/PricePublish/latest-prices") as response:
22
+ if response.status == 200:
23
+ return await response.json()
24
+ else:
25
+ raise Exception(
26
+ f"Failed to fetch prices: {response.status}")
27
+
28
+ # Returns a json, e.g: {'feed_id': '0x00039d9e45394f473ab1f050a1b963e6b05351e52d71e507509ada0c95ed75b8', 'bid': 97241.43864211132, 'mid': 97243.36503172085, 'ask': 97245.2739217016, 'isMarketOpen': True, 'from': 'BTC', 'to': 'USD', 'timestampSeconds': 1740043714}
29
+ async def get_latest_price_json(self, from_asset: str, to_asset: str) -> Tuple[float, bool]:
30
+ prices = await self.get_latest_prices()
31
+ for price_data in prices:
32
+ if (price_data.get('from') == from_asset and
33
+ price_data.get('to') == to_asset):
34
+ self.log(f"get_latest_price_json: {price_data}")
35
+ return price_data
36
+ raise ValueError(f"No price found for pair: {from_asset}/{to_asset}")
37
+
38
+ # Returns a mid price and isMarketOpen tuple, e.g: (97243.36503172085, True)
39
+ async def get_price(self, from_currency, to_currency):
40
+ self.log(f"Getting price for {from_currency}/{to_currency}")
41
+ prices = await self.get_latest_prices()
42
+ for price_data in prices:
43
+ if (price_data.get('from') == from_currency and
44
+ price_data.get('to') == to_currency):
45
+ return float(price_data.get('mid', 0)), price_data.get('isMarketOpen', False)
46
+ raise ValueError(
47
+ f"No price found for pair: {from_currency}/{to_currency}")
@@ -1,6 +1,8 @@
1
1
  from dotenv import load_dotenv
2
2
  import os
3
3
  from decimal import Decimal
4
+
5
+ from .formulae_wrapper import get_trade_metrics
4
6
  from .constants import PRECISION_2, PRECISION_6, PRECISION_12, PRECISION_18, PRECISION_9
5
7
 
6
8
  from ostium_python_sdk.faucet import Faucet
@@ -14,7 +16,8 @@ from .subgraph import SubgraphClient
14
16
 
15
17
 
16
18
  class OstiumSDK:
17
- def __init__(self, network: Union[str, NetworkConfig], private_key: str = None, rpc_url: str = None):
19
+ def __init__(self, network: Union[str, NetworkConfig], private_key: str = None, rpc_url: str = None, verbose=False):
20
+ self.verbose = verbose
18
21
  load_dotenv()
19
22
  self.private_key = private_key or os.getenv('PRIVATE_KEY')
20
23
  # if not self.private_key:
@@ -52,20 +55,60 @@ class OstiumSDK:
52
55
  self.network_config.contracts["usdc"],
53
56
  self.network_config.contracts["tradingStorage"],
54
57
  self.network_config.contracts["trading"],
55
- private_key=self.private_key
58
+ private_key=self.private_key,
59
+ verbose=self.verbose
56
60
  )
57
61
 
58
62
  # Initialize subgraph client
59
- self.subgraph = SubgraphClient(url=self.network_config.graph_url)
63
+ self.subgraph = SubgraphClient(
64
+ url=self.network_config.graph_url, verbose=self.verbose)
60
65
 
61
- self.balance = Balance(self.w3, self.network_config.contracts["usdc"])
62
- self.price = Price()
66
+ self.balance = Balance(
67
+ self.w3, self.network_config.contracts["usdc"], verbose=self.verbose)
68
+ self.price = Price(verbose=self.verbose)
63
69
 
64
70
  if self.network_config.is_testnet:
65
- self.faucet = Faucet(self.w3, self.private_key)
71
+ self.faucet = Faucet(self.w3, self.private_key,
72
+ verbose=self.verbose)
66
73
  else:
67
74
  self.faucet = None
68
75
 
76
+ def log(self, message):
77
+ if self.verbose:
78
+ print(message)
79
+
80
+ # if SDK instantiated with a private key, this function will return a given open trade metrics,
81
+ # such as: funding fee, roll over fee, Unrealized Pnl, Profit Percent, etc.
82
+ #
83
+ # Will thorw in case SDK instantiated with no private key
84
+ async def get_open_trade_metrics(self, pair_id, trade_index):
85
+ trader_public_address = self.ostium.get_public_address()
86
+ self.log(f"Trader public address: {trader_public_address}")
87
+ open_trades = await self.subgraph.get_open_trades(trader_public_address)
88
+
89
+ trade_details = None
90
+
91
+ if len(open_trades) == 0:
92
+ raise ValueError(f"No Open Trades for {trader_public_address}")
93
+
94
+ for t in open_trades:
95
+ if int(t['pair']['id']) == int(pair_id) and int(t['index']) == int(trade_index):
96
+ trade_details = t
97
+ break
98
+
99
+ if trade_details is None:
100
+ raise ValueError(
101
+ f"Trade not found for {trader_public_address} pair {pair_id} and index {trade_index}")
102
+
103
+ self.log(f"Trade details: {trade_details}")
104
+ # get the price for this trade's asset/feed
105
+ price_data = await self.price.get_latest_price_json(trade_details['pair']['from'], trade_details['pair']['to'])
106
+ self.log(f"Price data: {price_data} (need here bid, mid, ask prices)")
107
+ # get the block number
108
+ block_number = self.ostium.get_block_number()
109
+ self.log(f"Block number: {block_number}")
110
+ return get_trade_metrics(trade_details, price_data, block_number)
111
+
69
112
  async def get_formatted_pairs_details(self) -> list:
70
113
  """
71
114
  Get formatted details for all trading pairs, with proper decimal conversion.