nautilus_coinbase_intx/common/
parse.rs1use std::str::FromStr;
17
18use nautilus_core::{datetime::NANOSECONDS_IN_MILLISECOND, nanos::UnixNanos};
19use nautilus_model::{
20 currencies::CURRENCY_MAP,
21 data::{
22 BarSpecification,
23 bar::{
24 BAR_SPEC_1_DAY_LAST, BAR_SPEC_1_MINUTE_LAST, BAR_SPEC_2_HOUR_LAST,
25 BAR_SPEC_5_MINUTE_LAST, BAR_SPEC_30_MINUTE_LAST,
26 },
27 },
28 enums::{AggressorSide, CurrencyType, LiquiditySide, OrderSide, PositionSide},
29 identifiers::{InstrumentId, Symbol},
30 types::{Currency, Money, Price, Quantity},
31};
32use serde::{Deserialize, Deserializer};
33use ustr::Ustr;
34
35use crate::{
36 common::{
37 consts::COINBASE_INTX_VENUE,
38 enums::{CoinbaseIntxExecType, CoinbaseIntxSide},
39 },
40 websocket::enums::CoinbaseIntxWsChannel,
41};
42
43pub fn deserialize_optional_string_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
49where
50 D: Deserializer<'de>,
51{
52 let s: Option<String> = Option::deserialize(deserializer)?;
53 match s {
54 Some(s) if s.is_empty() => Ok(None),
55 Some(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
56 None => Ok(None),
57 }
58}
59
60pub fn get_currency(code: &str) -> Currency {
67 CURRENCY_MAP
68 .lock()
69 .unwrap()
70 .get(code)
71 .copied()
72 .unwrap_or(Currency::new(code, 8, 0, code, CurrencyType::Crypto))
73}
74
75#[must_use]
77pub fn parse_instrument_id(symbol: Ustr) -> InstrumentId {
78 InstrumentId::new(Symbol::from_ustr_unchecked(symbol), *COINBASE_INTX_VENUE)
79}
80
81pub fn parse_millisecond_timestamp(timestamp: &str) -> anyhow::Result<UnixNanos> {
87 let millis: u64 = timestamp.parse()?;
88 Ok(UnixNanos::from(millis * NANOSECONDS_IN_MILLISECOND))
89}
90
91pub fn parse_rfc3339_timestamp(timestamp: &str) -> anyhow::Result<UnixNanos> {
97 let dt = chrono::DateTime::parse_from_rfc3339(timestamp)?;
98 let nanos = dt
99 .timestamp_nanos_opt()
100 .ok_or_else(|| anyhow::anyhow!("RFC3339 timestamp out of range: {timestamp}"))?;
101 Ok(UnixNanos::from(nanos as u64))
102}
103
104pub fn parse_price(value: &str) -> anyhow::Result<Price> {
110 Price::from_str(value).map_err(|e| anyhow::anyhow!(e))
111}
112
113pub fn parse_quantity(value: &str, precision: u8) -> anyhow::Result<Quantity> {
119 Quantity::new_checked(value.parse::<f64>()?, precision)
120}
121
122pub fn parse_notional(value: &str, currency: Currency) -> anyhow::Result<Option<Money>> {
128 let parsed = value.trim().parse::<f64>()?;
129 Ok(if parsed == 0.0 {
130 None
131 } else {
132 Some(Money::new(parsed, currency))
133 })
134}
135
136#[must_use]
137pub const fn parse_aggressor_side(side: &Option<CoinbaseIntxSide>) -> AggressorSide {
138 match side {
139 Some(CoinbaseIntxSide::Buy) => nautilus_model::enums::AggressorSide::Buyer,
140 Some(CoinbaseIntxSide::Sell) => nautilus_model::enums::AggressorSide::Seller,
141 None => nautilus_model::enums::AggressorSide::NoAggressor,
142 }
143}
144
145#[must_use]
146pub const fn parse_execution_type(liquidity: &Option<CoinbaseIntxExecType>) -> LiquiditySide {
147 match liquidity {
148 Some(CoinbaseIntxExecType::Maker) => nautilus_model::enums::LiquiditySide::Maker,
149 Some(CoinbaseIntxExecType::Taker) => nautilus_model::enums::LiquiditySide::Taker,
150 _ => nautilus_model::enums::LiquiditySide::NoLiquiditySide,
151 }
152}
153
154#[must_use]
155pub const fn parse_position_side(current_qty: Option<f64>) -> PositionSide {
156 match current_qty {
157 Some(qty) if qty.is_sign_positive() => PositionSide::Long,
158 Some(qty) if qty.is_sign_negative() => PositionSide::Short,
159 _ => PositionSide::Flat,
160 }
161}
162
163#[must_use]
164pub const fn parse_order_side(order_side: &Option<CoinbaseIntxSide>) -> OrderSide {
165 match order_side {
166 Some(CoinbaseIntxSide::Buy) => OrderSide::Buy,
167 Some(CoinbaseIntxSide::Sell) => OrderSide::Sell,
168 None => OrderSide::NoOrderSide,
169 }
170}
171
172pub fn bar_spec_as_coinbase_channel(
178 bar_spec: BarSpecification,
179) -> anyhow::Result<CoinbaseIntxWsChannel> {
180 let channel = match bar_spec {
181 BAR_SPEC_1_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesOneMinute,
182 BAR_SPEC_5_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesFiveMinute,
183 BAR_SPEC_30_MINUTE_LAST => CoinbaseIntxWsChannel::CandlesThirtyMinute,
184 BAR_SPEC_2_HOUR_LAST => CoinbaseIntxWsChannel::CandlesTwoHour,
185 BAR_SPEC_1_DAY_LAST => CoinbaseIntxWsChannel::CandlesOneDay,
186 _ => anyhow::bail!("Invalid `BarSpecification` for channel, was {bar_spec}"),
187 };
188 Ok(channel)
189}
190
191pub fn coinbase_channel_as_bar_spec(
197 channel: &CoinbaseIntxWsChannel,
198) -> anyhow::Result<BarSpecification> {
199 let bar_spec = match channel {
200 CoinbaseIntxWsChannel::CandlesOneMinute => BAR_SPEC_1_MINUTE_LAST,
201 CoinbaseIntxWsChannel::CandlesFiveMinute => BAR_SPEC_5_MINUTE_LAST,
202 CoinbaseIntxWsChannel::CandlesThirtyMinute => BAR_SPEC_30_MINUTE_LAST,
203 CoinbaseIntxWsChannel::CandlesTwoHour => BAR_SPEC_2_HOUR_LAST,
204 CoinbaseIntxWsChannel::CandlesOneDay => BAR_SPEC_1_DAY_LAST,
205 _ => anyhow::bail!("Invalid channel for `BarSpecification`, was {channel}"),
206 };
207 Ok(bar_spec)
208}