1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
//! TWS notices: the [`Notice`] type, its [`NoticeCategory`] classification,
//! [`ConnectivityStatus`], the notice code constants and ranges, and the
//! builders for client-synthesized notices.
use Display;
use ;
use OffsetDateTime;
use ResponseMessage;
/// An error message from the TWS API.
/// Error code indicating an order was cancelled (confirmation, not an error).
pub const ORDER_CANCELLED_CODE: i32 = 202;
/// Generic order-message code whose text determines whether TWS reports a warning or an error.
pub const ORDER_MESSAGE_CODE: i32 = 399;
/// "Market depth data has been RESET". A [`DATA_ADVISORY_CODES`] entry; a
/// depth subscription yields it as [`MarketDepths::Reset`](crate::market_data::realtime::MarketDepths::Reset).
pub const MARKET_DEPTH_RESET_CODE: i32 = 317;
/// "Historical Market Data Service query message". Classified `Error`, but a
/// scanner subscription keeps its "no items retrieved" form nonterminal; see
/// [`classify`].
pub const HMDS_QUERY_MESSAGE_CODE: i32 = 165;
/// Range of error codes that are considered warnings: the whole `21xx` band.
///
/// IB's published table stops at 2169, but the gateway keeps adding codes above
/// it (2176, the fractional-share size-rule warning; 2187, generic ticks
/// unavailable on delayed-data fallback), and a ceiling of 2169 turned each new
/// one into a hard error that failed in-flight one-shots and ended
/// subscriptions.
pub const WARNING_CODE_RANGE: RangeInclusive = 2100..=2199;
/// Code 0 is a code-less frame: IB Gateway omits `error_code` on informational
/// notices (e.g. "Warning: Approaching max rate of 50 messages per second"),
/// and the absent field decodes to 0. An error frame that fails proto decode
/// falls back to the same default, so code 0 never carries a hard error.
pub
/// Classify a raw error frame into a disjoint [`NoticeCategory`].
///
/// The single owner of the precedence chain documented on [`NoticeCategory`];
/// [`Notice::category`] and [`is_informational_code`] both derive from it, so
/// routing and the public partition cannot disagree.
///
/// One exception sits downstream of routing:
/// [`StreamDecoder::is_nonterminal_notice`](crate::subscriptions::common::StreamDecoder::is_nonterminal_notice)
/// lets a subscription deliver a request-bound `Error` notice as
/// `SubscriptionItem::Notice` instead of ending. The notice keeps its `Error`
/// category. Only the scanner uses it, for [`HMDS_QUERY_MESSAGE_CODE`] "no items
/// retrieved" (#886); making 165 informational here would also reach one-shot
/// requests, which would wait for an answer that never comes.
pub
/// Check if an error code is informational: every [`NoticeCategory`] except
/// `RequestError`, `OrderRejection` and `Error`.
///
/// For these TWS proceeds with the request, the frame confirms an outcome the
/// caller asked for, or the frame reports a connection-wide state change
/// rather than a failed request — so they are routed as a `Notice` rather
/// than terminating the subscription as an `Error`, and, when request-less,
/// they do not fail the pending one-shots.
///
/// System messages never stand in for a per-request answer: after 1100 TWS
/// still answers or rejects each request itself, and after 1300 the socket is
/// dropped, so pending one-shots see `Error::ConnectionReset` from the
/// transport reset and retry. Note 202 and the system codes are deliberately
/// *not* in [`is_warning_message`]: `Notice::is_warning()` stays false for
/// them; only the routing disposition treats them like warnings.
pub
/// Connectivity between IB and TWS has been lost.
pub const CONNECTIVITY_LOST_CODE: i32 = 1100;
/// Connectivity restored, but market data was lost; resubscription is required.
pub const CONNECTIVITY_RESTORED_DATA_LOST_CODE: i32 = 1101;
/// Connectivity restored with market data maintained (nothing lost).
pub const CONNECTIVITY_RESTORED_DATA_MAINTAINED_CODE: i32 = 1102;
/// Socket port was reset during an active connection; the connection is dropped.
pub const SOCKET_PORT_RESET_CODE: i32 = 1300;
/// System message codes indicating connectivity status.
/// - 1100: Connectivity lost
/// - 1101: Connectivity restored, market data lost (resubscribe needed)
/// - 1102: Connectivity restored, market data maintained
/// - 1300: Socket port reset during active connection
pub const SYSTEM_MESSAGE_CODES: = ;
/// Data-advisory codes: frames that look like errors but reject nothing — the
/// request stands and data keeps flowing. Each one describes what will arrive
/// (a fallback, a partial entitlement, or a reset of what was already
/// delivered), so they are informational notices, not errors. Classifying them
/// as errors would terminate the subscription before its data arrives.
/// - 317: Market depth data has been RESET. Please empty deep book contents
/// before applying any new entries. A depth subscription yields it as
/// [`MarketDepths::Reset`](crate::market_data::realtime::MarketDepths::Reset).
/// - 2188: Up-to-the-second historical data requires additional subscription for the API.
/// - 10089: Requested market data requires additional subscription for API; delayed market data is available.
/// - 10090: Part of requested market data is not subscribed. Subscription-independent ticks are still active.
/// - 10091: Part of requested market data requires additional subscription for API.
/// - 10167: Requested market data is not subscribed. Displaying delayed market data.
///
/// A slice rather than an array so that adding a code is not a type change
/// for callers that bind the constant explicitly.
pub const DATA_ADVISORY_CODES: & = &;
/// Request-error codes inside [`ORDER_REJECTION_CODE_RANGE`]: a request other
/// than an order failed (market data, depth, historical data, scanner, session
/// setup). Classified [`NoticeCategory::RequestError`], ahead of the band.
///
/// Only codes that never answer an order are listed. Codes that answer both
/// orders and other requests stay [`NoticeCategory::OrderRejection`], so
/// [`Notice::is_order_rejection`] keeps matching them on order streams: 200 (no
/// security definition) and 320-323 (server error reading, validating or
/// processing a request; 320 answers an invalid attached order, #842).
/// - 300: Can't find EId with ticker Id (cancelling unknown market data).
/// - 301, 302: Invalid ticker action; error parsing stop ticker string.
/// - 309: Max number of market depth requests has been reached.
/// - 310: Can't find the subscribed market depth.
/// - 316: Market depth data has been HALTED. Please re-subscribe.
/// - 319: Invalid log level.
/// - 326: Client id already in use.
/// - 327: Only clientId 0 can set the auto bind TWS orders property.
/// - 330, 331: Managed accounts list needs an FA or STL account with managed accounts.
/// - 354: Not subscribed to requested market data.
/// - 357: Client version out of date.
/// - 365, 366: No scanner subscription / historical data query for the ticker id.
/// - 385, 386: Duplicate ticker id for scanner subscription / historical data query.
///
/// A slice rather than an array so that adding a code is not a type change
/// for callers that bind the constant explicitly.
pub const REQUEST_ERROR_CODES: & = &;
/// Data-farm codes reporting a healthy connection ("…connection is OK").
/// Subset of [`WARNING_CODE_RANGE`]; classified [`ConnectivityStatus::Ok`].
pub const FARM_OK_CODES: = ;
/// Data-farm codes reporting a broken connection ("…connection is broken").
/// Subset of [`WARNING_CODE_RANGE`]; classified [`ConnectivityStatus::Broken`].
pub const FARM_BROKEN_CODES: = ;
/// Data-farm codes reporting a dormant-but-available connection
/// ("…inactive but should be available upon demand").
/// Subset of [`WARNING_CODE_RANGE`]; classified [`ConnectivityStatus::Inactive`].
pub const FARM_INACTIVE_CODES: = ;
/// Data-farm codes reporting a connection in progress ("…farm is connecting").
/// Subset of [`WARNING_CODE_RANGE`]; classified [`ConnectivityStatus::Connecting`].
pub const FARM_CONNECTING_CODES: = ;
/// The order-rejection band (200-399): order parameter validation, margin and
/// risk-check rejections.
///
/// [`Notice::category`] classifies a code in this band as
/// [`NoticeCategory::OrderRejection`] unless an earlier rule claims it; see
/// [`NoticeCategory`] for the precedence chain.
pub const ORDER_REJECTION_CODE_RANGE: RangeInclusive = 200..=399;
/// Synthesized notice code emitted when a handshake-time frame's recognized
/// [`IncomingMessages`](super::IncomingMessages) kind has no typed `StartupMessage` variant. A frame
/// whose id maps to no kind at all raises [`UNKNOWN_MESSAGE_TYPE_CODE`]
/// instead, as it does after the handshake. Negative
/// (TWS uses 0+); the other client-synthesized codes are
/// [`HANDSHAKE_DECODE_FAILURE_CODE`], [`UNKNOWN_MESSAGE_TYPE_CODE`],
/// [`SUBSCRIPTION_LAG_CODE`], [`NOTICE_STREAM_LAG_CODE`], and
/// [`TRANSPORT_RECONNECT_CODE`]. See
/// [`Notice::is_handshake_synthetic`].
pub const HANDSHAKE_UNKNOWN_FRAME_CODE: i32 = -3;
/// Synthesized notice code emitted when a typed handshake decoder fails for
/// a known [`IncomingMessages`](super::IncomingMessages) kind (`OpenOrder`, `OrderStatus`,
/// `AccountValue`/`PortfolioValue`/`AccountUpdateTime`/`AccountDownloadEnd`,
/// `ExecutionData`, `CommissionsReport`, `CompletedOrder`). Distinct from
/// [`HANDSHAKE_UNKNOWN_FRAME_CODE`] so consumers can separate TWS schema
/// drift from rust-ibapi decoder bugs. See
/// [`Notice::is_handshake_synthetic`].
pub const HANDSHAKE_DECODE_FAILURE_CODE: i32 = -4;
/// Synthesized notice code emitted when a frame arrives whose 4-byte message
/// id maps to no known [`IncomingMessages`](super::IncomingMessages) kind, so nothing can route it.
///
/// Negative, like the other client-synthesized notice codes
/// ([`HANDSHAKE_UNKNOWN_FRAME_CODE`], [`HANDSHAKE_DECODE_FAILURE_CODE`],
/// [`SUBSCRIPTION_LAG_CODE`], [`NOTICE_STREAM_LAG_CODE`],
/// [`TRANSPORT_RECONNECT_CODE`]); TWS itself only
/// uses codes 0 and up.
///
/// This is the observable form of a framing desynchronization: the length
/// prefix is positional, so once a read starts at the wrong offset every
/// subsequent message id is garbage — usually unrecognized (this notice), but
/// occasionally colliding with a real kind, in which case the payload decodes
/// without error into plausible-looking wrong values.
///
/// The notice text names the offending id, which is what separates the two
/// explanations: a slipped stream yields *scattered* ids, while a message type
/// IBKR has added repeats one. A burst of distinct ids on a previously healthy
/// connection means the framing slipped.
///
/// Raised the same way during the connection handshake, which is the window a
/// reconnect runs through; [`HANDSHAKE_UNKNOWN_FRAME_CODE`] is reserved for
/// recognized kinds with no typed startup variant.
pub const UNKNOWN_MESSAGE_TYPE_CODE: i32 = -5;
/// Build the [`UNKNOWN_MESSAGE_TYPE_CODE`] notice for `message`, and emit the
/// matching `warn!`. The single owner of the wording, shared by steady-state
/// routing and the handshake.
pub
/// Synthesized notice code emitted in-band on an async subscription whose
/// consumer fell behind its broadcast channel: the channel evicted the oldest
/// frames, and this notice — carrying the dropped count in its message — is
/// what the consumer sees in their place.
///
/// Negative, like the other client-synthesized notice codes; TWS itself only
/// uses codes 0 and up.
///
/// On receiving this notice the stream is still live, but frames are gone:
/// reconcile the same way as after a reconnect gap.
///
/// - **Market data and other non-order streams** (ticks, bars, depth,
/// positions, account updates, PnL, news, contract details): bounded and
/// lossy by design — freshness beats completeness, and a feed
/// self-corrects with the next tick. Channels hold
/// `ClientBuilder::channel_capacity` frames; a consumer that lags
/// persistently should raise it or consume faster.
/// - **Orders** (`place_order`, `cancel_order`, `exercise_options`,
/// `executions`, `order_update_stream`, the open/completed-order streams):
/// `channel_capacity` can raise these channels but not shrink them, so
/// this notice there means order state was lost and the client logs it as
/// an error. Resynchronize with `open_orders()` / `executions()` before
/// acting on recorded state.
///
/// The sync client never drops: its queues are unbounded and log a warning
/// at every 10,000 unread messages instead. Both transports surface falling
/// behind; only async trades completeness for bounded memory.
pub const SUBSCRIPTION_LAG_CODE: i32 = -6;
/// Build the in-band notice for a subscription that fell behind its broadcast
/// channel and had `skipped` frames evicted, and emit the matching `warn!`.
/// The single owner of the non-order lag wording; see [`SUBSCRIPTION_LAG_CODE`].
pub
/// [`subscription_lag_notice`] for an order-class channel, where a gap means
/// lost order state: logged as an error, and the message says to resync.
/// The single owner of the order lag wording.
pub
/// Synthesized notice code delivered in-band on the notice stream when its
/// consumer fell behind the notice fan-out: the broadcast channel evicted the
/// oldest notices, and this notice — carrying the dropped count in its
/// message — is what the consumer sees in their place.
///
/// Negative, like the other client-synthesized notice codes; TWS itself only
/// uses codes 0 and up.
///
/// The notice stream is how a stateful consumer receives the unrouted
/// connection-status notices (1100 lost, 1101/1102 restored) it derives
/// durable conclusions from, so a silent skip is not survivable: losing a
/// 1101 voids every market-data request server-side yet leaves the client's
/// subscriptions looking healthy forever, and losing a restoration notice
/// after a recorded 1100 holds every pending reopen forever. On receiving
/// this notice the stream's conclusions are unknown — any of them may rest
/// on notices that were dropped — so the consumer must resynchronize rather
/// than resume: treat recorded state as describing an unknown moment between
/// the eviction and now, and re-derive it (a connection-state authority
/// re-baselines its link state and re-establishes subscriptions). This is
/// the notice-stream instance of [`SUBSCRIPTION_LAG_CODE`], closing the
/// step-1 leftover of #779. The sync
/// notice fan-out is unbounded and cannot lag; it logs a warning at every
/// 10,000 unread notices instead.
pub const NOTICE_STREAM_LAG_CODE: i32 = -7;
/// Build the in-band notice delivered when the notice stream's consumer fell
/// behind the notice fan-out and `skipped` notices were evicted, and emit the
/// matching `warn!`. The single owner of the notice-stream lag wording; see
/// [`NOTICE_STREAM_LAG_CODE`].
pub
/// Synthesized notice code published to the notice stream (sync and async)
/// after the transport finishes reconnecting its socket to TWS/Gateway: a new
/// connection whose server-side state starts empty, so every request
/// subscription made on the previous connection is gone, and unrouted-notice
/// state — notably the 1100/1101/1102 connection-status sequence — describes
/// the previous connection only.
///
/// Negative, like the other client-synthesized notice codes; TWS itself only
/// uses codes 0 and up.
///
/// TWS never frames this event itself: 1101/1102 announce the restoration
/// *transition* to clients connected at that moment and are not replayed to a
/// client that connects afterwards, so a consumer that recorded 1100 ("TWS
/// lost its IB-server link") before a socket drop cannot learn from the new
/// connection that the link is back. On receiving this notice, treat every
/// fact gathered from the previous connection as describing that connection
/// alone: re-establish what the new session must provide (resubscribe
/// requests), and reset connection-state conclusions — a recorded 1100 — to
/// the fresh-connection baseline a new client starts from, then let the new
/// connection's own notices re-derive the link state. The market data type
/// set with `switch_market_data_type` needs no action: the reconnect re-sends
/// it before this notice.
///
/// Published once the reconnected session is live: the channel reset runs
/// before the reconnect, so a consumer may resubscribe from inside its handler
/// and land on the new session without racing either. Like the other
/// client-synthesized codes this classifies as [`NoticeCategory::Error`]
/// ("everything else"); consumers match the constant itself rather than the
/// category.
pub const TRANSPORT_RECONNECT_CODE: i32 = -8;
/// Build the [`TRANSPORT_RECONNECT_CODE`] notice. Emits no log line: the
/// transport already logs the reconnect. The single owner of the wording.
pub
/// Typed classification of a [`Notice`] by TWS error-code range.
///
/// Returned by [`Notice::category`]. Forms a disjoint partition over all
/// possible notices; when classifications overlap on the wire, the classifier
/// resolves overlap by **precedence**:
///
/// 1. [`Cancellation`](Self::Cancellation) — exact code 202.
/// 2. [`DataAdvisory`](Self::DataAdvisory) — [`DATA_ADVISORY_CODES`]. Ahead of
/// the ranges: 317 is inside [`ORDER_REJECTION_CODE_RANGE`] and 2188 inside
/// [`WARNING_CODE_RANGE`].
/// 3. [`Warning`](Self::Warning) — [`WARNING_CODE_RANGE`], code 399 with a `Warning:`
/// line, or code 0 (a frame whose `error_code` field was absent on the wire).
/// 4. [`SystemMessage`](Self::SystemMessage) — 1100, 1101, 1102, 1300.
/// 5. [`RequestError`](Self::RequestError) — [`REQUEST_ERROR_CODES`]. Ahead of
/// [`ORDER_REJECTION_CODE_RANGE`], which they sit inside.
/// 6. [`OrderRejection`](Self::OrderRejection) — [`ORDER_REJECTION_CODE_RANGE`],
/// excluding the cases above.
/// 7. [`Error`](Self::Error) — everything else.
///
/// Each `Notice::is_*` category predicate ([`Notice::is_cancellation`],
/// [`Notice::is_warning`], ...) is `category() == X` for its variant `X`, so the
/// predicates are disjoint too.
///
/// Marked `#[non_exhaustive]` so IBKR can introduce new code ranges without a
/// breaking release.
///
/// # Examples
///
/// ```no_run
/// use ibapi::{Notice, NoticeCategory};
/// # let notice: Notice = unimplemented!();
/// match notice.category() {
/// NoticeCategory::OrderRejection => eprintln!("rejected: {}", notice),
/// NoticeCategory::RequestError => eprintln!("failed: {}", notice),
/// NoticeCategory::Warning => eprintln!("warn: {}", notice),
/// NoticeCategory::Error => eprintln!("error: {}", notice),
/// _ => {}
/// }
/// ```
/// Connectivity sub-state of a data-farm notice within [`WARNING_CODE_RANGE`].
///
/// Returned by [`Notice::connectivity_status`] (and [`ConnectivityStatus::from_code`]
/// for a raw code) for the data-farm status codes; `None` for every other notice.
/// Lets reconnect/health logic tell "farm came back online" from "farm went
/// inactive" without re-parsing codes. Additive to [`NoticeCategory`], which still
/// classifies all of [`WARNING_CODE_RANGE`] as [`NoticeCategory::Warning`]
/// (bar the advisory 2188).
///
/// The code→state vocabulary follows IB's published Message Codes table:
///
/// | State | Codes | Wire meaning |
/// |---|---|---|
/// | [`Ok`](Self::Ok) | 2104, 2106, 2158 | …data farm connection is OK |
/// | [`Broken`](Self::Broken) | 2103, 2105, 2157 | …data farm connection is broken |
/// | [`Inactive`](Self::Inactive) | 2107, 2108 | …connection is inactive but should be available upon demand |
/// | [`Connecting`](Self::Connecting) | 2119 | …data farm is connecting |
///
/// The variant set is closed: an unrecognized code classifies as `None`
/// (via [`ConnectivityStatus::from_code`]), not a new variant — so callers can
/// match exhaustively and handle unknowns through the `Option`.
///
/// # Examples
///
/// ```no_run
/// use ibapi::{Notice, ConnectivityStatus};
/// # let notice: Notice = unimplemented!();
/// match notice.connectivity_status() {
/// Some(ConnectivityStatus::Ok) => {/* farm healthy */}
/// Some(ConnectivityStatus::Broken) => {/* link down */}
/// Some(ConnectivityStatus::Inactive) => {/* dormant, available on demand */}
/// Some(ConnectivityStatus::Connecting) => {/* reconnecting */}
/// None => {/* not a data-farm notice */}
/// }
/// ```