apple_quant_algorithmic/backends/
databento.rs1use std::{marker::PhantomData, range::Range};
2
3use bevy::log::error;
4use databento::{
5 DateTimeLike,
6 dbn::{FIXED_PRICE_SCALE, RecordRef, RecordRefEnum, Schema, Side, decode::AsyncDbnDecoder},
7};
8use smallstr::SmallString;
9use time::{Duration, OffsetDateTime, UtcDateTime, UtcOffset};
10use tokio::{fs::File, io::BufReader, time::sleep};
11
12use crate::{
13 aggregation::{Trade, TradeTradeTimestamp},
14 backend::{DataBackend, OrderIdGenerator, OrdersBackend, RealtimeDataBackend},
15 instrument::{InstrumentData, InstrumentSpec, InstrumentTicker, IsFloatingPoint, XSpec},
16 order::{ActiveOrderGoals, ActiveStateGoal, DeferredOrderActions},
17 order_manager::{OrderManager, OrdersCapacitySpec},
18 price::{AbsolutePrice, FromCorrectedPrice},
19 schema::SchemaFlags,
20 strategy::Strategy,
21 timestamp::{TickTimestamp, Timestamp, Timestamped, TradeTimestamp, TradeTimestamped},
22 volume::{
23 AggressiveVolume, AggressorSide, DirectionalExposure, DirectionlessVolume,
24 FromCorrectedVolume,
25 },
26};
27
28mod historical;
29mod live;
30mod symbology;
31
32use historical::*;
33use live::*;
34use symbology::*;
35
36impl From<&Schema> for SchemaFlags {
37 fn from(value: &Schema) -> Self {
38 match value {
39 Schema::Trades => SchemaFlags::Trades,
44 _ => SchemaFlags::empty(),
47 }
48 }
49}
50
51impl From<Schema> for SchemaFlags {
52 fn from(value: Schema) -> Self {
53 Self::from(&value)
54 }
55}
56
57pub(crate) fn schema_value(schema: &Schema) -> u8 {
58 match schema {
59 Schema::Mbo => 8,
60 Schema::Mbp10 => 7,
61 Schema::Mbp1 => 6,
62 Schema::Tbbo => 5,
63 Schema::Tcbbo => 4,
64 Schema::Trades => 3,
65 Schema::Ohlcv1S => 2,
66 Schema::Ohlcv1M => 1,
67 _ => 0,
68 }
69}
70
71impl DateTimeLike for Timestamp {
72 fn to_date_time(self) -> OffsetDateTime {
73 OffsetDateTime::from_unix_timestamp_nanos(self.as_utc_nanos()).unwrap()
74 }
75}
76
77pub struct Databento<IS: InstrumentSpec + Send, OB: OrdersBackend<IS>> {
78 key: Option<SmallString<[u8; 64]>>,
79
80 realtime: Option<DatabentoLive>,
81 historical: Option<DatabentoHistorical>,
82
83 _is: PhantomData<IS>,
84 _ob: PhantomData<OB>,
85}
86
87impl<IS: InstrumentSpec + Send, OB: OrdersBackend<IS>> Databento<IS, OB> {
88 pub fn new(key: Option<&str>) -> Self {
89 Self {
90 key: key.map(|key| SmallString::from_str(key)),
91
92 realtime: None,
93 historical: None,
94
95 _is: PhantomData::default(),
96 _ob: PhantomData::default(),
97 }
98 }
99
100 async fn new_realtime(
101 &mut self,
102 instrument_ticker: &InstrumentTicker,
103 start_timestamp: impl DateTimeLike,
104 ) -> Result<&mut DatabentoLive, ()> {
105 if let Some(mut realtime_databento) = self.realtime.take() {
106 let _ = realtime_databento
107 .client
108 .close()
109 .await;
110 }
111
112 let Some(key) = &self.key else {
113 return Err(());
114 };
115
116 let Ok((realtime_databento, _schema_flags)) = DatabentoLive::new(
117 instrument_ticker,
118 start_timestamp,
119 &[Schema::Trades],
120 key.as_str(),
121 )
122 .await
123 else {
124 return Err(());
125 };
126
127 unsafe {
128 self.realtime = Some(realtime_databento);
129
130 Ok(self
131 .realtime
132 .as_mut()
133 .unwrap_unchecked())
134 }
135 }
136
137 async fn current_realtime(&mut self) -> Option<&mut DatabentoLive> {
138 self.realtime.as_mut()
139 }
140
141 async fn initialize_historical(&mut self) -> Result<&mut DatabentoHistorical, ()> {
142 let Some(key) = &self.key else {
143 return Err(());
144 };
145
146 self.historical = Some(DatabentoHistorical::new(
147 key.as_str(),
148 ));
149
150 unsafe {
151 Ok(self
152 .historical
153 .as_mut()
154 .unwrap_unchecked())
155 }
156 }
157
158 async fn historical(&mut self) -> Result<&mut DatabentoHistorical, ()> {
159 if self.historical.is_none() {
160 return self
161 .initialize_historical()
162 .await;
163 }
164
165 let Some(databento_historical) = self.historical.as_mut() else {
166 return Err(());
167 };
168
169 Ok(databento_historical)
170 }
171
172 const HISTORICAL_LIVE_OVERLAP_SHORT: Duration = Duration::seconds(5);
173 const HISTORICAL_LIVE_OVERLAP_LONG: Duration = Duration::minutes(10);
174
175 async fn fetch_once(
176 &mut self,
177 instrument_ticker: &InstrumentTicker,
178 utc_date_time_range: Range<UtcDateTime>,
179 ) -> impl IntoIterator<Item = TradeTradeTimestamp<IS>> {
180 debug_assert!(utc_date_time_range.start <= utc_date_time_range.end);
181
182 let now = UtcDateTime::now();
183 let exact_cutoff = now - Duration::hours(24);
184
185 let live_safe_cutoff = exact_cutoff + Self::HISTORICAL_LIVE_OVERLAP_SHORT;
186 let historical_safe_cutoff = exact_cutoff - Self::HISTORICAL_LIVE_OVERLAP_SHORT;
187
188 debug_assert!(utc_date_time_range.end < (now + Duration::seconds(5)));
189
190 let mut market_data = Vec::with_capacity(100_000);
191
192 if utc_date_time_range.start <= historical_safe_cutoff
194 && utc_date_time_range.end <= historical_safe_cutoff
195 {
196 let historical_databento = self
197 .historical()
198 .await
199 .unwrap();
200
201 let mut stream = historical_databento
202 .stream(
203 instrument_ticker,
204 utc_date_time_range,
205 )
206 .await
207 .unwrap();
208
209 while let Ok(Some(record_ref)) = stream
210 .decode_record_ref()
211 .await
212 {
213 let Some(trade_trade_timestamp) = process_record_trades(record_ref) else {
214 continue;
215 };
216
217 market_data.push(trade_trade_timestamp);
218 }
219
220 return market_data;
221 }
222
223 if utc_date_time_range.start >= live_safe_cutoff
225 && utc_date_time_range.end >= live_safe_cutoff
226 {
227 let realtime_databento = self
228 .new_realtime(
229 instrument_ticker,
230 utc_date_time_range
231 .start
232 .to_offset(UtcOffset::UTC),
233 )
234 .await
235 .unwrap();
236
237 while let Ok(Some(record_ref)) = realtime_databento
238 .client
239 .next_record()
240 .await
241 {
242 let Some(trade_trade_timestamp) = process_record_trades(record_ref) else {
243 continue;
244 };
245
246 let trade_date_time = trade_trade_timestamp
247 .trade_timestamp()
248 .to_date_time()
249 .to_utc();
250
251 if trade_date_time >= utc_date_time_range.end {
252 break;
253 }
254
255 market_data.push(trade_trade_timestamp);
256 }
257
258 let _ = realtime_databento
259 .client
260 .close()
261 .await;
262
263 self.realtime = None;
264
265 return market_data;
266 }
267
268 let historical_start = if utc_date_time_range.start < historical_safe_cutoff {
269 utc_date_time_range.start
270 } else {
271 historical_safe_cutoff
272 };
273
274 let realtime_end = if utc_date_time_range.end > live_safe_cutoff {
275 utc_date_time_range.end
276 } else {
277 live_safe_cutoff
278 };
279
280 let realtime_databento = self
281 .new_realtime(
282 instrument_ticker,
283 OffsetDateTime::UNIX_EPOCH,
284 )
285 .await
286 .unwrap();
287
288 let mut realtime_satisfied_start = false;
289
290 while let Ok(Some(record_ref)) = realtime_databento
291 .client
292 .next_record()
293 .await
294 {
295 let Some(trade_trade_timestamp) = process_record_trades(record_ref) else {
296 continue;
297 };
298
299 let trade_date_time = trade_trade_timestamp
300 .trade_timestamp()
301 .to_date_time()
302 .to_utc();
303
304 if trade_date_time >= realtime_end {
305 break;
306 }
307
308 if trade_date_time < historical_start {
309 realtime_satisfied_start = true;
310 continue;
311 }
312
313 market_data.push(trade_trade_timestamp);
314 }
315
316 let _ = realtime_databento
317 .client
318 .close()
319 .await;
320
321 self.realtime = None;
322
323 if realtime_satisfied_start {
324 return market_data;
325 }
326
327 let first_live_timestamp = market_data
328 .first()
329 .unwrap()
330 .trade_timestamp()
331 .to_date_time()
332 .to_utc();
333
334 debug_assert!(first_live_timestamp < live_safe_cutoff);
335
336 let historical_databento = self
337 .historical()
338 .await
339 .unwrap();
340
341 let start_utc = UtcDateTime::now();
342
343 while UtcDateTime::now() - start_utc < Duration::seconds(30) {
344 let available_end = historical_databento
345 .available_end()
346 .await
347 .unwrap();
348
349 if available_end > first_live_timestamp {
350 break;
351 }
352
353 sleep(std::time::Duration::from_millis(100)).await;
354 }
355
356 let mut historical_market_data = Vec::with_capacity(10_000);
357
358 let mut stream = historical_databento
359 .stream(
360 instrument_ticker,
361 Range {
362 start: historical_start,
363 end: (first_live_timestamp + Duration::nanoseconds(1)),
364 },
365 )
366 .await
367 .unwrap();
368
369 while let Ok(Some(record_ref)) = stream
370 .decode_record_ref()
371 .await
372 {
373 let Some(trade_trade_timestamp) = process_record_trades(record_ref) else {
374 continue;
375 };
376
377 historical_market_data.push(trade_trade_timestamp);
378 }
379
380 let inclusive_start_live_idx = market_data
381 .iter()
382 .position(|trade_trade_timestamp| {
383 trade_trade_timestamp
384 .trade_timestamp()
385 .to_date_time()
386 .to_utc() > first_live_timestamp
387 })
388 .unwrap();
389
390 historical_market_data.extend(
391 market_data
392 .into_iter()
393 .skip(inclusive_start_live_idx),
394 );
395
396 historical_market_data
397 }
398}
399
400impl<
401 IS: InstrumentSpec + Send,
402 OB: OrdersBackend<IS>,
403 OrdersCS: OrdersCapacitySpec,
404 S: Strategy<IS, OB, OrdersCS> + Send,
405> RealtimeDataBackend<IS, OB, OrdersCS, S> for Databento<IS, OB>
406{
407 async fn initialize_realtime(
408 &mut self,
409 instrument_ticker: &InstrumentTicker,
410 recent_trade_timestamp: Option<&TradeTimestamp>,
411 ) {
412 let start_timestamp = recent_trade_timestamp
413 .map(|trade_timestamp| trade_timestamp.to_date_time())
414 .unwrap_or_else(|| OffsetDateTime::now_utc());
415
416 let _ = self
417 .new_realtime(
418 instrument_ticker,
419 start_timestamp,
420 )
421 .await;
422 }
423
424 async fn poll_realtime<'instrument_data, 'aggregated_data>(
425 &mut self,
426 instrument_data: &'instrument_data mut InstrumentData<
427 'instrument_data,
428 'aggregated_data,
429 IS,
430 >,
431 strategy: &mut S,
432 order_manager: &OrderManager<IS, OrdersCS>,
433 directional_exposure: &DirectionalExposure<IS>,
434 active_order_goals: &ActiveOrderGoals<IS>,
435 active_state_goal: &ActiveStateGoal,
436 deferred_order_actions: &mut DeferredOrderActions<IS>,
437 order_id_generator: &mut OrderIdGenerator,
438 recent_trade_timestamp: Option<&TradeTimestamp>,
439 ) {
440 let start_recent_trade_timestamp = recent_trade_timestamp.cloned();
441 let mut last_recent_trade_timestamp = recent_trade_timestamp.cloned();
442
443 #[rustfmt::skip]
444 let Some(
445 realtime_databento,
446 ) = &mut self.realtime else {
447 return;
448 };
449
450 loop {
451 let next_record = realtime_databento
452 .client
453 .try_next_record();
454
455 let record_ref = match next_record {
456 Err(error) => {
457 error!("{error}");
458 break;
459 }
460 Ok(record_ref) => record_ref,
461 };
462
463 if let Some(record_ref) = record_ref {
464 let Some(trade_trade_timestamp) = process_record_trades::<IS>(record_ref) else {
465 continue;
466 };
467
468 last_recent_trade_timestamp = Some(*trade_trade_timestamp.trade_timestamp());
469 instrument_data.new_trades([trade_trade_timestamp].into_iter());
470
471 continue;
472 }
473
474 let size = match realtime_databento
475 .client
476 .fill_buf()
477 .await
478 {
479 Err(error) => {
480 error!("{error}");
481 break;
482 }
483 Ok(size) => size,
484 };
485
486 if size == 0 {
487 break;
488 }
489
490 if realtime_databento
491 .client
492 .is_closed()
493 {
494 unimplemented!();
495 }
496 }
497
498 let Some(last_recent_trade_timestamp) = last_recent_trade_timestamp else {
499 return;
500 };
501
502 let start_recent_trade_timestamp =
503 start_recent_trade_timestamp.unwrap_or(last_recent_trade_timestamp);
504
505 let tick_timestamp = TickTimestamp::new(*start_recent_trade_timestamp.timestamp());
506
507 strategy
508 .data_update(
509 &tick_timestamp,
510 instrument_data,
511 order_manager,
512 directional_exposure,
513 active_order_goals,
514 active_state_goal,
515 deferred_order_actions,
516 order_id_generator,
517 )
518 .await;
519 }
520}
521
522impl<IS: InstrumentSpec + Send, OB: OrdersBackend<IS> + Send> DataBackend<IS>
523 for Databento<IS, OB>
524{
525 fn new(key: Option<&str>) -> Self {
526 Databento::new(key)
527 }
528
529 fn file_name_postpend() -> &'static str {
530 ".dbn"
531 }
532
533 async fn stream_in(
534 &mut self,
535 buf_reader: BufReader<File>,
536 ) -> impl ExactSizeIterator<Item = TradeTradeTimestamp<IS>> {
537 let mut decoder = AsyncDbnDecoder::new(buf_reader)
538 .await
539 .unwrap();
540
541 let mut trade_trade_timestamps: Vec<TradeTradeTimestamp<IS>> =
542 Vec::with_capacity(1_000_000);
543
544 while let Some(record_ref) = decoder
545 .decode_record_ref()
546 .await
547 .unwrap()
548 {
549 let Some(trade_trade_timestamp) = process_record_trades(record_ref) else {
550 continue;
551 };
552
553 trade_trade_timestamps.push(trade_trade_timestamp);
554 }
555
556 trade_trade_timestamps.into_iter()
557 }
558}
559
560fn process_record_trades<IS: InstrumentSpec>(
561 record_ref: RecordRef
562) -> Option<TradeTradeTimestamp<IS>> {
563 match record_ref.as_enum().unwrap() {
564 RecordRefEnum::Mbo(mbo_msg) => unimplemented!(),
565 RecordRefEnum::Mbp10(msg) => unimplemented!(),
566 RecordRefEnum::Mbp1(msg) => unimplemented!(),
567 RecordRefEnum::Bbo(msg) => unimplemented!(),
568 RecordRefEnum::Cbbo(msg) => unimplemented!(),
569 RecordRefEnum::Trade(trade_msg) => {
570 let Ok(side) = trade_msg.side() else {
571 return None;
572 };
573
574 let aggressor_side = match side {
575 Side::Ask => AggressorSide::Ask,
576 Side::Bid => AggressorSide::Bid,
577 Side::None => return None,
578 };
579
580 let trade_trade_timestamp = create_trade_trade_timestamp::<IS>(
581 trade_msg.ts_recv,
582 trade_msg.price,
583 trade_msg.size,
584 aggressor_side,
585 );
586
587 Some(trade_trade_timestamp)
588 }
589 RecordRefEnum::Ohlcv(msg) => unimplemented!(),
590 _ => None,
591 }
592}
593
594fn create_trade_trade_timestamp<IS: InstrumentSpec>(
595 ts_recv: u64,
596 price: i64,
597 volume: u32,
598 aggressor_side: AggressorSide,
599) -> TradeTradeTimestamp<IS> {
600 let trade_timestamp = TradeTimestamp::new(Timestamp::new(
601 ts_recv as i128,
602 ));
603
604 let price = if IS::PriceType::IS_FLOATING_POINT {
605 price_float_ticks::<IS>(price)
606 } else {
607 price_integer_ticks::<IS>(price)
608 };
609
610 let volume = AggressiveVolume::new(
611 DirectionlessVolume::new_checked(IS::VolumeType::from_u64(
612 volume as u64,
613 ))
614 .unwrap(),
615 aggressor_side,
616 );
617
618 let trade = Trade {
619 price,
620 aggressive_volume: volume,
621 };
622
623 TradeTradeTimestamp::new(trade, trade_timestamp)
624}
625
626#[inline]
627fn price_integer_ticks<IS: InstrumentSpec>(price: i64) -> AbsolutePrice<IS> {
628 let price_scale_ticks = FIXED_PRICE_SCALE / IS::PriceSpec::TICKS_PER_POINT as i64;
629 let price = IS::PriceType::from_i64(price / price_scale_ticks);
630
631 AbsolutePrice::new(price)
632}
633
634#[inline]
635fn price_float_ticks<IS: InstrumentSpec>(price: i64) -> AbsolutePrice<IS> {
636 let price_scale_ticks = FIXED_PRICE_SCALE as f64 / IS::PriceSpec::TICKS_PER_POINT as f64;
637 let price = IS::PriceType::from_f64(price as f64 / price_scale_ticks);
638
639 AbsolutePrice::new(price)
640}