opinion-clob-sdk 0.1.9__py3-none-any.whl → 0.1.10__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.

Potentially problematic release.


This version of opinion-clob-sdk might be problematic. Click here for more details.

Files changed (65) hide show
  1. opinion_clob_sdk/__init__.py +1 -1
  2. opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  3. opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  4. opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  5. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  6. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  7. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  8. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  9. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  10. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  11. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  12. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  13. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  14. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  15. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  16. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  17. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
  18. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +52 -28
  19. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +26 -0
  20. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/__init__.py +0 -0
  21. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contract_caller.py +390 -0
  22. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/__init__.py +0 -0
  23. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/conditional_tokens.py +707 -0
  24. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/erc20.py +111 -0
  25. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/exception.py +11 -0
  26. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/__init__.py +0 -0
  27. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/__init__.py +0 -0
  28. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/base_builder.py +41 -0
  29. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/exception.py +2 -0
  30. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder.py +90 -0
  31. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder_test.py +40 -0
  32. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/constants.py +2 -0
  33. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/__init__.py +0 -0
  34. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order.py +254 -0
  35. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order_type.py +9 -0
  36. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/sides.py +8 -0
  37. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/signatures.py +8 -0
  38. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/signer.py +20 -0
  39. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +139 -0
  40. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/__init__.py +0 -0
  41. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/constants.py +19 -0
  42. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/eip712/__init__.py +176 -0
  43. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/enums.py +6 -0
  44. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/exceptions.py +94 -0
  45. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/multisend.py +347 -0
  46. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe.py +141 -0
  47. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/__init__.py +0 -0
  48. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/compatibility_fallback_handler_v1_3_0.py +327 -0
  49. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/multisend_v1_3_0.py +22 -0
  50. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/safe_v1_3_0.py +1035 -0
  51. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/utils.py +26 -0
  52. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_signature.py +364 -0
  53. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_test.py +37 -0
  54. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_tx.py +437 -0
  55. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/signatures.py +63 -0
  56. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/typing.py +17 -0
  57. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/utils.py +218 -0
  58. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/config.py +4 -0
  59. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/model.py +19 -0
  60. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/sdk.py +957 -0
  61. opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/verify_api_calls.py +135 -0
  62. {opinion_clob_sdk-0.1.9.dist-info → opinion_clob_sdk-0.1.10.dist-info}/METADATA +1 -1
  63. {opinion_clob_sdk-0.1.9.dist-info → opinion_clob_sdk-0.1.10.dist-info}/RECORD +65 -22
  64. {opinion_clob_sdk-0.1.9.dist-info → opinion_clob_sdk-0.1.10.dist-info}/WHEEL +0 -0
  65. {opinion_clob_sdk-0.1.9.dist-info → opinion_clob_sdk-0.1.10.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ from opinion_clob_sdk.chain.exception import (
12
12
  InsufficientGasBalance
13
13
  )
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = [
17
17
  "Client",
18
18
  "TopicStatus",
@@ -42,19 +42,22 @@ def prepend_zx(in_str: str) -> str:
42
42
  def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, decimals: int) -> tuple[int, int]:
43
43
  """
44
44
  Calculate the maker and taker amounts based on the price and side.
45
-
45
+
46
+ Uses precise Decimal arithmetic to ensure precision within 6 significant digits
47
+ (matching the matching engine's precision limit).
48
+
46
49
  Args:
47
50
  price: The price of the order (between 0.001 and 0.999)
48
51
  maker_amount: The maker amount in base units
49
52
  side: The order side (BUY or SELL)
50
- decimals: The number of decimal places for the currency
51
-
53
+ decimals: The number of decimal places for the currency (unused, kept for compatibility)
54
+
52
55
  Returns:
53
56
  tuple[int, int]: A tuple containing (recalculated_maker_amount, taker_amount)
54
- For BUY: price = taker/maker
55
- For SELL: price = taker/maker
57
+ For BUY: price = maker/taker, so taker = maker/price
58
+ For SELL: price = taker/maker, so taker = maker*price
56
59
  """
57
-
60
+
58
61
  # Validate price using Decimal for exact comparison
59
62
  try:
60
63
  price_decimal = Decimal(str(price))
@@ -68,48 +71,69 @@ def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, de
68
71
  if not (min_price <= price_decimal <= max_price):
69
72
  raise ValueError(f"Price must be between {min_price} and {max_price} (inclusive), got {price}")
70
73
 
71
- # Ensure price doesn't have excessive precision (max 3 decimal places for prediction markets)
72
- if price_decimal.as_tuple().exponent < -3:
73
- raise ValueError(f"Price precision cannot exceed 3 decimal places, got {price}")
74
+ # Ensure price doesn't have excessive precision (max 6 decimal places to match engine precision)
75
+ if price_decimal.as_tuple().exponent < -6:
76
+ raise ValueError(f"Price precision cannot exceed 6 decimal places, got {price}")
74
77
 
75
78
  maker_decimal = Decimal(str(maker_amount))
76
- decimals_decimal = Decimal(str(decimals))
77
79
 
78
80
  if side == OrderSide.BUY:
79
81
  # For BUY: price = maker/taker
80
- # Calculate taker amount: taker = maker / price
81
- # We need to ensure that recalculated_maker = taker * price <= original maker_amount
82
- # To avoid precision loss, we iteratively reduce taker until maker fits
82
+ # Goal: Find maximum taker such that floor(taker * price) <= maker
83
+ # Then use recalculated_maker = floor(taker * price)
84
+ #
85
+ # This ensures precision: given (recalculated_maker, taker, price),
86
+ # you can always verify: recalculated_maker = floor(taker * price)
87
+
88
+ # Calculate exact taker = maker / price
83
89
  exact_taker = maker_decimal / price_decimal
84
- taker_amount = int(math.floor(exact_taker))
85
90
 
86
- # Recalculate maker from taker and ensure it doesn't exceed original maker_amount
87
- exact_maker = Decimal(str(taker_amount)) * price_decimal
88
- recalculated_maker_amount = int(math.floor(exact_maker))
91
+ # Start with floor(exact_taker)
92
+ taker_amount = int(exact_taker)
93
+
94
+ # Calculate corresponding maker = floor(taker * price)
95
+ recalculated_maker_decimal = Decimal(str(taker_amount)) * price_decimal
96
+ recalculated_maker_amount = int(recalculated_maker_decimal)
97
+
98
+ # Try to increment taker to use more of the available maker amount
99
+ # but ensure we don't exceed the original maker_amount
100
+ while taker_amount > 0:
101
+ test_taker = taker_amount + 1
102
+ test_maker_decimal = Decimal(str(test_taker)) * price_decimal
103
+ test_maker = int(test_maker_decimal)
104
+
105
+ if test_maker <= maker_amount:
106
+ # We can use this higher taker value
107
+ taker_amount = test_taker
108
+ recalculated_maker_amount = test_maker
109
+ else:
110
+ # Can't increase further without exceeding maker_amount
111
+ break
89
112
 
90
- # If recalculated maker exceeds original, reduce taker by 1 and recalculate
91
- while recalculated_maker_amount > maker_amount and taker_amount > 0:
92
- taker_amount -= 1
93
- exact_maker = Decimal(str(taker_amount)) * price_decimal
94
- recalculated_maker_amount = int(math.floor(exact_maker))
95
113
  else: # SELL
96
114
  # For SELL: price = taker/maker
97
- # Calculate taker amount: taker = maker * price
115
+ # We want: taker = floor(maker * price)
116
+ # Maker stays the same
117
+
118
+ # Calculate exact taker = maker * price
98
119
  exact_taker = maker_decimal * price_decimal
99
- taker_amount = int(math.floor(exact_taker))
120
+
121
+ # Round down to get integer taker
122
+ taker_amount = int(exact_taker)
100
123
 
101
124
  # For SELL, maker amount doesn't change
102
125
  recalculated_maker_amount = int(maker_amount)
103
-
126
+
104
127
  # Ensure amounts are at least 1
105
128
  taker_amount = int(max(1, taker_amount))
106
129
  recalculated_maker_amount = int(max(1, recalculated_maker_amount))
107
130
 
108
131
  logging.debug(f"Order calculation: taker_amount={taker_amount}, recalculated_maker_amount={recalculated_maker_amount}")
109
132
 
110
- calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
111
-
133
+ # Validate the calculated price is within bounds
134
+ calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
135
+
112
136
  if calculated_price > 0.999 or calculated_price < 0.001:
113
137
  raise ValueError("invalid taker_amount and recalculated_maker_amount")
114
-
138
+
115
139
  return recalculated_maker_amount, taker_amount
@@ -12,7 +12,7 @@ from opinion_clob_sdk.chain.exception import (
12
12
  InsufficientGasBalance
13
13
  )
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = [
17
17
  "Client",
18
18
  "TopicStatus",
@@ -42,19 +42,22 @@ def prepend_zx(in_str: str) -> str:
42
42
  def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, decimals: int) -> tuple[int, int]:
43
43
  """
44
44
  Calculate the maker and taker amounts based on the price and side.
45
-
45
+
46
+ Uses precise Decimal arithmetic to ensure precision within 6 significant digits
47
+ (matching the matching engine's precision limit).
48
+
46
49
  Args:
47
50
  price: The price of the order (between 0.001 and 0.999)
48
51
  maker_amount: The maker amount in base units
49
52
  side: The order side (BUY or SELL)
50
- decimals: The number of decimal places for the currency
51
-
53
+ decimals: The number of decimal places for the currency (unused, kept for compatibility)
54
+
52
55
  Returns:
53
56
  tuple[int, int]: A tuple containing (recalculated_maker_amount, taker_amount)
54
- For BUY: price = taker/maker
55
- For SELL: price = taker/maker
57
+ For BUY: price = maker/taker, so taker = maker/price
58
+ For SELL: price = taker/maker, so taker = maker*price
56
59
  """
57
-
60
+
58
61
  # Validate price using Decimal for exact comparison
59
62
  try:
60
63
  price_decimal = Decimal(str(price))
@@ -68,48 +71,69 @@ def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, de
68
71
  if not (min_price <= price_decimal <= max_price):
69
72
  raise ValueError(f"Price must be between {min_price} and {max_price} (inclusive), got {price}")
70
73
 
71
- # Ensure price doesn't have excessive precision (max 3 decimal places for prediction markets)
72
- if price_decimal.as_tuple().exponent < -3:
73
- raise ValueError(f"Price precision cannot exceed 3 decimal places, got {price}")
74
+ # Ensure price doesn't have excessive precision (max 6 decimal places to match engine precision)
75
+ if price_decimal.as_tuple().exponent < -6:
76
+ raise ValueError(f"Price precision cannot exceed 6 decimal places, got {price}")
74
77
 
75
78
  maker_decimal = Decimal(str(maker_amount))
76
- decimals_decimal = Decimal(str(decimals))
77
79
 
78
80
  if side == OrderSide.BUY:
79
81
  # For BUY: price = maker/taker
80
- # Calculate taker amount: taker = maker / price
81
- # We need to ensure that recalculated_maker = taker * price <= original maker_amount
82
- # To avoid precision loss, we iteratively reduce taker until maker fits
82
+ # Goal: Find maximum taker such that floor(taker * price) <= maker
83
+ # Then use recalculated_maker = floor(taker * price)
84
+ #
85
+ # This ensures precision: given (recalculated_maker, taker, price),
86
+ # you can always verify: recalculated_maker = floor(taker * price)
87
+
88
+ # Calculate exact taker = maker / price
83
89
  exact_taker = maker_decimal / price_decimal
84
- taker_amount = int(math.floor(exact_taker))
85
90
 
86
- # Recalculate maker from taker and ensure it doesn't exceed original maker_amount
87
- exact_maker = Decimal(str(taker_amount)) * price_decimal
88
- recalculated_maker_amount = int(math.floor(exact_maker))
91
+ # Start with floor(exact_taker)
92
+ taker_amount = int(exact_taker)
93
+
94
+ # Calculate corresponding maker = floor(taker * price)
95
+ recalculated_maker_decimal = Decimal(str(taker_amount)) * price_decimal
96
+ recalculated_maker_amount = int(recalculated_maker_decimal)
97
+
98
+ # Try to increment taker to use more of the available maker amount
99
+ # but ensure we don't exceed the original maker_amount
100
+ while taker_amount > 0:
101
+ test_taker = taker_amount + 1
102
+ test_maker_decimal = Decimal(str(test_taker)) * price_decimal
103
+ test_maker = int(test_maker_decimal)
104
+
105
+ if test_maker <= maker_amount:
106
+ # We can use this higher taker value
107
+ taker_amount = test_taker
108
+ recalculated_maker_amount = test_maker
109
+ else:
110
+ # Can't increase further without exceeding maker_amount
111
+ break
89
112
 
90
- # If recalculated maker exceeds original, reduce taker by 1 and recalculate
91
- while recalculated_maker_amount > maker_amount and taker_amount > 0:
92
- taker_amount -= 1
93
- exact_maker = Decimal(str(taker_amount)) * price_decimal
94
- recalculated_maker_amount = int(math.floor(exact_maker))
95
113
  else: # SELL
96
114
  # For SELL: price = taker/maker
97
- # Calculate taker amount: taker = maker * price
115
+ # We want: taker = floor(maker * price)
116
+ # Maker stays the same
117
+
118
+ # Calculate exact taker = maker * price
98
119
  exact_taker = maker_decimal * price_decimal
99
- taker_amount = int(math.floor(exact_taker))
120
+
121
+ # Round down to get integer taker
122
+ taker_amount = int(exact_taker)
100
123
 
101
124
  # For SELL, maker amount doesn't change
102
125
  recalculated_maker_amount = int(maker_amount)
103
-
126
+
104
127
  # Ensure amounts are at least 1
105
128
  taker_amount = int(max(1, taker_amount))
106
129
  recalculated_maker_amount = int(max(1, recalculated_maker_amount))
107
130
 
108
131
  logging.debug(f"Order calculation: taker_amount={taker_amount}, recalculated_maker_amount={recalculated_maker_amount}")
109
132
 
110
- calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
111
-
133
+ # Validate the calculated price is within bounds
134
+ calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
135
+
112
136
  if calculated_price > 0.999 or calculated_price < 0.001:
113
137
  raise ValueError("invalid taker_amount and recalculated_maker_amount")
114
-
138
+
115
139
  return recalculated_maker_amount, taker_amount
@@ -12,7 +12,7 @@ from opinion_clob_sdk.chain.exception import (
12
12
  InsufficientGasBalance
13
13
  )
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = [
17
17
  "Client",
18
18
  "TopicStatus",
@@ -42,19 +42,22 @@ def prepend_zx(in_str: str) -> str:
42
42
  def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, decimals: int) -> tuple[int, int]:
43
43
  """
44
44
  Calculate the maker and taker amounts based on the price and side.
45
-
45
+
46
+ Uses precise Decimal arithmetic to ensure precision within 6 significant digits
47
+ (matching the matching engine's precision limit).
48
+
46
49
  Args:
47
50
  price: The price of the order (between 0.001 and 0.999)
48
51
  maker_amount: The maker amount in base units
49
52
  side: The order side (BUY or SELL)
50
- decimals: The number of decimal places for the currency
51
-
53
+ decimals: The number of decimal places for the currency (unused, kept for compatibility)
54
+
52
55
  Returns:
53
56
  tuple[int, int]: A tuple containing (recalculated_maker_amount, taker_amount)
54
- For BUY: price = taker/maker
55
- For SELL: price = taker/maker
57
+ For BUY: price = maker/taker, so taker = maker/price
58
+ For SELL: price = taker/maker, so taker = maker*price
56
59
  """
57
-
60
+
58
61
  # Validate price using Decimal for exact comparison
59
62
  try:
60
63
  price_decimal = Decimal(str(price))
@@ -68,48 +71,69 @@ def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, de
68
71
  if not (min_price <= price_decimal <= max_price):
69
72
  raise ValueError(f"Price must be between {min_price} and {max_price} (inclusive), got {price}")
70
73
 
71
- # Ensure price doesn't have excessive precision (max 3 decimal places for prediction markets)
72
- if price_decimal.as_tuple().exponent < -3:
73
- raise ValueError(f"Price precision cannot exceed 3 decimal places, got {price}")
74
+ # Ensure price doesn't have excessive precision (max 6 decimal places to match engine precision)
75
+ if price_decimal.as_tuple().exponent < -6:
76
+ raise ValueError(f"Price precision cannot exceed 6 decimal places, got {price}")
74
77
 
75
78
  maker_decimal = Decimal(str(maker_amount))
76
- decimals_decimal = Decimal(str(decimals))
77
79
 
78
80
  if side == OrderSide.BUY:
79
81
  # For BUY: price = maker/taker
80
- # Calculate taker amount: taker = maker / price
81
- # We need to ensure that recalculated_maker = taker * price <= original maker_amount
82
- # To avoid precision loss, we iteratively reduce taker until maker fits
82
+ # Goal: Find maximum taker such that floor(taker * price) <= maker
83
+ # Then use recalculated_maker = floor(taker * price)
84
+ #
85
+ # This ensures precision: given (recalculated_maker, taker, price),
86
+ # you can always verify: recalculated_maker = floor(taker * price)
87
+
88
+ # Calculate exact taker = maker / price
83
89
  exact_taker = maker_decimal / price_decimal
84
- taker_amount = int(math.floor(exact_taker))
85
90
 
86
- # Recalculate maker from taker and ensure it doesn't exceed original maker_amount
87
- exact_maker = Decimal(str(taker_amount)) * price_decimal
88
- recalculated_maker_amount = int(math.floor(exact_maker))
91
+ # Start with floor(exact_taker)
92
+ taker_amount = int(exact_taker)
93
+
94
+ # Calculate corresponding maker = floor(taker * price)
95
+ recalculated_maker_decimal = Decimal(str(taker_amount)) * price_decimal
96
+ recalculated_maker_amount = int(recalculated_maker_decimal)
97
+
98
+ # Try to increment taker to use more of the available maker amount
99
+ # but ensure we don't exceed the original maker_amount
100
+ while taker_amount > 0:
101
+ test_taker = taker_amount + 1
102
+ test_maker_decimal = Decimal(str(test_taker)) * price_decimal
103
+ test_maker = int(test_maker_decimal)
104
+
105
+ if test_maker <= maker_amount:
106
+ # We can use this higher taker value
107
+ taker_amount = test_taker
108
+ recalculated_maker_amount = test_maker
109
+ else:
110
+ # Can't increase further without exceeding maker_amount
111
+ break
89
112
 
90
- # If recalculated maker exceeds original, reduce taker by 1 and recalculate
91
- while recalculated_maker_amount > maker_amount and taker_amount > 0:
92
- taker_amount -= 1
93
- exact_maker = Decimal(str(taker_amount)) * price_decimal
94
- recalculated_maker_amount = int(math.floor(exact_maker))
95
113
  else: # SELL
96
114
  # For SELL: price = taker/maker
97
- # Calculate taker amount: taker = maker * price
115
+ # We want: taker = floor(maker * price)
116
+ # Maker stays the same
117
+
118
+ # Calculate exact taker = maker * price
98
119
  exact_taker = maker_decimal * price_decimal
99
- taker_amount = int(math.floor(exact_taker))
120
+
121
+ # Round down to get integer taker
122
+ taker_amount = int(exact_taker)
100
123
 
101
124
  # For SELL, maker amount doesn't change
102
125
  recalculated_maker_amount = int(maker_amount)
103
-
126
+
104
127
  # Ensure amounts are at least 1
105
128
  taker_amount = int(max(1, taker_amount))
106
129
  recalculated_maker_amount = int(max(1, recalculated_maker_amount))
107
130
 
108
131
  logging.debug(f"Order calculation: taker_amount={taker_amount}, recalculated_maker_amount={recalculated_maker_amount}")
109
132
 
110
- calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
111
-
133
+ # Validate the calculated price is within bounds
134
+ calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
135
+
112
136
  if calculated_price > 0.999 or calculated_price < 0.001:
113
137
  raise ValueError("invalid taker_amount and recalculated_maker_amount")
114
-
138
+
115
139
  return recalculated_maker_amount, taker_amount
@@ -12,7 +12,7 @@ from opinion_clob_sdk.chain.exception import (
12
12
  InsufficientGasBalance
13
13
  )
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = [
17
17
  "Client",
18
18
  "TopicStatus",
@@ -42,19 +42,22 @@ def prepend_zx(in_str: str) -> str:
42
42
  def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, decimals: int) -> tuple[int, int]:
43
43
  """
44
44
  Calculate the maker and taker amounts based on the price and side.
45
-
45
+
46
+ Uses precise Decimal arithmetic to ensure precision within 6 significant digits
47
+ (matching the matching engine's precision limit).
48
+
46
49
  Args:
47
50
  price: The price of the order (between 0.001 and 0.999)
48
51
  maker_amount: The maker amount in base units
49
52
  side: The order side (BUY or SELL)
50
- decimals: The number of decimal places for the currency
51
-
53
+ decimals: The number of decimal places for the currency (unused, kept for compatibility)
54
+
52
55
  Returns:
53
56
  tuple[int, int]: A tuple containing (recalculated_maker_amount, taker_amount)
54
- For BUY: price = taker/maker
55
- For SELL: price = taker/maker
57
+ For BUY: price = maker/taker, so taker = maker/price
58
+ For SELL: price = taker/maker, so taker = maker*price
56
59
  """
57
-
60
+
58
61
  # Validate price using Decimal for exact comparison
59
62
  try:
60
63
  price_decimal = Decimal(str(price))
@@ -68,48 +71,69 @@ def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, de
68
71
  if not (min_price <= price_decimal <= max_price):
69
72
  raise ValueError(f"Price must be between {min_price} and {max_price} (inclusive), got {price}")
70
73
 
71
- # Ensure price doesn't have excessive precision (max 3 decimal places for prediction markets)
72
- if price_decimal.as_tuple().exponent < -3:
73
- raise ValueError(f"Price precision cannot exceed 3 decimal places, got {price}")
74
+ # Ensure price doesn't have excessive precision (max 6 decimal places to match engine precision)
75
+ if price_decimal.as_tuple().exponent < -6:
76
+ raise ValueError(f"Price precision cannot exceed 6 decimal places, got {price}")
74
77
 
75
78
  maker_decimal = Decimal(str(maker_amount))
76
- decimals_decimal = Decimal(str(decimals))
77
79
 
78
80
  if side == OrderSide.BUY:
79
81
  # For BUY: price = maker/taker
80
- # Calculate taker amount: taker = maker / price
81
- # We need to ensure that recalculated_maker = taker * price <= original maker_amount
82
- # To avoid precision loss, we iteratively reduce taker until maker fits
82
+ # Goal: Find maximum taker such that floor(taker * price) <= maker
83
+ # Then use recalculated_maker = floor(taker * price)
84
+ #
85
+ # This ensures precision: given (recalculated_maker, taker, price),
86
+ # you can always verify: recalculated_maker = floor(taker * price)
87
+
88
+ # Calculate exact taker = maker / price
83
89
  exact_taker = maker_decimal / price_decimal
84
- taker_amount = int(math.floor(exact_taker))
85
90
 
86
- # Recalculate maker from taker and ensure it doesn't exceed original maker_amount
87
- exact_maker = Decimal(str(taker_amount)) * price_decimal
88
- recalculated_maker_amount = int(math.floor(exact_maker))
91
+ # Start with floor(exact_taker)
92
+ taker_amount = int(exact_taker)
93
+
94
+ # Calculate corresponding maker = floor(taker * price)
95
+ recalculated_maker_decimal = Decimal(str(taker_amount)) * price_decimal
96
+ recalculated_maker_amount = int(recalculated_maker_decimal)
97
+
98
+ # Try to increment taker to use more of the available maker amount
99
+ # but ensure we don't exceed the original maker_amount
100
+ while taker_amount > 0:
101
+ test_taker = taker_amount + 1
102
+ test_maker_decimal = Decimal(str(test_taker)) * price_decimal
103
+ test_maker = int(test_maker_decimal)
104
+
105
+ if test_maker <= maker_amount:
106
+ # We can use this higher taker value
107
+ taker_amount = test_taker
108
+ recalculated_maker_amount = test_maker
109
+ else:
110
+ # Can't increase further without exceeding maker_amount
111
+ break
89
112
 
90
- # If recalculated maker exceeds original, reduce taker by 1 and recalculate
91
- while recalculated_maker_amount > maker_amount and taker_amount > 0:
92
- taker_amount -= 1
93
- exact_maker = Decimal(str(taker_amount)) * price_decimal
94
- recalculated_maker_amount = int(math.floor(exact_maker))
95
113
  else: # SELL
96
114
  # For SELL: price = taker/maker
97
- # Calculate taker amount: taker = maker * price
115
+ # We want: taker = floor(maker * price)
116
+ # Maker stays the same
117
+
118
+ # Calculate exact taker = maker * price
98
119
  exact_taker = maker_decimal * price_decimal
99
- taker_amount = int(math.floor(exact_taker))
120
+
121
+ # Round down to get integer taker
122
+ taker_amount = int(exact_taker)
100
123
 
101
124
  # For SELL, maker amount doesn't change
102
125
  recalculated_maker_amount = int(maker_amount)
103
-
126
+
104
127
  # Ensure amounts are at least 1
105
128
  taker_amount = int(max(1, taker_amount))
106
129
  recalculated_maker_amount = int(max(1, recalculated_maker_amount))
107
130
 
108
131
  logging.debug(f"Order calculation: taker_amount={taker_amount}, recalculated_maker_amount={recalculated_maker_amount}")
109
132
 
110
- calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
111
-
133
+ # Validate the calculated price is within bounds
134
+ calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
135
+
112
136
  if calculated_price > 0.999 or calculated_price < 0.001:
113
137
  raise ValueError("invalid taker_amount and recalculated_maker_amount")
114
-
138
+
115
139
  return recalculated_maker_amount, taker_amount
@@ -12,7 +12,7 @@ from opinion_clob_sdk.chain.exception import (
12
12
  InsufficientGasBalance
13
13
  )
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = [
17
17
  "Client",
18
18
  "TopicStatus",
@@ -42,19 +42,22 @@ def prepend_zx(in_str: str) -> str:
42
42
  def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, decimals: int) -> tuple[int, int]:
43
43
  """
44
44
  Calculate the maker and taker amounts based on the price and side.
45
-
45
+
46
+ Uses precise Decimal arithmetic to ensure precision within 6 significant digits
47
+ (matching the matching engine's precision limit).
48
+
46
49
  Args:
47
50
  price: The price of the order (between 0.001 and 0.999)
48
51
  maker_amount: The maker amount in base units
49
52
  side: The order side (BUY or SELL)
50
- decimals: The number of decimal places for the currency
51
-
53
+ decimals: The number of decimal places for the currency (unused, kept for compatibility)
54
+
52
55
  Returns:
53
56
  tuple[int, int]: A tuple containing (recalculated_maker_amount, taker_amount)
54
- For BUY: price = taker/maker
55
- For SELL: price = taker/maker
57
+ For BUY: price = maker/taker, so taker = maker/price
58
+ For SELL: price = taker/maker, so taker = maker*price
56
59
  """
57
-
60
+
58
61
  # Validate price using Decimal for exact comparison
59
62
  try:
60
63
  price_decimal = Decimal(str(price))
@@ -68,48 +71,69 @@ def calculate_order_amounts(price: float, maker_amount: int, side: OrderSide, de
68
71
  if not (min_price <= price_decimal <= max_price):
69
72
  raise ValueError(f"Price must be between {min_price} and {max_price} (inclusive), got {price}")
70
73
 
71
- # Ensure price doesn't have excessive precision (max 3 decimal places for prediction markets)
72
- if price_decimal.as_tuple().exponent < -3:
73
- raise ValueError(f"Price precision cannot exceed 3 decimal places, got {price}")
74
+ # Ensure price doesn't have excessive precision (max 6 decimal places to match engine precision)
75
+ if price_decimal.as_tuple().exponent < -6:
76
+ raise ValueError(f"Price precision cannot exceed 6 decimal places, got {price}")
74
77
 
75
78
  maker_decimal = Decimal(str(maker_amount))
76
- decimals_decimal = Decimal(str(decimals))
77
79
 
78
80
  if side == OrderSide.BUY:
79
81
  # For BUY: price = maker/taker
80
- # Calculate taker amount: taker = maker / price
81
- # We need to ensure that recalculated_maker = taker * price <= original maker_amount
82
- # To avoid precision loss, we iteratively reduce taker until maker fits
82
+ # Goal: Find maximum taker such that floor(taker * price) <= maker
83
+ # Then use recalculated_maker = floor(taker * price)
84
+ #
85
+ # This ensures precision: given (recalculated_maker, taker, price),
86
+ # you can always verify: recalculated_maker = floor(taker * price)
87
+
88
+ # Calculate exact taker = maker / price
83
89
  exact_taker = maker_decimal / price_decimal
84
- taker_amount = int(math.floor(exact_taker))
85
90
 
86
- # Recalculate maker from taker and ensure it doesn't exceed original maker_amount
87
- exact_maker = Decimal(str(taker_amount)) * price_decimal
88
- recalculated_maker_amount = int(math.floor(exact_maker))
91
+ # Start with floor(exact_taker)
92
+ taker_amount = int(exact_taker)
93
+
94
+ # Calculate corresponding maker = floor(taker * price)
95
+ recalculated_maker_decimal = Decimal(str(taker_amount)) * price_decimal
96
+ recalculated_maker_amount = int(recalculated_maker_decimal)
97
+
98
+ # Try to increment taker to use more of the available maker amount
99
+ # but ensure we don't exceed the original maker_amount
100
+ while taker_amount > 0:
101
+ test_taker = taker_amount + 1
102
+ test_maker_decimal = Decimal(str(test_taker)) * price_decimal
103
+ test_maker = int(test_maker_decimal)
104
+
105
+ if test_maker <= maker_amount:
106
+ # We can use this higher taker value
107
+ taker_amount = test_taker
108
+ recalculated_maker_amount = test_maker
109
+ else:
110
+ # Can't increase further without exceeding maker_amount
111
+ break
89
112
 
90
- # If recalculated maker exceeds original, reduce taker by 1 and recalculate
91
- while recalculated_maker_amount > maker_amount and taker_amount > 0:
92
- taker_amount -= 1
93
- exact_maker = Decimal(str(taker_amount)) * price_decimal
94
- recalculated_maker_amount = int(math.floor(exact_maker))
95
113
  else: # SELL
96
114
  # For SELL: price = taker/maker
97
- # Calculate taker amount: taker = maker * price
115
+ # We want: taker = floor(maker * price)
116
+ # Maker stays the same
117
+
118
+ # Calculate exact taker = maker * price
98
119
  exact_taker = maker_decimal * price_decimal
99
- taker_amount = int(math.floor(exact_taker))
120
+
121
+ # Round down to get integer taker
122
+ taker_amount = int(exact_taker)
100
123
 
101
124
  # For SELL, maker amount doesn't change
102
125
  recalculated_maker_amount = int(maker_amount)
103
-
126
+
104
127
  # Ensure amounts are at least 1
105
128
  taker_amount = int(max(1, taker_amount))
106
129
  recalculated_maker_amount = int(max(1, recalculated_maker_amount))
107
130
 
108
131
  logging.debug(f"Order calculation: taker_amount={taker_amount}, recalculated_maker_amount={recalculated_maker_amount}")
109
132
 
110
- calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
111
-
133
+ # Validate the calculated price is within bounds
134
+ calculated_price = recalculated_maker_amount / taker_amount if side == OrderSide.BUY else taker_amount / recalculated_maker_amount
135
+
112
136
  if calculated_price > 0.999 or calculated_price < 0.001:
113
137
  raise ValueError("invalid taker_amount and recalculated_maker_amount")
114
-
138
+
115
139
  return recalculated_maker_amount, taker_amount