44Offline (no devnet): builds payloads with a fixed key + a hand-seeded
55symbol→marketId map, and asserts on the emitted wire shape — numeric fields as
66plain decimal strings (never scientific notation), the decoupled
7- ``deadline`` / ``expiresAfter`` fields, and the ``postOnly`` flag and its entry
8- guards .
7+ ``deadline`` / ``expiresAfter`` fields, and the order/cancel entry-rule guards
8+ (``reduceOnly``, ``postOnly``, GTT, and the cancel-identifier rules) .
99
1010Regression: the sell-trigger sentinel limit price is ``Decimal("0.000000001")``,
1111and ``str(Decimal("0.000000001"))`` is ``"1E-9"``. The server's ethers
2323from sdk .open_api .models .order_type import OrderType
2424from sdk .open_api .models .time_in_force import TimeInForce
2525from sdk .reya_rest_api import ReyaTradingClient
26+ from sdk .reya_rest_api .client import _SPOT_MARKET_ID_OFFSET
2627from sdk .reya_rest_api .config import TradingConfig
2728from sdk .reya_rest_api .models .orders import LimitOrderParameters , TriggerOrderParameters
2829
2930PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
3031SIGNER_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
3132CHAIN_ID = 89346162
3233PERP_SYMBOL = "ETHRUSDPERP"
34+ SPOT_SYMBOL = "WETHRUSD" # market_id >= _SPOT_MARKET_ID_OFFSET => spot namespace
3335
3436
3537@pytest .fixture
@@ -48,7 +50,7 @@ def client() -> ReyaTradingClient:
4850 account_id = 12345 ,
4951 )
5052 c = ReyaTradingClient (config )
51- c ._symbol_to_market_id = {PERP_SYMBOL : 1 } # perp core id, unified == raw
53+ c ._symbol_to_market_id = {PERP_SYMBOL : 1 , SPOT_SYMBOL : _SPOT_MARKET_ID_OFFSET + 1 }
5254 c ._symbol_to_tick_size = {PERP_SYMBOL : "0.001" } # tick size drives the sell-trigger sentinel
5355 c ._initialized = True
5456 return c
@@ -186,3 +188,54 @@ def test_gtt_rejected_pending_offchain(client: ReyaTradingClient) -> None:
186188 time_in_force = TimeInForce .GTT ,
187189 )
188190 )
191+
192+
193+ def test_reduce_only_on_spot_rejected (client : ReyaTradingClient ) -> None :
194+ """reduce_only is perp-IOC-only; on a spot order it must be rejected at entry
195+ (the server forbids it on spot), not silently dropped."""
196+ with pytest .raises (ValueError , match = "reduce_only is only supported on perp IOC" ):
197+ client .build_create_limit_order_payload (
198+ LimitOrderParameters (
199+ symbol = SPOT_SYMBOL ,
200+ is_buy = True ,
201+ limit_px = "3000" ,
202+ qty = "0.01" ,
203+ time_in_force = TimeInForce .IOC ,
204+ reduce_only = True ,
205+ )
206+ )
207+
208+
209+ def test_reduce_only_on_trigger_rejected (client : ReyaTradingClient ) -> None :
210+ """reduce_only / close-on-trigger TP/SL isn't supported yet — an explicit
211+ reduce_only on a trigger order is rejected rather than signed + sent."""
212+ with pytest .raises (ValueError , match = "reduce_only on TP/SL trigger orders is not supported" ):
213+ client .build_create_trigger_order_payload (
214+ TriggerOrderParameters (
215+ symbol = PERP_SYMBOL ,
216+ is_buy = False ,
217+ qty = "0.01" ,
218+ trigger_px = "1000" ,
219+ trigger_type = OrderType .STOP_LOSS ,
220+ reduce_only = True ,
221+ )
222+ )
223+
224+
225+ def test_cancel_requires_an_identifier (client : ReyaTradingClient ) -> None :
226+ """A cancel must carry at least one of order_id / client_order_id; neither is rejected."""
227+ with pytest .raises (ValueError , match = "Provide either order_id or client_order_id" ):
228+ client .build_cancel_order_payload (symbol = PERP_SYMBOL )
229+
230+
231+ def test_cancel_accepts_both_identifiers (client : ReyaTradingClient ) -> None :
232+ """The client accepts both order_id and client_order_id and carries both on the
233+ wire (the server resolves precedence in favour of order_id) rather than rejecting
234+ the combination."""
235+ payload = client .build_cancel_order_payload (
236+ symbol = PERP_SYMBOL ,
237+ order_id = "123" ,
238+ client_order_id = 456 ,
239+ )
240+ assert payload ["orderId" ] == "123"
241+ assert payload ["clientOrderId" ] == 456
0 commit comments