1mod sink;
9
10pub(crate) use sink::TradeBars;
11
12pub use sink::{Sink, aggregate_trades, aggregate_trades_with_tick_size};
13
14use super::depth::Book;
15use super::depth_history::{DepthColumn, DepthHistory, DepthHistoryConfig, Sampler};
16use super::merge::Origin;
17use super::trades::Trades;
18use super::{FeedError, FeedStatus, OrderQueue, SubscriptionId};
19use crate::model::{Bar, BarClock, Level1Quote, OrderBook};
20use chrono::{DateTime, TimeDelta, Utc};
21use std::any::Any;
22use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
23use std::marker::PhantomData;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
26
27pub(crate) type Answer = Box<dyn Any + Send>;
29
30#[derive(Default)]
32pub(crate) struct Inbox {
33 state: Mutex<State>,
34 waker: OnceLock<Box<dyn Fn() + Send + Sync>>,
35}
36
37#[derive(Default)]
38struct State {
39 woken: bool,
41 answers: Vec<(u64, Answer)>,
42 streamed: HashMap<u64, usize>,
44 subscriptions: HashMap<SubscriptionId, Live>,
45}
46
47struct Live {
48 symbol: String,
49 clock: BarClock,
50 forming: Option<DateTime<Utc>>,
52 offset: TimeDelta,
54 count_bar: Option<(Bar, u64)>,
55 delta: f64,
56 event_closed: bool,
57 last_trade: Option<DateTime<Utc>>,
58 last_close: Option<f64>,
59 empty: bool,
60 book: Book,
61 orders: Option<OrderQueue>,
62 orders_changed: bool,
63 tick_size: Option<f64>,
64 sampler: Option<Sampler>,
66 study_inputs: Vec<crate::data::OrderFlowInput>,
67 taken: TakenIds,
68 updates: Updates,
69}
70
71struct TakenIds {
74 order: VecDeque<u64>,
75 ids: HashSet<u64>,
76 capacity: usize,
77}
78
79pub(crate) struct Drained {
81 pub(crate) answers: Vec<(u64, Answer)>,
83 pub(crate) updates: Vec<(SubscriptionId, Updates)>,
84}
85
86pub(crate) struct Updates {
88 pub(crate) reset: bool,
90 pub(crate) bars: BTreeMap<DateTime<Utc>, (Bar, Origin)>,
92 pub(crate) trades: Trades,
94 pub(crate) depth: Option<OrderBook>,
96 pub(crate) orders: Option<OrderQueue>,
97 pub(crate) orders_changed: bool,
98 pub(crate) depth_columns: VecDeque<DepthColumn>,
101 pub(crate) replay_depth: Option<DepthHistory>,
103 pub(crate) quote: Option<Level1Quote>,
105 pub(crate) time: Option<DateTime<Utc>>,
107 pub(crate) resnapshot: bool,
109 pub(crate) symbols_changed: bool,
110 pub(crate) status: Option<FeedStatus>,
111 pub(crate) error: Option<FeedError>,
112 pub(crate) late_ticks: u64,
114 pub(crate) repeated_ticks: u64,
116 pub(crate) rejected: u64,
119 dropped_bars: u64,
121 pub(crate) rejected_depth: u64,
123}
124
125impl Updates {
126 fn new(trades: Trades) -> Self {
127 Self {
128 reset: false,
129 bars: BTreeMap::new(),
130 trades,
131 depth: None,
132 orders: None,
133 orders_changed: false,
134 depth_columns: VecDeque::new(),
135 replay_depth: None,
136 quote: None,
137 time: None,
138 resnapshot: false,
139 symbols_changed: false,
140 status: None,
141 error: None,
142 late_ticks: 0,
143 repeated_ticks: 0,
144 rejected: 0,
145 dropped_bars: 0,
146 rejected_depth: 0,
147 }
148 }
149}
150
151impl Inbox {
152 #[cfg(feature = "widget")]
154 pub(crate) fn server_time(&self, id: SubscriptionId) -> Option<DateTime<Utc>> {
155 Utc::now().checked_add_signed(self.state().subscriptions.get(&id)?.offset)
156 }
157
158 pub(crate) fn set_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
160 let _ = self.waker.set(Box::new(waker));
161 }
162
163 pub(crate) fn reply<T: Send + 'static>(self: &Arc<Self>, request: u64) -> Reply<T> {
165 Reply {
166 inbox: Arc::clone(self),
167 request,
168 sent: false,
169 answer: PhantomData,
170 }
171 }
172
173 pub(crate) fn sample_depth(&self, id: SubscriptionId, config: Option<DepthHistoryConfig>) {
176 if let Some(live) = self.state().subscriptions.get_mut(&id) {
177 live.sampler = config.map(Sampler::new);
178 live.updates.depth_columns.clear();
179 live.updates.replay_depth = None;
180 }
181 }
182
183 #[cfg(any(feature = "widget", feature = "alert-engine"))]
185 pub(crate) fn study_inputs(
186 &self,
187 id: SubscriptionId,
188 inputs: Vec<crate::data::OrderFlowInput>,
189 ) {
190 if let Some(live) = self.state().subscriptions.get_mut(&id) {
191 live.connect_study_inputs(inputs);
192 }
193 }
194
195 pub(crate) fn unsubscribe(&self, id: SubscriptionId) {
197 self.state().subscriptions.remove(&id);
198 }
199
200 pub(crate) fn drain(&self) -> Drained {
202 let mut state = self.state();
203 state.woken = false;
204 let answers = std::mem::take(&mut state.answers);
205 state.streamed.clear();
206 let updates = state
207 .subscriptions
208 .iter_mut()
209 .map(|(id, live)| {
210 let next = Updates::new(live.updates.trades.empty());
211 let mut updates = std::mem::replace(&mut live.updates, next);
212 if updates.dropped_bars > 0 {
213 updates.rejected += updates.dropped_bars;
214 updates.error = Some(FeedError::InvalidData(format!(
215 "pending bar limit exceeded: {} bars dropped; reload history",
216 updates.dropped_bars,
217 )));
218 }
219 updates.depth = live.book.take_changed();
220 updates.orders_changed = std::mem::take(&mut live.orders_changed);
221 if updates.orders_changed {
222 updates.orders = live.orders.clone();
223 }
224 (*id, updates)
225 })
226 .collect();
227 Drained { answers, updates }
228 }
229
230 fn state(&self) -> MutexGuard<'_, State> {
231 self.state.lock().unwrap_or_else(PoisonError::into_inner)
233 }
234
235 fn push(&self, push: impl FnOnce(&mut State) -> bool) {
237 let wake = {
238 let mut state = self.state();
239 push(&mut state) && !std::mem::replace(&mut state.woken, true)
240 };
241 if wake && let Some(waker) = self.waker.get() {
242 waker();
243 }
244 }
245
246 fn answer<T: Send + 'static>(&self, request: u64, result: Result<T, FeedError>) {
247 self.push(|state| {
248 state.answers.push((request, Box::new(result)));
249 true
250 });
251 }
252
253 pub(crate) fn push_answer(&self, key: u64, event: Answer, active: &AtomicBool) {
256 self.push(|state| {
257 if !active.load(Ordering::Relaxed) {
258 return false;
259 }
260 let queued = state.streamed.entry(key).or_default();
261 if *queued == STREAM_BOUND {
262 active.store(false, Ordering::Relaxed);
263 return false;
264 }
265 *queued += 1;
266 state.answers.push((key, event));
267 true
268 });
269 }
270}
271
272const STREAM_BOUND: usize = 10_000;
274
275#[must_use = "answer the request with Reply::send"]
278pub struct Reply<T: Send + 'static> {
279 inbox: Arc<Inbox>,
280 request: u64,
281 sent: bool,
282 answer: PhantomData<fn(T)>,
283}
284
285impl<T: Send + 'static> Reply<T> {
286 pub fn send(mut self, result: Result<T, FeedError>) {
288 self.sent = true;
289 self.inbox.answer(self.request, result);
290 }
291}
292
293impl<T: Send + 'static> Drop for Reply<T> {
294 fn drop(&mut self) {
295 if !self.sent {
296 self.inbox
297 .answer(self.request, Err::<T, _>(FeedError::Unanswered));
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests;