crypticorn-utils 0.1.0rc1__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,915 @@
1
+ """Comprehensive error handling system defining various API error types, HTTP exceptions, and error content structures."""
2
+
3
+ from enum import Enum
4
+
5
+ from crypticorn_utils.mixins import ApiErrorFallback
6
+ from fastapi import status
7
+
8
+ try:
9
+ from enum import StrEnum
10
+ except ImportError:
11
+ from strenum import StrEnum
12
+
13
+
14
+ class ApiErrorType(StrEnum):
15
+ """Type of the API error."""
16
+
17
+ USER_ERROR = "user error"
18
+ """user error by people using our services"""
19
+ EXCHANGE_ERROR = "exchange error"
20
+ """re-tryable error by the exchange or network conditions"""
21
+ SERVER_ERROR = "server error"
22
+ """server error that needs a new version rollout for a fix"""
23
+ NO_ERROR = "no error"
24
+ """error that does not need to be handled or does not affect the program or is a placeholder."""
25
+
26
+
27
+ class ApiErrorLevel(StrEnum):
28
+ """Level of the API error."""
29
+
30
+ ERROR = "error"
31
+ INFO = "info"
32
+ SUCCESS = "success"
33
+ WARNING = "warning"
34
+
35
+
36
+ class ApiErrorIdentifier(StrEnum):
37
+ """Unique identifier of the API error."""
38
+
39
+ ALLOCATION_BELOW_EXPOSURE = "allocation_below_current_exposure"
40
+ ALLOCATION_BELOW_MINIMUM = "allocation_below_min_amount"
41
+ ALLOCATION_LIMIT_EXCEEDED = "allocation_limit_exceeded"
42
+ BLACK_SWAN = "black_swan"
43
+ BOT_ALREADY_DELETED = "bot_already_deleted"
44
+ BOT_STOPPING_COMPLETED = "bot_stopping_completed"
45
+ BOT_STOPPING_STARTED = "bot_stopping_started"
46
+ CANCELLED_OPEN_ORDER = "cancelled_open_order"
47
+ CLIENT_ORDER_ID_REPEATED = "client_order_id_already_exists"
48
+ CONTENT_TYPE_ERROR = "invalid_content_type"
49
+ COUPON_INVALID = "coupon_invalid"
50
+ DELETE_BOT_ERROR = "delete_bot_error"
51
+ EXCHANGE_HTTP_ERROR = "exchange_http_request_error"
52
+ EXCHANGE_INVALID_PARAMETER = "exchange_invalid_parameter"
53
+ EXCHANGE_INVALID_SIGNATURE = "exchange_invalid_signature"
54
+ EXCHANGE_INVALID_TIMESTAMP = "exchange_invalid_timestamp"
55
+ EXCHANGE_IP_RESTRICTED = "exchange_ip_address_is_not_authorized"
56
+ EXCHANGE_KEY_ALREADY_EXISTS = "exchange_key_already_exists"
57
+ EXCHANGE_KEY_IN_USE = "exchange_key_in_use"
58
+ EXCHANGE_MAINTENANCE = "exchange_system_under_maintenance"
59
+ EXCHANGE_RATE_LIMIT = "exchange_rate_limit_exceeded"
60
+ EXCHANGE_PERMISSION_DENIED = "insufficient_permissions_spot_and_futures_required"
61
+ EXCHANGE_SERVICE_UNAVAILABLE = "exchange_service_temporarily_unavailable"
62
+ EXCHANGE_SYSTEM_BUSY = "exchange_system_is_busy"
63
+ EXCHANGE_SYSTEM_CONFIG_ERROR = "exchange_system_configuration_error"
64
+ EXCHANGE_SYSTEM_ERROR = "exchange_internal_system_error"
65
+ EXCHANGE_USER_FROZEN = "exchange_user_account_is_frozen"
66
+ EXPIRED_API_KEY = "api_key_expired"
67
+ EXPIRED_BEARER = "bearer_token_expired"
68
+ FAILED_OPEN_ORDER = "failed_open_order"
69
+ FORBIDDEN = "forbidden"
70
+ HEDGE_MODE_NOT_ACTIVE = "hedge_mode_not_active"
71
+ INSUFFICIENT_BALANCE = "insufficient_balance"
72
+ INSUFFICIENT_MARGIN = "insufficient_margin"
73
+ INSUFFICIENT_SCOPES = "insufficient_scopes"
74
+ INVALID_API_KEY = "invalid_api_key"
75
+ INVALID_BASIC_AUTH = "invalid_basic_auth"
76
+ INVALID_BEARER = "invalid_bearer"
77
+ INVALID_DATA_REQUEST = "invalid_data"
78
+ INVALID_DATA_RESPONSE = "invalid_data_response"
79
+ INVALID_EXCHANGE_KEY = "invalid_exchange_key"
80
+ INVALID_MODEL_NAME = "invalid_model_name"
81
+ LEVERAGE_EXCEEDED = "leverage_limit_exceeded"
82
+ LIQUIDATION_PRICE_VIOLATION = "order_violates_liquidation_price_constraints"
83
+ MARGIN_MODE_CLASH = "margin_mode_clash"
84
+ NAME_NOT_UNIQUE = "name_not_unique"
85
+ NO_CREDENTIALS = "no_credentials"
86
+ NOW_API_DOWN = "now_api_down"
87
+ OBJECT_ALREADY_EXISTS = "object_already_exists"
88
+ OBJECT_CREATED = "object_created"
89
+ OBJECT_DELETED = "object_deleted"
90
+ OBJECT_LOCKED = "object_locked"
91
+ OBJECT_NOT_FOUND = "object_not_found"
92
+ OBJECT_UPDATED = "object_updated"
93
+ ORDER_ALREADY_FILLED = "order_is_already_filled"
94
+ ORDER_IN_PROCESS = "order_is_being_processed"
95
+ ORDER_LIMIT_EXCEEDED = "order_quantity_limit_exceeded"
96
+ ORDER_NOT_FOUND = "order_does_not_exist"
97
+ ORDER_PRICE_INVALID = "order_price_is_invalid"
98
+ ORDER_SIZE_TOO_LARGE = "order_size_too_large"
99
+ ORDER_SIZE_TOO_SMALL = "order_size_too_small"
100
+ ORPHAN_OPEN_ORDER = "orphan_open_order"
101
+ ORPHAN_CLOSE_ORDER = "orphan_close_order"
102
+ POSITION_LIMIT_EXCEEDED = "position_limit_exceeded"
103
+ POSITION_NOT_FOUND = "position_does_not_exist"
104
+ POSITION_SUSPENDED = "position_opening_temporarily_suspended"
105
+ POST_ONLY_REJECTED = "post_only_order_would_immediately_match"
106
+ REQUEST_SCOPE_EXCEEDED = "request_scope_limit_exceeded"
107
+ RISK_LIMIT_EXCEEDED = "risk_limit_exceeded"
108
+ RPC_TIMEOUT = "rpc_timeout"
109
+ SETTLEMENT_IN_PROGRESS = "system_settlement_in_process"
110
+ STRATEGY_DISABLED = "strategy_disabled"
111
+ STRATEGY_LEVERAGE_MISMATCH = "strategy_leverage_mismatch"
112
+ STRATEGY_NOT_SUPPORTING_EXCHANGE = "strategy_not_supporting_exchange"
113
+ SUCCESS = "success"
114
+ SYMBOL_NOT_FOUND = "symbol_does_not_exist"
115
+ TRADING_ACTION_EXPIRED = "trading_action_expired"
116
+ TRADING_ACTION_SKIPPED_BOT_STOPPING = "trading_action_skipped_bot_stopping"
117
+ TRADING_LOCKED = "trading_has_been_locked"
118
+ TRADING_SUSPENDED = "trading_is_suspended"
119
+ UNKNOWN_ERROR = "unknown_error_occurred"
120
+ URL_NOT_FOUND = "requested_resource_not_found"
121
+
122
+ def get_error(self) -> "ApiError":
123
+ """Get the corresponding ApiError."""
124
+ return getattr(ApiError, self.name)
125
+
126
+
127
+ class ApiError(Enum, metaclass=ApiErrorFallback):
128
+ # Fallback to UNKNOWN_ERROR for error codes not yet published to PyPI.
129
+ """Crypticorn API error enumeration."""
130
+
131
+ ALLOCATION_BELOW_EXPOSURE = (
132
+ ApiErrorIdentifier.ALLOCATION_BELOW_EXPOSURE,
133
+ ApiErrorType.USER_ERROR,
134
+ ApiErrorLevel.ERROR,
135
+ )
136
+ ALLOCATION_BELOW_MINIMUM = (
137
+ ApiErrorIdentifier.ALLOCATION_BELOW_MINIMUM,
138
+ ApiErrorType.USER_ERROR,
139
+ ApiErrorLevel.ERROR,
140
+ )
141
+ ALLOCATION_LIMIT_EXCEEDED = (
142
+ ApiErrorIdentifier.ALLOCATION_LIMIT_EXCEEDED,
143
+ ApiErrorType.NO_ERROR,
144
+ ApiErrorLevel.INFO,
145
+ )
146
+ BLACK_SWAN = (
147
+ ApiErrorIdentifier.BLACK_SWAN,
148
+ ApiErrorType.EXCHANGE_ERROR,
149
+ ApiErrorLevel.INFO,
150
+ )
151
+ BOT_ALREADY_DELETED = (
152
+ ApiErrorIdentifier.BOT_ALREADY_DELETED,
153
+ ApiErrorType.USER_ERROR,
154
+ ApiErrorLevel.INFO,
155
+ )
156
+ BOT_STOPPING_COMPLETED = (
157
+ ApiErrorIdentifier.BOT_STOPPING_COMPLETED,
158
+ ApiErrorType.NO_ERROR,
159
+ ApiErrorLevel.SUCCESS,
160
+ )
161
+ BOT_STOPPING_STARTED = (
162
+ ApiErrorIdentifier.BOT_STOPPING_STARTED,
163
+ ApiErrorType.NO_ERROR,
164
+ ApiErrorLevel.SUCCESS,
165
+ )
166
+ CANCELLED_OPEN_ORDER = (
167
+ ApiErrorIdentifier.CANCELLED_OPEN_ORDER,
168
+ ApiErrorType.NO_ERROR,
169
+ ApiErrorLevel.INFO,
170
+ )
171
+ CLIENT_ORDER_ID_REPEATED = (
172
+ ApiErrorIdentifier.CLIENT_ORDER_ID_REPEATED,
173
+ ApiErrorType.SERVER_ERROR,
174
+ ApiErrorLevel.ERROR,
175
+ )
176
+ CONTENT_TYPE_ERROR = (
177
+ ApiErrorIdentifier.CONTENT_TYPE_ERROR,
178
+ ApiErrorType.SERVER_ERROR,
179
+ ApiErrorLevel.ERROR,
180
+ )
181
+ COUPON_INVALID = (
182
+ ApiErrorIdentifier.COUPON_INVALID,
183
+ ApiErrorType.USER_ERROR,
184
+ ApiErrorLevel.ERROR,
185
+ )
186
+ DELETE_BOT_ERROR = (
187
+ ApiErrorIdentifier.DELETE_BOT_ERROR,
188
+ ApiErrorType.SERVER_ERROR,
189
+ ApiErrorLevel.ERROR,
190
+ )
191
+ EXCHANGE_HTTP_ERROR = (
192
+ ApiErrorIdentifier.EXCHANGE_HTTP_ERROR,
193
+ ApiErrorType.EXCHANGE_ERROR,
194
+ ApiErrorLevel.ERROR,
195
+ )
196
+ EXCHANGE_INVALID_PARAMETER = (
197
+ ApiErrorIdentifier.EXCHANGE_INVALID_PARAMETER,
198
+ ApiErrorType.SERVER_ERROR,
199
+ ApiErrorLevel.ERROR,
200
+ )
201
+ EXCHANGE_INVALID_SIGNATURE = (
202
+ ApiErrorIdentifier.EXCHANGE_INVALID_SIGNATURE,
203
+ ApiErrorType.SERVER_ERROR,
204
+ ApiErrorLevel.ERROR,
205
+ )
206
+ EXCHANGE_INVALID_TIMESTAMP = (
207
+ ApiErrorIdentifier.EXCHANGE_INVALID_TIMESTAMP,
208
+ ApiErrorType.SERVER_ERROR,
209
+ ApiErrorLevel.ERROR,
210
+ )
211
+ EXCHANGE_IP_RESTRICTED = (
212
+ ApiErrorIdentifier.EXCHANGE_IP_RESTRICTED,
213
+ ApiErrorType.USER_ERROR,
214
+ ApiErrorLevel.ERROR,
215
+ )
216
+ EXCHANGE_KEY_ALREADY_EXISTS = (
217
+ ApiErrorIdentifier.EXCHANGE_KEY_ALREADY_EXISTS,
218
+ ApiErrorType.USER_ERROR,
219
+ ApiErrorLevel.ERROR,
220
+ )
221
+ EXCHANGE_KEY_IN_USE = (
222
+ ApiErrorIdentifier.EXCHANGE_KEY_IN_USE,
223
+ ApiErrorType.USER_ERROR,
224
+ ApiErrorLevel.ERROR,
225
+ )
226
+ EXCHANGE_MAINTENANCE = (
227
+ ApiErrorIdentifier.EXCHANGE_MAINTENANCE,
228
+ ApiErrorType.EXCHANGE_ERROR,
229
+ ApiErrorLevel.ERROR,
230
+ )
231
+ EXCHANGE_RATE_LIMIT = (
232
+ ApiErrorIdentifier.EXCHANGE_RATE_LIMIT,
233
+ ApiErrorType.SERVER_ERROR,
234
+ ApiErrorLevel.ERROR,
235
+ )
236
+ EXCHANGE_PERMISSION_DENIED = (
237
+ ApiErrorIdentifier.EXCHANGE_PERMISSION_DENIED,
238
+ ApiErrorType.USER_ERROR,
239
+ ApiErrorLevel.ERROR,
240
+ )
241
+ EXCHANGE_SERVICE_UNAVAILABLE = (
242
+ ApiErrorIdentifier.EXCHANGE_SERVICE_UNAVAILABLE,
243
+ ApiErrorType.EXCHANGE_ERROR,
244
+ ApiErrorLevel.ERROR,
245
+ )
246
+ EXCHANGE_SYSTEM_BUSY = (
247
+ ApiErrorIdentifier.EXCHANGE_SYSTEM_BUSY,
248
+ ApiErrorType.EXCHANGE_ERROR,
249
+ ApiErrorLevel.ERROR,
250
+ )
251
+ EXCHANGE_SYSTEM_CONFIG_ERROR = (
252
+ ApiErrorIdentifier.EXCHANGE_SYSTEM_CONFIG_ERROR,
253
+ ApiErrorType.EXCHANGE_ERROR,
254
+ ApiErrorLevel.ERROR,
255
+ )
256
+ EXCHANGE_SYSTEM_ERROR = (
257
+ ApiErrorIdentifier.EXCHANGE_SYSTEM_ERROR,
258
+ ApiErrorType.EXCHANGE_ERROR,
259
+ ApiErrorLevel.ERROR,
260
+ )
261
+ EXCHANGE_USER_FROZEN = (
262
+ ApiErrorIdentifier.EXCHANGE_USER_FROZEN,
263
+ ApiErrorType.USER_ERROR,
264
+ ApiErrorLevel.ERROR,
265
+ )
266
+ EXPIRED_API_KEY = (
267
+ ApiErrorIdentifier.EXPIRED_API_KEY,
268
+ ApiErrorType.USER_ERROR,
269
+ ApiErrorLevel.ERROR,
270
+ )
271
+ EXPIRED_BEARER = (
272
+ ApiErrorIdentifier.EXPIRED_BEARER,
273
+ ApiErrorType.USER_ERROR,
274
+ ApiErrorLevel.ERROR,
275
+ )
276
+ FAILED_OPEN_ORDER = (
277
+ ApiErrorIdentifier.FAILED_OPEN_ORDER,
278
+ ApiErrorType.NO_ERROR,
279
+ ApiErrorLevel.INFO,
280
+ )
281
+ FORBIDDEN = (
282
+ ApiErrorIdentifier.FORBIDDEN,
283
+ ApiErrorType.USER_ERROR,
284
+ ApiErrorLevel.ERROR,
285
+ )
286
+ HEDGE_MODE_NOT_ACTIVE = (
287
+ ApiErrorIdentifier.HEDGE_MODE_NOT_ACTIVE,
288
+ ApiErrorType.USER_ERROR,
289
+ ApiErrorLevel.ERROR,
290
+ )
291
+ INSUFFICIENT_BALANCE = (
292
+ ApiErrorIdentifier.INSUFFICIENT_BALANCE,
293
+ ApiErrorType.USER_ERROR,
294
+ ApiErrorLevel.ERROR,
295
+ )
296
+ INSUFFICIENT_MARGIN = (
297
+ ApiErrorIdentifier.INSUFFICIENT_MARGIN,
298
+ ApiErrorType.USER_ERROR,
299
+ ApiErrorLevel.ERROR,
300
+ )
301
+ INSUFFICIENT_SCOPES = (
302
+ ApiErrorIdentifier.INSUFFICIENT_SCOPES,
303
+ ApiErrorType.USER_ERROR,
304
+ ApiErrorLevel.ERROR,
305
+ )
306
+ INVALID_API_KEY = (
307
+ ApiErrorIdentifier.INVALID_API_KEY,
308
+ ApiErrorType.USER_ERROR,
309
+ ApiErrorLevel.ERROR,
310
+ )
311
+ INVALID_BASIC_AUTH = (
312
+ ApiErrorIdentifier.INVALID_BASIC_AUTH,
313
+ ApiErrorType.USER_ERROR,
314
+ ApiErrorLevel.ERROR,
315
+ )
316
+ INVALID_BEARER = (
317
+ ApiErrorIdentifier.INVALID_BEARER,
318
+ ApiErrorType.USER_ERROR,
319
+ ApiErrorLevel.ERROR,
320
+ )
321
+ INVALID_DATA_REQUEST = (
322
+ ApiErrorIdentifier.INVALID_DATA_REQUEST,
323
+ ApiErrorType.USER_ERROR,
324
+ ApiErrorLevel.ERROR,
325
+ )
326
+ INVALID_DATA_RESPONSE = (
327
+ ApiErrorIdentifier.INVALID_DATA_RESPONSE,
328
+ ApiErrorType.SERVER_ERROR,
329
+ ApiErrorLevel.ERROR,
330
+ )
331
+ INVALID_EXCHANGE_KEY = (
332
+ ApiErrorIdentifier.INVALID_EXCHANGE_KEY,
333
+ ApiErrorType.USER_ERROR,
334
+ ApiErrorLevel.ERROR,
335
+ )
336
+ INVALID_MODEL_NAME = (
337
+ ApiErrorIdentifier.INVALID_MODEL_NAME,
338
+ ApiErrorType.USER_ERROR,
339
+ ApiErrorLevel.ERROR,
340
+ )
341
+ LEVERAGE_EXCEEDED = (
342
+ ApiErrorIdentifier.LEVERAGE_EXCEEDED,
343
+ ApiErrorType.SERVER_ERROR,
344
+ ApiErrorLevel.ERROR,
345
+ )
346
+ LIQUIDATION_PRICE_VIOLATION = (
347
+ ApiErrorIdentifier.LIQUIDATION_PRICE_VIOLATION,
348
+ ApiErrorType.SERVER_ERROR,
349
+ ApiErrorLevel.ERROR,
350
+ )
351
+ MARGIN_MODE_CLASH = (
352
+ ApiErrorIdentifier.MARGIN_MODE_CLASH,
353
+ ApiErrorType.USER_ERROR,
354
+ ApiErrorLevel.ERROR,
355
+ )
356
+ NAME_NOT_UNIQUE = (
357
+ ApiErrorIdentifier.NAME_NOT_UNIQUE,
358
+ ApiErrorType.USER_ERROR,
359
+ ApiErrorLevel.ERROR,
360
+ )
361
+ NO_CREDENTIALS = (
362
+ ApiErrorIdentifier.NO_CREDENTIALS,
363
+ ApiErrorType.USER_ERROR,
364
+ ApiErrorLevel.ERROR,
365
+ )
366
+ NOW_API_DOWN = (
367
+ ApiErrorIdentifier.NOW_API_DOWN,
368
+ ApiErrorType.SERVER_ERROR,
369
+ ApiErrorLevel.ERROR,
370
+ )
371
+ OBJECT_ALREADY_EXISTS = (
372
+ ApiErrorIdentifier.OBJECT_ALREADY_EXISTS,
373
+ ApiErrorType.USER_ERROR,
374
+ ApiErrorLevel.ERROR,
375
+ )
376
+ OBJECT_CREATED = (
377
+ ApiErrorIdentifier.OBJECT_CREATED,
378
+ ApiErrorType.NO_ERROR,
379
+ ApiErrorLevel.SUCCESS,
380
+ )
381
+ OBJECT_DELETED = (
382
+ ApiErrorIdentifier.OBJECT_DELETED,
383
+ ApiErrorType.NO_ERROR,
384
+ ApiErrorLevel.SUCCESS,
385
+ )
386
+ OBJECT_LOCKED = (
387
+ ApiErrorIdentifier.OBJECT_LOCKED,
388
+ ApiErrorType.NO_ERROR,
389
+ ApiErrorLevel.INFO,
390
+ )
391
+ OBJECT_NOT_FOUND = (
392
+ ApiErrorIdentifier.OBJECT_NOT_FOUND,
393
+ ApiErrorType.USER_ERROR,
394
+ ApiErrorLevel.ERROR,
395
+ )
396
+ OBJECT_UPDATED = (
397
+ ApiErrorIdentifier.OBJECT_UPDATED,
398
+ ApiErrorType.NO_ERROR,
399
+ ApiErrorLevel.SUCCESS,
400
+ )
401
+ ORDER_ALREADY_FILLED = (
402
+ ApiErrorIdentifier.ORDER_ALREADY_FILLED,
403
+ ApiErrorType.SERVER_ERROR,
404
+ ApiErrorLevel.INFO,
405
+ )
406
+ ORDER_IN_PROCESS = (
407
+ ApiErrorIdentifier.ORDER_IN_PROCESS,
408
+ ApiErrorType.NO_ERROR,
409
+ ApiErrorLevel.INFO,
410
+ )
411
+ ORDER_LIMIT_EXCEEDED = (
412
+ ApiErrorIdentifier.ORDER_LIMIT_EXCEEDED,
413
+ ApiErrorType.USER_ERROR,
414
+ ApiErrorLevel.ERROR,
415
+ )
416
+ ORDER_NOT_FOUND = (
417
+ ApiErrorIdentifier.ORDER_NOT_FOUND,
418
+ ApiErrorType.SERVER_ERROR,
419
+ ApiErrorLevel.ERROR,
420
+ )
421
+ ORDER_PRICE_INVALID = (
422
+ ApiErrorIdentifier.ORDER_PRICE_INVALID,
423
+ ApiErrorType.SERVER_ERROR,
424
+ ApiErrorLevel.ERROR,
425
+ )
426
+ ORDER_SIZE_TOO_LARGE = (
427
+ ApiErrorIdentifier.ORDER_SIZE_TOO_LARGE,
428
+ ApiErrorType.USER_ERROR,
429
+ ApiErrorLevel.WARNING,
430
+ )
431
+ ORDER_SIZE_TOO_SMALL = (
432
+ ApiErrorIdentifier.ORDER_SIZE_TOO_SMALL,
433
+ ApiErrorType.USER_ERROR,
434
+ ApiErrorLevel.WARNING,
435
+ )
436
+ ORPHAN_OPEN_ORDER = (
437
+ ApiErrorIdentifier.ORPHAN_OPEN_ORDER,
438
+ ApiErrorType.NO_ERROR,
439
+ ApiErrorLevel.WARNING,
440
+ )
441
+ ORPHAN_CLOSE_ORDER = (
442
+ ApiErrorIdentifier.ORPHAN_CLOSE_ORDER,
443
+ ApiErrorType.NO_ERROR,
444
+ ApiErrorLevel.WARNING,
445
+ )
446
+ POSITION_LIMIT_EXCEEDED = (
447
+ ApiErrorIdentifier.POSITION_LIMIT_EXCEEDED,
448
+ ApiErrorType.USER_ERROR,
449
+ ApiErrorLevel.ERROR,
450
+ )
451
+ POSITION_NOT_FOUND = (
452
+ ApiErrorIdentifier.POSITION_NOT_FOUND,
453
+ ApiErrorType.NO_ERROR,
454
+ ApiErrorLevel.WARNING,
455
+ )
456
+ POSITION_SUSPENDED = (
457
+ ApiErrorIdentifier.POSITION_SUSPENDED,
458
+ ApiErrorType.EXCHANGE_ERROR,
459
+ ApiErrorLevel.ERROR,
460
+ )
461
+ POST_ONLY_REJECTED = (
462
+ ApiErrorIdentifier.POST_ONLY_REJECTED,
463
+ ApiErrorType.SERVER_ERROR,
464
+ ApiErrorLevel.ERROR,
465
+ )
466
+ REQUEST_SCOPE_EXCEEDED = (
467
+ ApiErrorIdentifier.REQUEST_SCOPE_EXCEEDED,
468
+ ApiErrorType.EXCHANGE_ERROR,
469
+ ApiErrorLevel.ERROR,
470
+ )
471
+ RISK_LIMIT_EXCEEDED = (
472
+ ApiErrorIdentifier.RISK_LIMIT_EXCEEDED,
473
+ ApiErrorType.SERVER_ERROR,
474
+ ApiErrorLevel.ERROR,
475
+ )
476
+ RPC_TIMEOUT = (
477
+ ApiErrorIdentifier.RPC_TIMEOUT,
478
+ ApiErrorType.EXCHANGE_ERROR,
479
+ ApiErrorLevel.ERROR,
480
+ )
481
+ SETTLEMENT_IN_PROGRESS = (
482
+ ApiErrorIdentifier.SETTLEMENT_IN_PROGRESS,
483
+ ApiErrorType.EXCHANGE_ERROR,
484
+ ApiErrorLevel.ERROR,
485
+ )
486
+ STRATEGY_DISABLED = (
487
+ ApiErrorIdentifier.STRATEGY_DISABLED,
488
+ ApiErrorType.NO_ERROR,
489
+ ApiErrorLevel.WARNING,
490
+ )
491
+ STRATEGY_LEVERAGE_MISMATCH = (
492
+ ApiErrorIdentifier.STRATEGY_LEVERAGE_MISMATCH,
493
+ ApiErrorType.USER_ERROR,
494
+ ApiErrorLevel.ERROR,
495
+ )
496
+ STRATEGY_NOT_SUPPORTING_EXCHANGE = (
497
+ ApiErrorIdentifier.STRATEGY_NOT_SUPPORTING_EXCHANGE,
498
+ ApiErrorType.NO_ERROR,
499
+ ApiErrorLevel.WARNING,
500
+ )
501
+ SUCCESS = (ApiErrorIdentifier.SUCCESS, ApiErrorType.NO_ERROR, ApiErrorLevel.SUCCESS)
502
+ SYMBOL_NOT_FOUND = (
503
+ ApiErrorIdentifier.SYMBOL_NOT_FOUND,
504
+ ApiErrorType.SERVER_ERROR,
505
+ ApiErrorLevel.ERROR,
506
+ )
507
+ TRADING_ACTION_EXPIRED = (
508
+ ApiErrorIdentifier.TRADING_ACTION_EXPIRED,
509
+ ApiErrorType.NO_ERROR,
510
+ ApiErrorLevel.INFO,
511
+ )
512
+ TRADING_ACTION_SKIPPED_BOT_STOPPING = (
513
+ ApiErrorIdentifier.TRADING_ACTION_SKIPPED_BOT_STOPPING,
514
+ ApiErrorType.NO_ERROR,
515
+ ApiErrorLevel.INFO,
516
+ )
517
+ TRADING_LOCKED = (
518
+ ApiErrorIdentifier.TRADING_LOCKED,
519
+ ApiErrorType.EXCHANGE_ERROR,
520
+ ApiErrorLevel.ERROR,
521
+ )
522
+ TRADING_SUSPENDED = (
523
+ ApiErrorIdentifier.TRADING_SUSPENDED,
524
+ ApiErrorType.EXCHANGE_ERROR,
525
+ ApiErrorLevel.ERROR,
526
+ )
527
+ UNKNOWN_ERROR = (
528
+ ApiErrorIdentifier.UNKNOWN_ERROR,
529
+ ApiErrorType.SERVER_ERROR,
530
+ ApiErrorLevel.ERROR,
531
+ )
532
+ URL_NOT_FOUND = (
533
+ ApiErrorIdentifier.URL_NOT_FOUND,
534
+ ApiErrorType.SERVER_ERROR,
535
+ ApiErrorLevel.ERROR,
536
+ )
537
+
538
+ @property
539
+ def identifier(self) -> str:
540
+ """Identifier of the error."""
541
+ return self.value[0]
542
+
543
+ @property
544
+ def type(self) -> ApiErrorType:
545
+ """Type of the error."""
546
+ return self.value[1]
547
+
548
+ @property
549
+ def level(self) -> ApiErrorLevel:
550
+ """Level of the error."""
551
+ return self.value[2]
552
+
553
+ @property
554
+ def http_code(self) -> int:
555
+ """HTTP status code for the error."""
556
+ return StatusCodeMapper.get_http_code(self)
557
+
558
+ @property
559
+ def websocket_code(self) -> int:
560
+ """WebSocket status code for the error."""
561
+ return StatusCodeMapper.get_websocket_code(self)
562
+
563
+ @classmethod
564
+ def from_json(cls, data: dict) -> "ApiError":
565
+ """Load an ApiError from a dictionary. Must contain the identifier with the key 'code'."""
566
+ return next(error for error in cls if error.identifier == data["code"])
567
+
568
+
569
+ class StatusCodeMapper:
570
+ """Mapping of API errors to HTTP/Websocket status codes."""
571
+
572
+ _mapping = {
573
+ # Authentication/Authorization
574
+ ApiError.EXPIRED_BEARER: (
575
+ status.HTTP_401_UNAUTHORIZED,
576
+ status.WS_1008_POLICY_VIOLATION,
577
+ ),
578
+ ApiError.INVALID_BASIC_AUTH: (
579
+ status.HTTP_401_UNAUTHORIZED,
580
+ status.WS_1008_POLICY_VIOLATION,
581
+ ),
582
+ ApiError.INVALID_BEARER: (
583
+ status.HTTP_401_UNAUTHORIZED,
584
+ status.WS_1008_POLICY_VIOLATION,
585
+ ),
586
+ ApiError.EXPIRED_API_KEY: (
587
+ status.HTTP_401_UNAUTHORIZED,
588
+ status.WS_1008_POLICY_VIOLATION,
589
+ ),
590
+ ApiError.INVALID_API_KEY: (
591
+ status.HTTP_401_UNAUTHORIZED,
592
+ status.WS_1008_POLICY_VIOLATION,
593
+ ),
594
+ ApiError.NO_CREDENTIALS: (
595
+ status.HTTP_401_UNAUTHORIZED,
596
+ status.WS_1008_POLICY_VIOLATION,
597
+ ),
598
+ ApiError.INSUFFICIENT_SCOPES: (
599
+ status.HTTP_403_FORBIDDEN,
600
+ status.WS_1008_POLICY_VIOLATION,
601
+ ),
602
+ ApiError.EXCHANGE_PERMISSION_DENIED: (
603
+ status.HTTP_403_FORBIDDEN,
604
+ status.WS_1008_POLICY_VIOLATION,
605
+ ),
606
+ ApiError.EXCHANGE_USER_FROZEN: (
607
+ status.HTTP_403_FORBIDDEN,
608
+ status.WS_1008_POLICY_VIOLATION,
609
+ ),
610
+ ApiError.TRADING_LOCKED: (
611
+ status.HTTP_403_FORBIDDEN,
612
+ status.WS_1008_POLICY_VIOLATION,
613
+ ),
614
+ ApiError.FORBIDDEN: (
615
+ status.HTTP_403_FORBIDDEN,
616
+ status.WS_1008_POLICY_VIOLATION,
617
+ ),
618
+ # Not Found
619
+ ApiError.URL_NOT_FOUND: (
620
+ status.HTTP_404_NOT_FOUND,
621
+ status.WS_1008_POLICY_VIOLATION,
622
+ ),
623
+ ApiError.OBJECT_NOT_FOUND: (
624
+ status.HTTP_404_NOT_FOUND,
625
+ status.WS_1008_POLICY_VIOLATION,
626
+ ),
627
+ ApiError.ORDER_NOT_FOUND: (
628
+ status.HTTP_404_NOT_FOUND,
629
+ status.WS_1008_POLICY_VIOLATION,
630
+ ),
631
+ ApiError.POSITION_NOT_FOUND: (
632
+ status.HTTP_404_NOT_FOUND,
633
+ status.WS_1008_POLICY_VIOLATION,
634
+ ),
635
+ ApiError.SYMBOL_NOT_FOUND: (
636
+ status.HTTP_404_NOT_FOUND,
637
+ status.WS_1008_POLICY_VIOLATION,
638
+ ),
639
+ # Conflicts/Duplicates
640
+ ApiError.CLIENT_ORDER_ID_REPEATED: (
641
+ status.HTTP_409_CONFLICT,
642
+ status.WS_1008_POLICY_VIOLATION,
643
+ ),
644
+ ApiError.OBJECT_ALREADY_EXISTS: (
645
+ status.HTTP_409_CONFLICT,
646
+ status.WS_1008_POLICY_VIOLATION,
647
+ ),
648
+ ApiError.EXCHANGE_KEY_ALREADY_EXISTS: (
649
+ status.HTTP_409_CONFLICT,
650
+ status.WS_1008_POLICY_VIOLATION,
651
+ ),
652
+ ApiError.BOT_ALREADY_DELETED: (
653
+ status.HTTP_409_CONFLICT,
654
+ status.WS_1008_POLICY_VIOLATION,
655
+ ),
656
+ ApiError.NAME_NOT_UNIQUE: (
657
+ status.HTTP_409_CONFLICT,
658
+ status.WS_1008_POLICY_VIOLATION,
659
+ ),
660
+ # Invalid Content
661
+ ApiError.CONTENT_TYPE_ERROR: (
662
+ status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
663
+ status.WS_1003_UNSUPPORTED_DATA,
664
+ ),
665
+ ApiError.INVALID_DATA_REQUEST: (
666
+ status.HTTP_422_UNPROCESSABLE_ENTITY,
667
+ status.WS_1007_INVALID_FRAME_PAYLOAD_DATA,
668
+ ),
669
+ ApiError.INVALID_DATA_RESPONSE: (
670
+ status.HTTP_422_UNPROCESSABLE_ENTITY,
671
+ status.WS_1007_INVALID_FRAME_PAYLOAD_DATA,
672
+ ),
673
+ # Rate Limits
674
+ ApiError.EXCHANGE_RATE_LIMIT: (
675
+ status.HTTP_429_TOO_MANY_REQUESTS,
676
+ status.WS_1013_TRY_AGAIN_LATER,
677
+ ),
678
+ ApiError.REQUEST_SCOPE_EXCEEDED: (
679
+ status.HTTP_429_TOO_MANY_REQUESTS,
680
+ status.WS_1013_TRY_AGAIN_LATER,
681
+ ),
682
+ # Server Errors
683
+ ApiError.UNKNOWN_ERROR: (
684
+ status.HTTP_500_INTERNAL_SERVER_ERROR,
685
+ status.WS_1011_INTERNAL_ERROR,
686
+ ),
687
+ ApiError.EXCHANGE_SYSTEM_ERROR: (
688
+ status.HTTP_500_INTERNAL_SERVER_ERROR,
689
+ status.WS_1011_INTERNAL_ERROR,
690
+ ),
691
+ ApiError.NOW_API_DOWN: (
692
+ status.HTTP_500_INTERNAL_SERVER_ERROR,
693
+ status.WS_1011_INTERNAL_ERROR,
694
+ ),
695
+ ApiError.RPC_TIMEOUT: (
696
+ status.HTTP_500_INTERNAL_SERVER_ERROR,
697
+ status.WS_1011_INTERNAL_ERROR,
698
+ ),
699
+ # Service Unavailable
700
+ ApiError.EXCHANGE_SERVICE_UNAVAILABLE: (
701
+ status.HTTP_503_SERVICE_UNAVAILABLE,
702
+ status.WS_1011_INTERNAL_ERROR,
703
+ ),
704
+ ApiError.EXCHANGE_MAINTENANCE: (
705
+ status.HTTP_503_SERVICE_UNAVAILABLE,
706
+ status.WS_1011_INTERNAL_ERROR,
707
+ ),
708
+ ApiError.EXCHANGE_SYSTEM_BUSY: (
709
+ status.HTTP_503_SERVICE_UNAVAILABLE,
710
+ status.WS_1011_INTERNAL_ERROR,
711
+ ),
712
+ ApiError.SETTLEMENT_IN_PROGRESS: (
713
+ status.HTTP_503_SERVICE_UNAVAILABLE,
714
+ status.WS_1011_INTERNAL_ERROR,
715
+ ),
716
+ ApiError.POSITION_SUSPENDED: (
717
+ status.HTTP_503_SERVICE_UNAVAILABLE,
718
+ status.WS_1011_INTERNAL_ERROR,
719
+ ),
720
+ ApiError.TRADING_SUSPENDED: (
721
+ status.HTTP_503_SERVICE_UNAVAILABLE,
722
+ status.WS_1011_INTERNAL_ERROR,
723
+ ),
724
+ # Bad Requests (400) - Invalid parameters or states
725
+ ApiError.MARGIN_MODE_CLASH: (
726
+ status.HTTP_400_BAD_REQUEST,
727
+ status.WS_1008_POLICY_VIOLATION,
728
+ ),
729
+ ApiError.INVALID_MODEL_NAME: (
730
+ status.HTTP_400_BAD_REQUEST,
731
+ status.WS_1008_POLICY_VIOLATION,
732
+ ),
733
+ ApiError.ALLOCATION_BELOW_EXPOSURE: (
734
+ status.HTTP_400_BAD_REQUEST,
735
+ status.WS_1008_POLICY_VIOLATION,
736
+ ),
737
+ ApiError.ALLOCATION_LIMIT_EXCEEDED: (
738
+ status.HTTP_400_BAD_REQUEST,
739
+ status.WS_1008_POLICY_VIOLATION,
740
+ ),
741
+ ApiError.ALLOCATION_BELOW_MINIMUM: (
742
+ status.HTTP_400_BAD_REQUEST,
743
+ status.WS_1008_POLICY_VIOLATION,
744
+ ),
745
+ ApiError.BLACK_SWAN: (
746
+ status.HTTP_400_BAD_REQUEST,
747
+ status.WS_1008_POLICY_VIOLATION,
748
+ ),
749
+ ApiError.DELETE_BOT_ERROR: (
750
+ status.HTTP_400_BAD_REQUEST,
751
+ status.WS_1008_POLICY_VIOLATION,
752
+ ),
753
+ ApiError.EXCHANGE_INVALID_SIGNATURE: (
754
+ status.HTTP_400_BAD_REQUEST,
755
+ status.WS_1008_POLICY_VIOLATION,
756
+ ),
757
+ ApiError.EXCHANGE_INVALID_TIMESTAMP: (
758
+ status.HTTP_400_BAD_REQUEST,
759
+ status.WS_1008_POLICY_VIOLATION,
760
+ ),
761
+ ApiError.EXCHANGE_IP_RESTRICTED: (
762
+ status.HTTP_400_BAD_REQUEST,
763
+ status.WS_1008_POLICY_VIOLATION,
764
+ ),
765
+ ApiError.EXCHANGE_KEY_IN_USE: (
766
+ status.HTTP_400_BAD_REQUEST,
767
+ status.WS_1008_POLICY_VIOLATION,
768
+ ),
769
+ ApiError.EXCHANGE_SYSTEM_CONFIG_ERROR: (
770
+ status.HTTP_400_BAD_REQUEST,
771
+ status.WS_1008_POLICY_VIOLATION,
772
+ ),
773
+ ApiError.HEDGE_MODE_NOT_ACTIVE: (
774
+ status.HTTP_400_BAD_REQUEST,
775
+ status.WS_1008_POLICY_VIOLATION,
776
+ ),
777
+ ApiError.EXCHANGE_HTTP_ERROR: (
778
+ status.HTTP_400_BAD_REQUEST,
779
+ status.WS_1008_POLICY_VIOLATION,
780
+ ),
781
+ ApiError.INSUFFICIENT_BALANCE: (
782
+ status.HTTP_400_BAD_REQUEST,
783
+ status.WS_1008_POLICY_VIOLATION,
784
+ ),
785
+ ApiError.INSUFFICIENT_MARGIN: (
786
+ status.HTTP_400_BAD_REQUEST,
787
+ status.WS_1008_POLICY_VIOLATION,
788
+ ),
789
+ ApiError.INVALID_EXCHANGE_KEY: (
790
+ status.HTTP_400_BAD_REQUEST,
791
+ status.WS_1008_POLICY_VIOLATION,
792
+ ),
793
+ ApiError.EXCHANGE_INVALID_PARAMETER: (
794
+ status.HTTP_400_BAD_REQUEST,
795
+ status.WS_1008_POLICY_VIOLATION,
796
+ ),
797
+ ApiError.LEVERAGE_EXCEEDED: (
798
+ status.HTTP_400_BAD_REQUEST,
799
+ status.WS_1008_POLICY_VIOLATION,
800
+ ),
801
+ ApiError.LIQUIDATION_PRICE_VIOLATION: (
802
+ status.HTTP_400_BAD_REQUEST,
803
+ status.WS_1008_POLICY_VIOLATION,
804
+ ),
805
+ ApiError.ORDER_ALREADY_FILLED: (
806
+ status.HTTP_400_BAD_REQUEST,
807
+ status.WS_1008_POLICY_VIOLATION,
808
+ ),
809
+ ApiError.ORDER_IN_PROCESS: (
810
+ status.HTTP_400_BAD_REQUEST,
811
+ status.WS_1008_POLICY_VIOLATION,
812
+ ),
813
+ ApiError.ORDER_LIMIT_EXCEEDED: (
814
+ status.HTTP_400_BAD_REQUEST,
815
+ status.WS_1008_POLICY_VIOLATION,
816
+ ),
817
+ ApiError.ORDER_PRICE_INVALID: (
818
+ status.HTTP_400_BAD_REQUEST,
819
+ status.WS_1008_POLICY_VIOLATION,
820
+ ),
821
+ ApiError.ORDER_SIZE_TOO_LARGE: (
822
+ status.HTTP_400_BAD_REQUEST,
823
+ status.WS_1008_POLICY_VIOLATION,
824
+ ),
825
+ ApiError.ORDER_SIZE_TOO_SMALL: (
826
+ status.HTTP_400_BAD_REQUEST,
827
+ status.WS_1008_POLICY_VIOLATION,
828
+ ),
829
+ ApiError.ORPHAN_OPEN_ORDER: (
830
+ status.HTTP_400_BAD_REQUEST,
831
+ status.WS_1008_POLICY_VIOLATION,
832
+ ),
833
+ ApiError.ORPHAN_CLOSE_ORDER: (
834
+ status.HTTP_400_BAD_REQUEST,
835
+ status.WS_1008_POLICY_VIOLATION,
836
+ ),
837
+ ApiError.POSITION_LIMIT_EXCEEDED: (
838
+ status.HTTP_400_BAD_REQUEST,
839
+ status.WS_1008_POLICY_VIOLATION,
840
+ ),
841
+ ApiError.POST_ONLY_REJECTED: (
842
+ status.HTTP_400_BAD_REQUEST,
843
+ status.WS_1008_POLICY_VIOLATION,
844
+ ),
845
+ ApiError.RISK_LIMIT_EXCEEDED: (
846
+ status.HTTP_400_BAD_REQUEST,
847
+ status.WS_1008_POLICY_VIOLATION,
848
+ ),
849
+ ApiError.STRATEGY_DISABLED: (
850
+ status.HTTP_400_BAD_REQUEST,
851
+ status.WS_1008_POLICY_VIOLATION,
852
+ ),
853
+ ApiError.STRATEGY_LEVERAGE_MISMATCH: (
854
+ status.HTTP_400_BAD_REQUEST,
855
+ status.WS_1008_POLICY_VIOLATION,
856
+ ),
857
+ ApiError.STRATEGY_NOT_SUPPORTING_EXCHANGE: (
858
+ status.HTTP_400_BAD_REQUEST,
859
+ status.WS_1008_POLICY_VIOLATION,
860
+ ),
861
+ ApiError.TRADING_ACTION_EXPIRED: (
862
+ status.HTTP_400_BAD_REQUEST,
863
+ status.WS_1008_POLICY_VIOLATION,
864
+ ),
865
+ ApiError.TRADING_ACTION_SKIPPED_BOT_STOPPING: (
866
+ status.HTTP_400_BAD_REQUEST,
867
+ status.WS_1008_POLICY_VIOLATION,
868
+ ),
869
+ # Success cases
870
+ ApiError.SUCCESS: (status.HTTP_200_OK, status.WS_1000_NORMAL_CLOSURE),
871
+ ApiError.BOT_STOPPING_COMPLETED: (
872
+ status.HTTP_200_OK,
873
+ status.WS_1000_NORMAL_CLOSURE,
874
+ ),
875
+ ApiError.BOT_STOPPING_STARTED: (
876
+ status.HTTP_200_OK,
877
+ status.WS_1000_NORMAL_CLOSURE,
878
+ ),
879
+ ApiError.OBJECT_CREATED: (
880
+ status.HTTP_201_CREATED,
881
+ status.WS_1000_NORMAL_CLOSURE,
882
+ ),
883
+ ApiError.OBJECT_UPDATED: (status.HTTP_200_OK, status.WS_1000_NORMAL_CLOSURE),
884
+ ApiError.OBJECT_DELETED: (
885
+ status.HTTP_204_NO_CONTENT,
886
+ status.WS_1000_NORMAL_CLOSURE,
887
+ ),
888
+ ApiError.CANCELLED_OPEN_ORDER: (
889
+ status.HTTP_200_OK,
890
+ status.WS_1008_POLICY_VIOLATION,
891
+ ),
892
+ ApiError.FAILED_OPEN_ORDER: (
893
+ status.HTTP_200_OK,
894
+ status.WS_1008_POLICY_VIOLATION,
895
+ ),
896
+ ApiError.COUPON_INVALID: (
897
+ status.HTTP_200_OK,
898
+ status.WS_1000_NORMAL_CLOSURE,
899
+ ),
900
+ # Miscellaneous
901
+ ApiError.OBJECT_LOCKED: (
902
+ status.HTTP_423_LOCKED,
903
+ status.WS_1013_TRY_AGAIN_LATER,
904
+ ),
905
+ }
906
+
907
+ @classmethod
908
+ def get_http_code(cls, error: ApiError) -> int:
909
+ """Get the HTTP status code for the error. If the error is not in the mapping, return 500."""
910
+ return cls._mapping.get(error, cls._mapping[ApiError.UNKNOWN_ERROR])[0]
911
+
912
+ @classmethod
913
+ def get_websocket_code(cls, error: ApiError) -> int:
914
+ """Get the WebSocket status code for the error. If the error is not in the mapping, return 1008."""
915
+ return cls._mapping.get(error, cls._mapping[ApiError.UNKNOWN_ERROR])[1]