nautilus_coinbase_intx/common/
parse.rs1use std::str::FromStr;
17
18use nautilus_core::{datetime::NANOSECONDS_IN_MILLISECOND, nanos::UnixNanos};
19use nautilus_model::{
20 data::{
21 BarSpecification,
22 bar::{
23 BAR_SPEC_1_DAY_LAST, BAR_SPEC_1_MINUTE_LAST, BAR_SPEC_2_HOUR_LAST,
24 BAR_SPEC_5_MINUTE_LAST, BAR_SPEC_30_MINUTE_LAST,
25 },
26 },
27 enums::{AggressorSide, LiquiditySide, PositionSide},
28 identifiers::{InstrumentId, Symbol},
29 types::{Currency, Money, Price, Quantity},
30};
31use serde::{Deserialize, Deserializer};
32use ustr::Ustr;
33
34use crate::{
35 common::{
36 consts::COINBASE_INTX_VENUE,
37 enums::{CoinbaseIntxExecType, CoinbaseIntxSide},
38 },
39 websocket::enums::CoinbaseIntxWsChannel,
40};
41
42pub fn deserialize_optional_string_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
48where
49 D: Deserializer<'de>,
50{
51 let s: Option<String> = Option::deserialize(deserializer)?;
52 match s {
53 Some(s) if s.is_empty() => Ok(None),
54 Some(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
55 None => Ok(None),
56 }
57}
58
59pub fn get_currency(code: &str) -> Currency {
64 Currency::get_or_create_crypto(code)
65}
66
67#[must_use]
69pub fn parse_instrument_id(symbol: Ustr) -> InstrumentId {
70 InstrumentId::new(Symbol::from_ustr_unchecked(symbol), *COINBASE_INTX_VENUE)
71}
72
73pub fn parse_millisecond_timestamp(timestamp: &str) -> anyhow::Result<UnixNanos> {
79 let millis: u64 = timestamp.parse()?;
80 Ok(UnixNanos::from(millis * NANOSECONDS_IN_MILLISECOND))
81}
82
83pub fn parse_rfc3339_timestamp(timestamp: &str) -> anyhow::Result<UnixNanos> {
89 let dt = chrono::DateTime::parse_from_rfc3339(timestamp)?;
90 let nanos = dt
91 .timestamp_nanos_opt()
92 .ok_or_else(|| anyhow::anyhow!("RFC3339 timestamp out of range: {timestamp}"))?;
93 Ok(UnixNanos::from(nanos as u64))
94}
95
96pub fn parse_price(value: &str) -> anyhow::Result<Price> {
102 Price::from_str(value).map_err(|e| anyhow::anyhow!(e))
103}
104
105pub fn parse_quantity(value: &str, precision: u8) -> anyhow::Result<Quantity> {
111 Quantity::new_checked(value.parse::<f64>()?, precision)
112}
113
114pub fn parse_notional(value: &str, currency: Currency) -> anyhow::Result<Option<Money>> {
120 let parsed = value.trim().parse::<f64>()?;
121 Ok(if parsed == 0.0 {
122 None
123 } else {
124 Some(Money::new(parsed, currency))
125 })
126}
127
128#[must_use]
129pub const fn parse_aggressor_side(side: &Option<CoinbaseIntxSide>) -> AggressorSide {
130 match side {
131 Some(CoinbaseIntxSide::Buy) => nautilus_model::enums::AggressorSide::Buyer,
132 Some(CoinbaseIntxSide::Sell) => nautilus_model::enums::AggressorSide::Seller,
133 None => nautilus_model::enums::AggressorSide::NoAggressor,
134 }
135}
136
137#[must_use]
138pub const fn parse_execution_type(liquidity: &Option<CoinbaseIntxExecType>) -> LiquiditySide {
139 match liquidity {
140 Some(CoinbaseIntxExecType::Maker) => nautilus_model::enums::LiquiditySide::Maker,
141 Some(CoinbaseIntxExecType::Taker) => nautilus_model::enums::LiquiditySide::Taker,
142 _ => nautilus_model::enums::LiquiditySide::NoLiquiditySide,
143 }
144}
145
146#[must_use]
147pub const fn parse_position_side(current_qty: Option<f64>) -> PositionSide {
148 match current_qty {
149 Some(qty) if qty.is_sign_positive() => PositionSide::Long,
150 Some(qty) if qty.is_sign_negative() => PositionSide::Short,
151 _ => PositionSide::Flat,
152 }
153}
154
155pub fn bar_spec_as_coinbase_channel(
161 bar_spec: BarSpecification,
162) -> anyhow::Result<CoinbaseIntxWsChannel> {
163 let channel = match bar_spec {
164 BAR_SPEC_1_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesOneMinute,
165 BAR_SPEC_5_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesFiveMinute,
166 BAR_SPEC_30_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesThirtyMinute,
167 BAR_SPEC_2_HOUR_LAST => CoinbaseIntxWsChannel::CandlesTwoHour,
168 BAR_SPEC_1_DAY_LAST => CoinbaseIntxWsChannel::CandlesOneDay,
169 _ => anyhow::bail!("Invalid `BarSpecification` for channel, was {bar_spec}"),
170 };
171 Ok(channel)
172}
173
174pub fn coinbase_channel_as_bar_spec(
180 channel: &CoinbaseIntxWsChannel,
181) -> anyhow::Result<BarSpecification> {
182 let bar_spec = match channel {
183 CoinbaseIntxWsChannel::CandlesOneMinute => BAR_SPEC_1_MINUTE_LAST,
184 CoinbaseIntxWsChannel::CandlesFiveMinute => BAR_SPEC_5_MINUTE_LAST,
185 CoinbaseIntxWsChannel::CandlesThirtyMinute => BAR_SPEC_30_MINUTE_LAST,
186 CoinbaseIntxWsChannel::CandlesTwoHour => BAR_SPEC_2_HOUR_LAST,
187 CoinbaseIntxWsChannel::CandlesOneDay => BAR_SPEC_1_DAY_LAST,
188 _ => anyhow::bail!("Invalid channel for `BarSpecification`, was {channel}"),
189 };
190 Ok(bar_spec)
191}