1313#  limitations under the License. 
1414# ------------------------------------------------------------------------------------------------- 
1515
16+ import  json 
1617import  pkgutil 
1718
18- import  msgspec 
1919import  pytest 
2020
2121from  nautilus_trader .adapters .binance .common .enums  import  BinanceAccountType 
2222from  nautilus_trader .adapters .binance .futures .providers  import  BinanceFuturesInstrumentProvider 
23+ from  nautilus_trader .adapters .binance .futures .schemas .account  import  BinanceFuturesAccountInfo 
2324from  nautilus_trader .adapters .binance .http .client  import  BinanceHttpClient 
2425from  nautilus_trader .adapters .binance .spot .providers  import  BinanceSpotInstrumentProvider 
2526from  nautilus_trader .common .component  import  LiveClock 
2829from  nautilus_trader .model .identifiers  import  Venue 
2930
3031
31- @pytest .mark .skip (reason = "WIP" ) 
3232class  TestBinanceInstrumentProvider :
3333    def  setup (self ):
3434        # Fixture Setup 
3535        self .clock  =  LiveClock ()
3636
37+     @pytest .mark .skip (reason = "WIP - test data format mismatch" ) 
3738    @pytest .mark .asyncio () 
3839    async  def  test_load_all_async_for_spot_markets (
3940        self ,
@@ -60,8 +61,9 @@ async def mock_send_request(
6061            http_method : str ,  # (needed for mock) 
6162            url_path : str ,  # (needed for mock) 
6263            payload : dict [str , str ],  # (needed for mock) 
64+             ratelimiter_keys : list [str ] |  None  =  None ,  # (needed for mock) 
6365        ) ->  bytes :
64-             return  msgspec . json . decode ( responses .pop () )
66+             return  responses .pop ()
6567
6668        # Apply mock coroutine to client 
6769        monkeypatch .setattr (
@@ -72,7 +74,6 @@ async def mock_send_request(
7274
7375        self .provider  =  BinanceSpotInstrumentProvider (
7476            client = binance_http_client ,
75-             logger = live_logger ,
7677            clock = self .clock ,
7778            account_type = BinanceAccountType .SPOT ,
7879        )
@@ -97,26 +98,34 @@ async def test_load_all_async_for_futures_markets(
9798        monkeypatch ,
9899    ):
99100        # Arrange: prepare data for monkey patch 
100-         # response1 = pkgutil.get_data( 
101-         #     package="tests.integration_tests.adapters.binance.resources.http_responses", 
102-         #     resource="http_wallet_trading_fee.json", 
103-         # ) 
104- 
105-         response2  =  pkgutil .get_data (
101+         exchange_info_response  =  pkgutil .get_data (
106102            package = "tests.integration_tests.adapters.binance.resources.http_responses" ,
107103            resource = "http_futures_market_exchange_info.json" ,
108104        )
109105
110-         responses  =  [response2 ]
106+         account_info  =  BinanceFuturesAccountInfo (
107+             feeTier = 0 ,
108+             canTrade = True ,
109+             canDeposit = True ,
110+             canWithdraw = True ,
111+             updateTime = 1234567890000 ,
112+             assets = [],
113+         )
114+ 
115+         responses  =  [exchange_info_response ]
111116
112117        # Mock coroutine for patch 
113118        async  def  mock_send_request (
114119            self ,  # (needed for mock) 
115120            http_method : str ,  # (needed for mock) 
116121            url_path : str ,  # (needed for mock) 
117122            payload : dict [str , str ],  # (needed for mock) 
123+             ratelimiter_keys : list [str ] |  None  =  None ,  # (needed for mock) 
118124        ) ->  bytes :
119-             return  msgspec .json .decode (responses .pop ())
125+             return  responses .pop ()
126+ 
127+         async  def  mock_query_account_info (recv_window : str ):
128+             return  account_info 
120129
121130        # Apply mock coroutine to client 
122131        monkeypatch .setattr (
@@ -127,11 +136,16 @@ async def mock_send_request(
127136
128137        self .provider  =  BinanceFuturesInstrumentProvider (
129138            client = binance_http_client ,
130-             logger = live_logger ,
131139            clock = self .clock ,
132140            account_type = BinanceAccountType .USDT_FUTURES ,
133141        )
134142
143+         monkeypatch .setattr (
144+             self .provider ._http_account ,
145+             "query_futures_account_info" ,
146+             mock_query_account_info ,
147+         )
148+ 
135149        # Act 
136150        await  self .provider .load_all_async ()
137151
@@ -150,3 +164,160 @@ async def mock_send_request(
150164        assert  "BTC"  in  self .provider .currencies ()
151165        assert  "ETH"  in  self .provider .currencies ()
152166        assert  "USDT"  in  self .provider .currencies ()
167+ 
168+     @pytest .mark .asyncio () 
169+     async  def  test_futures_instrument_info_dict_is_json_serializable (
170+         self ,
171+         binance_http_client ,
172+         live_logger ,
173+         monkeypatch ,
174+     ):
175+         """ 
176+         Test that the instrument info dict contains only JSON-serializable primitives. 
177+ 
178+         This regression test ensures that enums (like BinanceFuturesContractStatus, 
179+         BinanceOrderType, BinanceTimeInForce) are converted to their string values 
180+         in the info dict, preventing JSON serialization errors in PyO3 interop. 
181+ 
182+         See: https://github.com/nautechsystems/nautilus_trader/issues/3128 
183+ 
184+         """ 
185+         # Arrange 
186+         exchange_info_response  =  pkgutil .get_data (
187+             package = "tests.integration_tests.adapters.binance.resources.http_responses" ,
188+             resource = "http_futures_market_exchange_info.json" ,
189+         )
190+ 
191+         account_info  =  BinanceFuturesAccountInfo (
192+             feeTier = 0 ,
193+             canTrade = True ,
194+             canDeposit = True ,
195+             canWithdraw = True ,
196+             updateTime = 1234567890000 ,
197+             assets = [],
198+         )
199+ 
200+         responses  =  [exchange_info_response ]
201+ 
202+         async  def  mock_send_request (
203+             self ,
204+             http_method : str ,
205+             url_path : str ,
206+             payload : dict [str , str ],
207+             ratelimiter_keys : list [str ] |  None  =  None ,
208+         ) ->  bytes :
209+             return  responses .pop ()
210+ 
211+         async  def  mock_query_account_info (recv_window : str ):
212+             return  account_info 
213+ 
214+         monkeypatch .setattr (
215+             target = BinanceHttpClient ,
216+             name = "send_request" ,
217+             value = mock_send_request ,
218+         )
219+ 
220+         self .provider  =  BinanceFuturesInstrumentProvider (
221+             client = binance_http_client ,
222+             clock = self .clock ,
223+             account_type = BinanceAccountType .USDT_FUTURES ,
224+         )
225+ 
226+         monkeypatch .setattr (
227+             self .provider ._http_account ,
228+             "query_futures_account_info" ,
229+             mock_query_account_info ,
230+         )
231+ 
232+         # Act 
233+         await  self .provider .load_all_async ()
234+ 
235+         # Assert - verify instruments were loaded 
236+         btc_perp  =  self .provider .find (InstrumentId (Symbol ("BTCUSDT-PERP" ), Venue ("BINANCE" )))
237+         assert  btc_perp  is  not None 
238+ 
239+         # Assert - verify info dict is JSON-serializable (no enum objects) 
240+         info_dict  =  btc_perp .info 
241+         assert  info_dict  is  not None 
242+ 
243+         # This should not raise TypeError about enum not being JSON serializable 
244+         json_str  =  json .dumps (info_dict )
245+         assert  json_str  is  not None 
246+ 
247+         # Verify enum fields were converted to strings 
248+         assert  info_dict ["status" ] ==  "TRADING" 
249+         assert  isinstance (info_dict ["status" ], str )
250+         assert  all (isinstance (ot , str ) for  ot  in  info_dict ["orderTypes" ])
251+         assert  all (isinstance (tif , str ) for  tif  in  info_dict ["timeInForce" ])
252+ 
253+     @pytest .mark .skip (reason = "WIP - test data missing allowTrailingStop field" ) 
254+     @pytest .mark .asyncio () 
255+     async  def  test_spot_instrument_info_dict_is_json_serializable (
256+         self ,
257+         binance_http_client ,
258+         live_logger ,
259+         monkeypatch ,
260+     ):
261+         """ 
262+         Test that the Spot instrument info dict contains only JSON-serializable 
263+         primitives. 
264+ 
265+         This regression test ensures that enums (like BinanceOrderType) are converted 
266+         to their string values in the info dict, preventing JSON serialization errors. 
267+ 
268+         See: https://github.com/nautechsystems/nautilus_trader/issues/3128 
269+ 
270+         """ 
271+         # Arrange 
272+         exchange_info_response  =  pkgutil .get_data (
273+             package = "tests.integration_tests.adapters.binance.resources.http_responses" ,
274+             resource = "http_spot_market_exchange_info.json" ,
275+         )
276+ 
277+         trade_fees_response  =  pkgutil .get_data (
278+             package = "tests.integration_tests.adapters.binance.resources.http_responses" ,
279+             resource = "http_wallet_trading_fees.json" ,
280+         )
281+ 
282+         responses  =  [exchange_info_response , trade_fees_response ]
283+ 
284+         async  def  mock_send_request (
285+             self ,
286+             http_method : str ,
287+             url_path : str ,
288+             payload : dict [str , str ],
289+             ratelimiter_keys : list [str ] |  None  =  None ,
290+         ) ->  bytes :
291+             return  responses .pop ()
292+ 
293+         monkeypatch .setattr (
294+             target = BinanceHttpClient ,
295+             name = "send_request" ,
296+             value = mock_send_request ,
297+         )
298+ 
299+         self .provider  =  BinanceSpotInstrumentProvider (
300+             client = binance_http_client ,
301+             clock = self .clock ,
302+             account_type = BinanceAccountType .SPOT ,
303+         )
304+ 
305+         # Act 
306+         await  self .provider .load_all_async ()
307+ 
308+         # Assert - verify instruments were loaded 
309+         btc_usdt  =  self .provider .find (InstrumentId (Symbol ("BTCUSDT" ), Venue ("BINANCE" )))
310+         assert  btc_usdt  is  not None 
311+ 
312+         # Assert - verify info dict is JSON-serializable (no enum objects) 
313+         info_dict  =  btc_usdt .info 
314+         assert  info_dict  is  not None 
315+ 
316+         # This should not raise TypeError about enum not being JSON serializable 
317+         json_str  =  json .dumps (info_dict )
318+         assert  json_str  is  not None 
319+ 
320+         # Verify enum fields were converted to strings 
321+         assert  info_dict ["status" ] ==  "TRADING" 
322+         assert  isinstance (info_dict ["status" ], str )
323+         assert  all (isinstance (ot , str ) for  ot  in  info_dict ["orderTypes" ])
0 commit comments