Skip to main content

apple_quant_algorithmic/backends/
databento.rs

1use 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			// Self::Mbo => SchemaFlags::AddCancelModifyFillClear | SchemaFlags::Trades,
40			// Self::Mbp10 => SchemaFlags::LimitedAddCancelModifyFillClear | SchemaFlags::Trades,
41			// Self::Mbp1 => SchemaFlags::LimitedAddCancelModifyFillClear | SchemaFlags::Trades,
42			// Self::Tbbo => SchemaFlags::Trades,
43			Schema::Trades => SchemaFlags::Trades,
44			// Self::Ohlcv1S => SchemaFlags::OHLC1s,
45			// Self::Ohlcv1M => SchemaFlags::OHLC1m,
46			_ => 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		// Historical client only.
193		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		// Live client only.
224		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}