Skip to main content

nautilus_infrastructure/sql/models/
instruments.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16// Under development
17#![allow(dead_code)]
18#![allow(unused_variables)]
19
20use std::str::FromStr;
21
22use nautilus_core::{Params, UnixNanos};
23use nautilus_model::{
24    enums::{AssetClass, OptionKind},
25    identifiers::{InstrumentId, Symbol},
26    instruments::{
27        BettingInstrument, BinaryOption, Cfd, Commodity, CryptoFuture, CryptoFuturesSpread,
28        CryptoOption, CryptoOptionSpread, CryptoPerpetual, CurrencyPair, Equity, FuturesContract,
29        FuturesSpread, IndexInstrument, InstrumentAny, OptionContract, OptionSpread,
30        PerpetualContract, TokenizedAsset,
31    },
32    types::{Currency, Money, Price, Quantity},
33};
34use rust_decimal::Decimal;
35use sqlx::{FromRow, Row, postgres::PgRow, types::Json};
36use ustr::Ustr;
37
38use crate::sql::models::{enums::AssetClassPg, read_u8, read_u64};
39
40#[derive(Debug)]
41pub struct InstrumentAnyRow(pub InstrumentAny);
42
43#[derive(Debug)]
44pub struct BettingInstrumentRow(pub BettingInstrument);
45
46#[derive(Debug)]
47pub struct BinaryOptionRow(pub BinaryOption);
48
49#[derive(Debug)]
50pub struct CryptoFutureRow(pub CryptoFuture);
51
52#[derive(Debug)]
53pub struct CryptoOptionRow(pub CryptoOption);
54
55#[derive(Debug)]
56pub struct CryptoPerpetualRow(pub CryptoPerpetual);
57
58#[derive(Debug)]
59pub struct CurrencyPairRow(pub CurrencyPair);
60
61#[derive(Debug)]
62pub struct EquityRow(pub Equity);
63
64#[derive(Debug)]
65pub struct FuturesContractRow(pub FuturesContract);
66
67#[derive(Debug)]
68pub struct FuturesSpreadRow(pub FuturesSpread);
69
70#[derive(Debug)]
71pub struct OptionContractRow(pub OptionContract);
72
73#[derive(Debug)]
74pub struct CommodityRow(pub Commodity);
75
76#[derive(Debug)]
77pub struct IndexInstrumentRow(pub IndexInstrument);
78
79#[derive(Debug)]
80pub struct CfdRow(pub Cfd);
81
82#[derive(Debug)]
83pub struct PerpetualContractRow(pub PerpetualContract);
84
85#[derive(Debug)]
86pub struct OptionSpreadRow(pub OptionSpread);
87
88#[derive(Debug)]
89pub struct CryptoFuturesSpreadRow(pub CryptoFuturesSpread);
90
91#[derive(Debug)]
92pub struct CryptoOptionSpreadRow(pub CryptoOptionSpread);
93
94#[derive(Debug)]
95pub struct TokenizedAssetRow(pub TokenizedAsset);
96
97impl<'r> FromRow<'r, PgRow> for InstrumentAnyRow {
98    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
99        let kind = row.get::<String, _>("kind");
100        if kind == "BETTING" {
101            Ok(Self(InstrumentAny::Betting(
102                BettingInstrumentRow::from_row(row).unwrap().0,
103            )))
104        } else if kind == "BINARY_OPTION" {
105            Ok(Self(InstrumentAny::BinaryOption(
106                BinaryOptionRow::from_row(row).unwrap().0,
107            )))
108        } else if kind == "CRYPTO_FUTURE" {
109            Ok(Self(InstrumentAny::CryptoFuture(
110                CryptoFutureRow::from_row(row).unwrap().0,
111            )))
112        } else if kind == "CRYPTO_FUTURES_SPREAD" {
113            Ok(Self(InstrumentAny::CryptoFuturesSpread(
114                CryptoFuturesSpreadRow::from_row(row).unwrap().0,
115            )))
116        } else if kind == "CRYPTO_OPTION" {
117            Ok(Self(InstrumentAny::CryptoOption(
118                CryptoOptionRow::from_row(row).unwrap().0,
119            )))
120        } else if kind == "CRYPTO_OPTION_SPREAD" {
121            Ok(Self(InstrumentAny::CryptoOptionSpread(
122                CryptoOptionSpreadRow::from_row(row).unwrap().0,
123            )))
124        } else if kind == "CRYPTO_PERPETUAL" {
125            Ok(Self(InstrumentAny::CryptoPerpetual(
126                CryptoPerpetualRow::from_row(row).unwrap().0,
127            )))
128        } else if kind == "CURRENCY_PAIR" {
129            Ok(Self(InstrumentAny::CurrencyPair(
130                CurrencyPairRow::from_row(row).unwrap().0,
131            )))
132        } else if kind == "EQUITY" {
133            Ok(Self(InstrumentAny::Equity(
134                EquityRow::from_row(row).unwrap().0,
135            )))
136        } else if kind == "FUTURES_CONTRACT" {
137            Ok(Self(InstrumentAny::FuturesContract(
138                FuturesContractRow::from_row(row).unwrap().0,
139            )))
140        } else if kind == "FUTURES_SPREAD" {
141            Ok(Self(InstrumentAny::FuturesSpread(
142                FuturesSpreadRow::from_row(row).unwrap().0,
143            )))
144        } else if kind == "OPTION_CONTRACT" {
145            Ok(Self(InstrumentAny::OptionContract(
146                OptionContractRow::from_row(row).unwrap().0,
147            )))
148        } else if kind == "COMMODITY" {
149            Ok(Self(InstrumentAny::Commodity(
150                CommodityRow::from_row(row).unwrap().0,
151            )))
152        } else if kind == "INDEX_INSTRUMENT" {
153            Ok(Self(InstrumentAny::IndexInstrument(
154                IndexInstrumentRow::from_row(row).unwrap().0,
155            )))
156        } else if kind == "CFD" {
157            Ok(Self(InstrumentAny::Cfd(CfdRow::from_row(row).unwrap().0)))
158        } else if kind == "OPTION_SPREAD" {
159            Ok(Self(InstrumentAny::OptionSpread(
160                OptionSpreadRow::from_row(row).unwrap().0,
161            )))
162        } else if kind == "PERPETUAL_CONTRACT" {
163            Ok(Self(InstrumentAny::PerpetualContract(
164                PerpetualContractRow::from_row(row).unwrap().0,
165            )))
166        } else if kind == "TOKENIZED_ASSET" {
167            Ok(Self(InstrumentAny::TokenizedAsset(
168                TokenizedAssetRow::from_row(row).unwrap().0,
169            )))
170        } else {
171            Err(sqlx::Error::Decode(
172                format!("Unknown instrument type: {kind}").into(),
173            ))
174        }
175    }
176}
177
178// TODO: New/updated schema required to support betting instrument loading
179impl<'r> FromRow<'r, PgRow> for BettingInstrumentRow {
180    #[expect(
181        clippy::too_many_lines,
182        reason = "SQL row mapping mirrors the full betting instrument constructor"
183    )]
184    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
185        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
186        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
187        let event_type_id = read_u64(row, "event_type_id")?;
188        let event_type_name = row
189            .try_get::<String, _>("event_type_name")
190            .map(|res| Ustr::from(res.as_str()))?;
191        let competition_id = read_u64(row, "competition_id")?;
192        let competition_name = row
193            .try_get::<String, _>("competition_name")
194            .map(|res| Ustr::from(res.as_str()))?;
195        let event_id = read_u64(row, "event_id")?;
196        let event_name = row
197            .try_get::<String, _>("event_name")
198            .map(|res| Ustr::from(res.as_str()))?;
199        let event_country_code = row
200            .try_get::<String, _>("event_country_code")
201            .map(|res| Ustr::from(res.as_str()))?;
202        let event_open_date = row
203            .try_get::<String, _>("event_open_date")
204            .map(UnixNanos::from)?;
205        let betting_type = row
206            .try_get::<String, _>("betting_type")
207            .map(|res| Ustr::from(res.as_str()))?;
208        let market_id = row
209            .try_get::<String, _>("market_id")
210            .map(|res| Ustr::from(res.as_str()))?;
211        let market_name = row
212            .try_get::<String, _>("market_name")
213            .map(|res| Ustr::from(res.as_str()))?;
214        let market_type = row
215            .try_get::<String, _>("market_type")
216            .map(|res| Ustr::from(res.as_str()))?;
217        let market_start_time = row
218            .try_get::<String, _>("market_start_time")
219            .map(UnixNanos::from)?;
220        let selection_id = read_u64(row, "selection_id")?;
221        let selection_name = row
222            .try_get::<String, _>("selection_name")
223            .map(|res| Ustr::from(res.as_str()))?;
224        let selection_handicap = row.try_get::<f64, _>("selection_handicap")?;
225        let currency = row
226            .try_get::<String, _>("quote_currency")
227            .map(Currency::from)?;
228        let price_precision = read_u8(row, "price_precision")?;
229        let size_precision = read_u8(row, "size_precision")?;
230        let price_increment = row
231            .try_get::<String, _>("price_increment")
232            .map(Price::from)?;
233        let size_increment = row
234            .try_get::<String, _>("size_increment")
235            .map(Quantity::from)?;
236        let max_quantity = row
237            .try_get::<Option<String>, _>("max_quantity")
238            .ok()
239            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
240        let min_quantity = row
241            .try_get::<Option<String>, _>("min_quantity")
242            .ok()
243            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
244        let max_notional = row
245            .try_get::<Option<String>, _>("max_notional")
246            .ok()
247            .and_then(|res| res.map(|value| Money::from(value.as_str())));
248        let min_notional = row
249            .try_get::<Option<String>, _>("min_notional")
250            .ok()
251            .and_then(|res| res.map(|value| Money::from(value.as_str())));
252        let max_price = row
253            .try_get::<Option<String>, _>("max_price")
254            .ok()
255            .and_then(|res| res.map(|value| Price::from(value.as_str())));
256        let min_price = row
257            .try_get::<Option<String>, _>("min_price")
258            .ok()
259            .and_then(|res| res.map(|value| Price::from(value.as_str())));
260        let margin_init = row
261            .try_get::<String, _>("margin_init")
262            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
263        let margin_maint = row
264            .try_get::<String, _>("margin_maint")
265            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
266        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
267        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
268
269        let info: Option<Json<Params>> = row.try_get("info")?;
270
271        let inst = BettingInstrument::builder()
272            .instrument_id(id)
273            .raw_symbol(raw_symbol)
274            .event_type_id(event_type_id)
275            .event_type_name(event_type_name)
276            .competition_id(competition_id)
277            .competition_name(competition_name)
278            .event_id(event_id)
279            .event_name(event_name)
280            .event_country_code(event_country_code)
281            .event_open_date(event_open_date)
282            .betting_type(betting_type)
283            .market_id(market_id)
284            .market_name(market_name)
285            .market_type(market_type)
286            .market_start_time(market_start_time)
287            .selection_id(selection_id)
288            .selection_name(selection_name)
289            .selection_handicap(selection_handicap)
290            .currency(currency)
291            .price_precision(price_precision)
292            .size_precision(size_precision)
293            .price_increment(price_increment)
294            .size_increment(size_increment)
295            .maybe_max_quantity(max_quantity)
296            .maybe_min_quantity(min_quantity)
297            .maybe_max_notional(max_notional)
298            .maybe_min_notional(min_notional)
299            .maybe_max_price(max_price)
300            .maybe_min_price(min_price)
301            .maybe_margin_init(margin_init)
302            .maybe_margin_maint(margin_maint)
303            .maybe_info(info.map(|info| info.0))
304            .ts_event(ts_event)
305            .ts_init(ts_init)
306            .build()
307            .unwrap();
308        Ok(Self(inst))
309    }
310}
311
312impl<'r> FromRow<'r, PgRow> for BinaryOptionRow {
313    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
314        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
315        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
316        let asset_class = row
317            .try_get::<AssetClassPg, _>("asset_class")
318            .map(|res| res.0)?;
319        let currency = row
320            .try_get::<String, _>("quote_currency")
321            .map(Currency::from)?;
322        let activation_ns = row
323            .try_get::<String, _>("activation_ns")
324            .map(UnixNanos::from)?;
325        let expiration_ns = row
326            .try_get::<String, _>("expiration_ns")
327            .map(UnixNanos::from)?;
328        let price_precision = read_u8(row, "price_precision")?;
329        let size_precision = read_u8(row, "size_precision")?;
330        let price_increment = row
331            .try_get::<String, _>("price_increment")
332            .map(|res| Price::from_str(res.as_str()).unwrap())?;
333        let size_increment = row
334            .try_get::<String, _>("size_increment")
335            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
336        let outcome = row
337            .try_get::<Option<String>, _>("outcome")
338            .ok()
339            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
340        let description = row
341            .try_get::<Option<String>, _>("description")
342            .ok()
343            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
344        let max_quantity = row
345            .try_get::<Option<String>, _>("max_quantity")
346            .ok()
347            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
348        let min_quantity = row
349            .try_get::<Option<String>, _>("min_quantity")
350            .ok()
351            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
352        let max_notional = row
353            .try_get::<Option<String>, _>("max_notional")
354            .ok()
355            .and_then(|res| res.map(|value| Money::from(value.as_str())));
356        let min_notional = row
357            .try_get::<Option<String>, _>("min_notional")
358            .ok()
359            .and_then(|res| res.map(|value| Money::from(value.as_str())));
360        let max_price = row
361            .try_get::<Option<String>, _>("max_price")
362            .ok()
363            .and_then(|res| res.map(|value| Price::from(value.as_str())));
364        let min_price = row
365            .try_get::<Option<String>, _>("min_price")
366            .ok()
367            .and_then(|res| res.map(|value| Price::from(value.as_str())));
368        let margin_init = row
369            .try_get::<String, _>("margin_init")
370            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
371        let margin_maint = row
372            .try_get::<String, _>("margin_maint")
373            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
374        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
375        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
376
377        let info: Option<Json<Params>> = row.try_get("info")?;
378
379        let inst = BinaryOption::builder()
380            .instrument_id(id)
381            .raw_symbol(raw_symbol)
382            .asset_class(asset_class)
383            .currency(currency)
384            .activation_ns(activation_ns)
385            .expiration_ns(expiration_ns)
386            .price_precision(price_precision)
387            .size_precision(size_precision)
388            .price_increment(price_increment)
389            .size_increment(size_increment)
390            .maybe_outcome(outcome)
391            .maybe_description(description)
392            .maybe_max_quantity(max_quantity)
393            .maybe_min_quantity(min_quantity)
394            .maybe_max_notional(max_notional)
395            .maybe_min_notional(min_notional)
396            .maybe_max_price(max_price)
397            .maybe_min_price(min_price)
398            .maybe_margin_init(margin_init)
399            .maybe_margin_maint(margin_maint)
400            .maybe_info(info.map(|info| info.0))
401            .ts_event(ts_event)
402            .ts_init(ts_init)
403            .build()
404            .unwrap();
405        Ok(Self(inst))
406    }
407}
408
409impl<'r> FromRow<'r, PgRow> for CryptoFutureRow {
410    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
411        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
412        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
413        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
414        let quote_currency = row
415            .try_get::<String, _>("quote_currency")
416            .map(Currency::from)?;
417        let settlement_currency = row
418            .try_get::<String, _>("settlement_currency")
419            .map(Currency::from)?;
420        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
421        let activation_ns = row
422            .try_get::<String, _>("activation_ns")
423            .map(UnixNanos::from)?;
424        let expiration_ns = row
425            .try_get::<String, _>("expiration_ns")
426            .map(UnixNanos::from)?;
427        let price_precision = read_u8(row, "price_precision")?;
428        let size_precision = read_u8(row, "size_precision")?;
429        let price_increment = row
430            .try_get::<String, _>("price_increment")
431            .map(|res| Price::from_str(res.as_str()).unwrap())?;
432        let size_increment = row
433            .try_get::<String, _>("size_increment")
434            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
435        let multiplier = row
436            .try_get::<String, _>("multiplier")
437            .map(|res| Quantity::from(res.as_str()))?;
438        let lot_size = row
439            .try_get::<String, _>("lot_size")
440            .map(|res| Quantity::from(res.as_str()))?;
441        let max_quantity = row
442            .try_get::<Option<String>, _>("max_quantity")
443            .ok()
444            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
445        let min_quantity = row
446            .try_get::<Option<String>, _>("min_quantity")
447            .ok()
448            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
449        let max_notional = row
450            .try_get::<Option<String>, _>("max_notional")
451            .ok()
452            .and_then(|res| res.map(|value| Money::from(value.as_str())));
453        let min_notional = row
454            .try_get::<Option<String>, _>("min_notional")
455            .ok()
456            .and_then(|res| res.map(|value| Money::from(value.as_str())));
457        let max_price = row
458            .try_get::<Option<String>, _>("max_price")
459            .ok()
460            .and_then(|res| res.map(|value| Price::from(value.as_str())));
461        let min_price = row
462            .try_get::<Option<String>, _>("min_price")
463            .ok()
464            .and_then(|res| res.map(|value| Price::from(value.as_str())));
465        let margin_init = row
466            .try_get::<String, _>("margin_init")
467            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
468        let margin_maint = row
469            .try_get::<String, _>("margin_maint")
470            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
471        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
472        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
473
474        let info: Option<Json<Params>> = row.try_get("info")?;
475
476        let inst = CryptoFuture::builder()
477            .instrument_id(id)
478            .raw_symbol(raw_symbol)
479            .underlying(underlying)
480            .quote_currency(quote_currency)
481            .settlement_currency(settlement_currency)
482            .is_inverse(is_inverse)
483            .activation_ns(activation_ns)
484            .expiration_ns(expiration_ns)
485            .price_precision(price_precision)
486            .size_precision(size_precision)
487            .price_increment(price_increment)
488            .size_increment(size_increment)
489            .multiplier(multiplier)
490            .lot_size(lot_size)
491            .maybe_max_quantity(max_quantity)
492            .maybe_min_quantity(min_quantity)
493            .maybe_max_notional(max_notional)
494            .maybe_min_notional(min_notional)
495            .maybe_max_price(max_price)
496            .maybe_min_price(min_price)
497            .maybe_margin_init(margin_init)
498            .maybe_margin_maint(margin_maint)
499            .maybe_info(info.map(|info| info.0))
500            .ts_event(ts_event)
501            .ts_init(ts_init)
502            .build()
503            .unwrap();
504        Ok(Self(inst))
505    }
506}
507
508impl<'r> FromRow<'r, PgRow> for CryptoOptionRow {
509    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
510        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
511        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
512        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
513        let quote_currency = row
514            .try_get::<String, _>("quote_currency")
515            .map(Currency::from)?;
516        let settlement_currency = row
517            .try_get::<String, _>("settlement_currency")
518            .map(Currency::from)?;
519        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
520        let option_kind = row
521            .try_get::<String, _>("option_kind")
522            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
523        let strike_price = row
524            .try_get::<String, _>("strike_price")
525            .map(|res| Price::from_str(res.as_str()).unwrap())?;
526        let activation_ns = row
527            .try_get::<String, _>("activation_ns")
528            .map(UnixNanos::from)?;
529        let expiration_ns = row
530            .try_get::<String, _>("expiration_ns")
531            .map(UnixNanos::from)?;
532        let price_precision = read_u8(row, "price_precision")?;
533        let size_precision = read_u8(row, "size_precision")?;
534        let price_increment = row
535            .try_get::<String, _>("price_increment")
536            .map(|res| Price::from_str(res.as_str()).unwrap())?;
537        let size_increment = row
538            .try_get::<String, _>("size_increment")
539            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
540        let multiplier = row
541            .try_get::<String, _>("multiplier")
542            .map(|res| Quantity::from(res.as_str()))?;
543        let lot_size = row
544            .try_get::<String, _>("lot_size")
545            .map(|res| Quantity::from(res.as_str()))?;
546        let max_quantity = row
547            .try_get::<Option<String>, _>("max_quantity")
548            .ok()
549            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
550        let min_quantity = row
551            .try_get::<Option<String>, _>("min_quantity")
552            .ok()
553            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
554        let max_notional = row
555            .try_get::<Option<String>, _>("max_notional")
556            .ok()
557            .and_then(|res| res.map(|value| Money::from(value.as_str())));
558        let min_notional = row
559            .try_get::<Option<String>, _>("min_notional")
560            .ok()
561            .and_then(|res| res.map(|value| Money::from(value.as_str())));
562        let max_price = row
563            .try_get::<Option<String>, _>("max_price")
564            .ok()
565            .and_then(|res| res.map(|value| Price::from(value.as_str())));
566        let min_price = row
567            .try_get::<Option<String>, _>("min_price")
568            .ok()
569            .and_then(|res| res.map(|value| Price::from(value.as_str())));
570        let margin_init = row
571            .try_get::<String, _>("margin_init")
572            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
573        let margin_maint = row
574            .try_get::<String, _>("margin_maint")
575            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
576        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
577        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
578
579        let info: Option<Json<Params>> = row.try_get("info")?;
580
581        let inst = CryptoOption::builder()
582            .instrument_id(id)
583            .raw_symbol(raw_symbol)
584            .underlying(underlying)
585            .quote_currency(quote_currency)
586            .settlement_currency(settlement_currency)
587            .is_inverse(is_inverse)
588            .option_kind(option_kind)
589            .strike_price(strike_price)
590            .activation_ns(activation_ns)
591            .expiration_ns(expiration_ns)
592            .price_precision(price_precision)
593            .size_precision(size_precision)
594            .price_increment(price_increment)
595            .size_increment(size_increment)
596            .multiplier(multiplier)
597            .lot_size(lot_size)
598            .maybe_max_quantity(max_quantity)
599            .maybe_min_quantity(min_quantity)
600            .maybe_max_notional(max_notional)
601            .maybe_min_notional(min_notional)
602            .maybe_max_price(max_price)
603            .maybe_min_price(min_price)
604            .maybe_margin_init(margin_init)
605            .maybe_margin_maint(margin_maint)
606            .maybe_info(info.map(|info| info.0))
607            .ts_event(ts_event)
608            .ts_init(ts_init)
609            .build()
610            .unwrap();
611        Ok(Self(inst))
612    }
613}
614
615impl<'r> FromRow<'r, PgRow> for CryptoPerpetualRow {
616    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
617        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
618        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
619        let base_currency = row
620            .try_get::<String, _>("base_currency")
621            .map(Currency::from)?;
622        let quote_currency = row
623            .try_get::<String, _>("quote_currency")
624            .map(Currency::from)?;
625        let settlement_currency = row
626            .try_get::<String, _>("settlement_currency")
627            .map(Currency::from)?;
628        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
629        let price_precision = read_u8(row, "price_precision")?;
630        let size_precision = read_u8(row, "size_precision")?;
631        let price_increment = row
632            .try_get::<String, _>("price_increment")
633            .map(|res| Price::from_str(res.as_str()).unwrap())?;
634        let size_increment = row
635            .try_get::<String, _>("size_increment")
636            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
637        let multiplier = row
638            .try_get::<String, _>("multiplier")
639            .map(|res| Quantity::from(res.as_str()))?;
640        let lot_size = row
641            .try_get::<String, _>("lot_size")
642            .map(|res| Quantity::from(res.as_str()))?;
643        let max_quantity = row
644            .try_get::<Option<String>, _>("max_quantity")
645            .ok()
646            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
647        let min_quantity = row
648            .try_get::<Option<String>, _>("min_quantity")
649            .ok()
650            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
651        let max_notional = row
652            .try_get::<Option<String>, _>("max_notional")
653            .ok()
654            .and_then(|res| res.map(|res| Money::from(res.as_str())));
655        let min_notional = row
656            .try_get::<Option<String>, _>("min_notional")
657            .ok()
658            .and_then(|res| res.map(|res| Money::from(res.as_str())));
659        let max_price = row
660            .try_get::<Option<String>, _>("max_price")
661            .ok()
662            .and_then(|res| res.map(|res| Price::from(res.as_str())));
663        let min_price = row
664            .try_get::<Option<String>, _>("min_price")
665            .ok()
666            .and_then(|res| res.map(|res| Price::from(res.as_str())));
667        let margin_init = row
668            .try_get::<String, _>("margin_init")
669            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
670        let margin_maint = row
671            .try_get::<String, _>("margin_maint")
672            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
673        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
674        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
675        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
676
677        let info: Option<Json<Params>> = row.try_get("info")?;
678
679        let inst = CryptoPerpetual::builder()
680            .instrument_id(id)
681            .raw_symbol(raw_symbol)
682            .base_currency(base_currency)
683            .quote_currency(quote_currency)
684            .settlement_currency(settlement_currency)
685            .is_inverse(is_inverse)
686            .price_precision(price_precision)
687            .size_precision(size_precision)
688            .price_increment(price_increment)
689            .size_increment(size_increment)
690            .multiplier(multiplier)
691            .lot_size(lot_size)
692            .maybe_max_quantity(max_quantity)
693            .maybe_min_quantity(min_quantity)
694            .maybe_max_notional(max_notional)
695            .maybe_min_notional(min_notional)
696            .maybe_max_price(max_price)
697            .maybe_min_price(min_price)
698            .maybe_margin_init(margin_init)
699            .maybe_margin_maint(margin_maint)
700            .maybe_info(info.map(|info| info.0))
701            .ts_event(ts_event)
702            .ts_init(ts_init)
703            .build()
704            .unwrap();
705        Ok(Self(inst))
706    }
707}
708
709impl<'r> FromRow<'r, PgRow> for CurrencyPairRow {
710    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
711        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
712        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
713        let base_currency = row
714            .try_get::<String, _>("base_currency")
715            .map(Currency::from)?;
716        let quote_currency = row
717            .try_get::<String, _>("quote_currency")
718            .map(Currency::from)?;
719        let price_precision = read_u8(row, "price_precision")?;
720        let size_precision = read_u8(row, "size_precision")?;
721        let price_increment = row
722            .try_get::<String, _>("price_increment")
723            .map(|res| Price::from(res.as_str()))?;
724        let size_increment = row
725            .try_get::<String, _>("size_increment")
726            .map(|res| Quantity::from(res.as_str()))?;
727        let multiplier = row
728            .try_get::<Option<String>, _>("multiplier")
729            .ok()
730            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
731        let lot_size = row
732            .try_get::<Option<String>, _>("lot_size")
733            .ok()
734            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
735        let max_quantity = row
736            .try_get::<Option<String>, _>("max_quantity")
737            .ok()
738            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
739        let min_quantity = row
740            .try_get::<Option<String>, _>("min_quantity")
741            .ok()
742            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
743        let max_notional = row
744            .try_get::<Option<String>, _>("max_notional")
745            .ok()
746            .and_then(|res| res.map(|res| Money::from(res.as_str())));
747        let min_notional = row
748            .try_get::<Option<String>, _>("min_notional")
749            .ok()
750            .and_then(|res| res.map(|res| Money::from(res.as_str())));
751        let max_price = row
752            .try_get::<Option<String>, _>("max_price")
753            .ok()
754            .and_then(|res| res.map(|res| Price::from(res.as_str())));
755        let min_price = row
756            .try_get::<Option<String>, _>("min_price")
757            .ok()
758            .and_then(|res| res.map(|res| Price::from(res.as_str())));
759        let margin_init = row
760            .try_get::<String, _>("margin_init")
761            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
762        let margin_maint = row
763            .try_get::<String, _>("margin_maint")
764            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
765        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
766        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
767
768        let info: Option<Json<Params>> = row.try_get("info")?;
769
770        let inst = CurrencyPair::builder()
771            .instrument_id(id)
772            .raw_symbol(raw_symbol)
773            .base_currency(base_currency)
774            .quote_currency(quote_currency)
775            .price_precision(price_precision)
776            .size_precision(size_precision)
777            .price_increment(price_increment)
778            .size_increment(size_increment)
779            .maybe_multiplier(multiplier)
780            .maybe_lot_size(lot_size)
781            .maybe_max_quantity(max_quantity)
782            .maybe_min_quantity(min_quantity)
783            .maybe_max_notional(max_notional)
784            .maybe_min_notional(min_notional)
785            .maybe_max_price(max_price)
786            .maybe_min_price(min_price)
787            .maybe_margin_init(margin_init)
788            .maybe_margin_maint(margin_maint)
789            .maybe_info(info.map(|info| info.0))
790            .ts_event(ts_event)
791            .ts_init(ts_init)
792            .build()
793            .unwrap();
794        Ok(Self(inst))
795    }
796}
797
798impl<'r> FromRow<'r, PgRow> for EquityRow {
799    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
800        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
801        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
802        let isin = row
803            .try_get::<Option<String>, _>("isin")
804            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
805        let currency = row
806            .try_get::<String, _>("quote_currency")
807            .map(Currency::from)?;
808        let price_precision = read_u8(row, "price_precision")?;
809        let price_increment = row
810            .try_get::<String, _>("price_increment")
811            .map(|res| Price::from_str(res.as_str()).unwrap())?;
812        let lot_size = row
813            .try_get::<Option<String>, _>("lot_size")
814            .map(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()))?;
815        let max_quantity = row
816            .try_get::<Option<String>, _>("max_quantity")
817            .ok()
818            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
819        let min_quantity = row
820            .try_get::<Option<String>, _>("min_quantity")
821            .ok()
822            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
823        let max_price = row
824            .try_get::<Option<String>, _>("max_price")
825            .ok()
826            .and_then(|res| res.map(|s| Price::from(s.as_str())));
827        let min_price = row
828            .try_get::<Option<String>, _>("min_price")
829            .ok()
830            .and_then(|res| res.map(|s| Price::from(s.as_str())));
831        let margin_init = row
832            .try_get::<String, _>("margin_init")
833            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
834        let margin_maint = row
835            .try_get::<String, _>("margin_maint")
836            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
837        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
838        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
839
840        let info: Option<Json<Params>> = row.try_get("info")?;
841
842        let inst = Equity::builder()
843            .instrument_id(id)
844            .raw_symbol(raw_symbol)
845            .maybe_isin(isin)
846            .currency(currency)
847            .price_precision(price_precision)
848            .price_increment(price_increment)
849            .maybe_lot_size(lot_size)
850            .maybe_max_quantity(max_quantity)
851            .maybe_min_quantity(min_quantity)
852            .maybe_max_price(max_price)
853            .maybe_min_price(min_price)
854            .maybe_margin_init(margin_init)
855            .maybe_margin_maint(margin_maint)
856            .maybe_info(info.map(|info| info.0))
857            .ts_event(ts_event)
858            .ts_init(ts_init)
859            .build()
860            .unwrap();
861        Ok(Self(inst))
862    }
863}
864
865impl<'r> FromRow<'r, PgRow> for FuturesContractRow {
866    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
867        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
868        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
869        let asset_class = row
870            .try_get::<AssetClassPg, _>("asset_class")
871            .map(|res| res.0)?;
872        let exchange = row
873            .try_get::<Option<String>, _>("exchange")
874            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
875        let underlying = row
876            .try_get::<String, _>("underlying")
877            .map(|res| Ustr::from(res.as_str()))?;
878        let currency = row
879            .try_get::<String, _>("quote_currency")
880            .map(Currency::from)?;
881        let activation_ns = row
882            .try_get::<String, _>("activation_ns")
883            .map(UnixNanos::from)?;
884        let expiration_ns = row
885            .try_get::<String, _>("expiration_ns")
886            .map(UnixNanos::from)?;
887        let price_precision = read_u8(row, "price_precision")?;
888        let price_increment = row
889            .try_get::<String, _>("price_increment")
890            .map(|res| Price::from(res.as_str()))?;
891        let multiplier = row
892            .try_get::<String, _>("multiplier")
893            .map(|res| Quantity::from(res.as_str()))?;
894        let lot_size = row
895            .try_get::<String, _>("lot_size")
896            .map(|res| Quantity::from(res.as_str()))?;
897        let max_quantity = row
898            .try_get::<Option<String>, _>("max_quantity")
899            .ok()
900            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
901        let min_quantity = row
902            .try_get::<Option<String>, _>("min_quantity")
903            .ok()
904            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
905        let max_price = row
906            .try_get::<Option<String>, _>("max_price")
907            .ok()
908            .and_then(|res| res.map(|s| Price::from(s.as_str())));
909        let min_price = row
910            .try_get::<Option<String>, _>("min_price")
911            .ok()
912            .and_then(|res| res.map(|s| Price::from(s.as_str())));
913        let margin_init = row
914            .try_get::<String, _>("margin_init")
915            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
916        let margin_maint = row
917            .try_get::<String, _>("margin_maint")
918            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
919        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
920        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
921
922        let info: Option<Json<Params>> = row.try_get("info")?;
923
924        let inst = FuturesContract::builder()
925            .instrument_id(id)
926            .raw_symbol(raw_symbol)
927            .asset_class(asset_class)
928            .maybe_exchange(exchange)
929            .underlying(underlying)
930            .activation_ns(activation_ns)
931            .expiration_ns(expiration_ns)
932            .currency(currency)
933            .price_precision(price_precision)
934            .price_increment(price_increment)
935            .multiplier(multiplier)
936            .lot_size(lot_size)
937            .maybe_max_quantity(max_quantity)
938            .maybe_min_quantity(min_quantity)
939            .maybe_max_price(max_price)
940            .maybe_min_price(min_price)
941            .maybe_margin_init(margin_init)
942            .maybe_margin_maint(margin_maint)
943            .maybe_info(info.map(|info| info.0))
944            .ts_event(ts_event)
945            .ts_init(ts_init)
946            .build()
947            .unwrap();
948        Ok(Self(inst))
949    }
950}
951
952impl<'r> FromRow<'r, PgRow> for FuturesSpreadRow {
953    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
954        todo!("Implement FromRow for FuturesSpread")
955    }
956}
957
958impl<'r> FromRow<'r, PgRow> for OptionContractRow {
959    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
960        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
961        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
962        let asset_class = row
963            .try_get::<AssetClassPg, _>("asset_class")
964            .map(|res| res.0)?;
965        let exchange = row
966            .try_get::<Option<String>, _>("exchange")
967            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
968        let underlying = row
969            .try_get::<String, _>("underlying")
970            .map(|res| Ustr::from(res.as_str()))?;
971        let option_kind = row
972            .try_get::<String, _>("option_kind")
973            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
974        let activation_ns = row
975            .try_get::<String, _>("activation_ns")
976            .map(UnixNanos::from)?;
977        let expiration_ns = row
978            .try_get::<String, _>("expiration_ns")
979            .map(UnixNanos::from)?;
980        let strike_price = row
981            .try_get::<String, _>("strike_price")
982            .map(|res| Price::from_str(res.as_str()).unwrap())?;
983        let currency = row
984            .try_get::<String, _>("quote_currency")
985            .map(Currency::from)?;
986        let price_precision = read_u8(row, "price_precision")?;
987        let price_increment = row
988            .try_get::<String, _>("price_increment")
989            .map(|res| Price::from_str(res.as_str()).unwrap())?;
990        let multiplier = row
991            .try_get::<String, _>("multiplier")
992            .map(|res| Quantity::from(res.as_str()))?;
993        let lot_size = row
994            .try_get::<String, _>("lot_size")
995            .map(|res| Quantity::from(res.as_str()))
996            .unwrap();
997        let max_quantity = row
998            .try_get::<Option<String>, _>("max_quantity")
999            .ok()
1000            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
1001        let min_quantity = row
1002            .try_get::<Option<String>, _>("min_quantity")
1003            .ok()
1004            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
1005        let max_price = row
1006            .try_get::<Option<String>, _>("max_price")
1007            .ok()
1008            .and_then(|res| res.map(|s| Price::from(s.as_str())));
1009        let min_price = row
1010            .try_get::<Option<String>, _>("min_price")
1011            .ok()
1012            .and_then(|res| res.map(|s| Price::from(s.as_str())));
1013        let margin_init = row
1014            .try_get::<String, _>("margin_init")
1015            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1016        let margin_maint = row
1017            .try_get::<String, _>("margin_maint")
1018            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1019        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1020        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1021
1022        let info: Option<Json<Params>> = row.try_get("info")?;
1023
1024        let inst = OptionContract::builder()
1025            .instrument_id(id)
1026            .raw_symbol(raw_symbol)
1027            .asset_class(asset_class)
1028            .maybe_exchange(exchange)
1029            .underlying(underlying)
1030            .option_kind(option_kind)
1031            .strike_price(strike_price)
1032            .currency(currency)
1033            .activation_ns(activation_ns)
1034            .expiration_ns(expiration_ns)
1035            .price_precision(price_precision)
1036            .price_increment(price_increment)
1037            .multiplier(multiplier)
1038            .lot_size(lot_size)
1039            .maybe_max_quantity(max_quantity)
1040            .maybe_min_quantity(min_quantity)
1041            .maybe_max_price(max_price)
1042            .maybe_min_price(min_price)
1043            .maybe_margin_init(margin_init)
1044            .maybe_margin_maint(margin_maint)
1045            .maybe_info(info.map(|info| info.0))
1046            .ts_event(ts_event)
1047            .ts_init(ts_init)
1048            .build()
1049            .unwrap();
1050        Ok(Self(inst))
1051    }
1052}
1053
1054impl<'r> FromRow<'r, PgRow> for CommodityRow {
1055    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1056        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1057        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1058        let asset_class = row
1059            .try_get::<AssetClassPg, _>("asset_class")
1060            .map(|res| res.0)?;
1061        let quote_currency = row
1062            .try_get::<String, _>("quote_currency")
1063            .map(Currency::from)?;
1064        let price_precision = read_u8(row, "price_precision")?;
1065        let size_precision = read_u8(row, "size_precision")?;
1066        let price_increment = row
1067            .try_get::<String, _>("price_increment")
1068            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1069        let size_increment = row
1070            .try_get::<String, _>("size_increment")
1071            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1072        let lot_size = row
1073            .try_get::<Option<String>, _>("lot_size")
1074            .ok()
1075            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1076        let max_quantity = row
1077            .try_get::<Option<String>, _>("max_quantity")
1078            .ok()
1079            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1080        let min_quantity = row
1081            .try_get::<Option<String>, _>("min_quantity")
1082            .ok()
1083            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1084        let max_notional = row
1085            .try_get::<Option<String>, _>("max_notional")
1086            .ok()
1087            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1088        let min_notional = row
1089            .try_get::<Option<String>, _>("min_notional")
1090            .ok()
1091            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1092        let max_price = row
1093            .try_get::<Option<String>, _>("max_price")
1094            .ok()
1095            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1096        let min_price = row
1097            .try_get::<Option<String>, _>("min_price")
1098            .ok()
1099            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1100        let margin_init = row
1101            .try_get::<String, _>("margin_init")
1102            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1103        let margin_maint = row
1104            .try_get::<String, _>("margin_maint")
1105            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1106        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1107        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1108
1109        let info: Option<Json<Params>> = row.try_get("info")?;
1110
1111        let inst = Commodity::builder()
1112            .instrument_id(id)
1113            .raw_symbol(raw_symbol)
1114            .asset_class(asset_class)
1115            .quote_currency(quote_currency)
1116            .price_precision(price_precision)
1117            .size_precision(size_precision)
1118            .price_increment(price_increment)
1119            .size_increment(size_increment)
1120            .maybe_lot_size(lot_size)
1121            .maybe_max_quantity(max_quantity)
1122            .maybe_min_quantity(min_quantity)
1123            .maybe_max_notional(max_notional)
1124            .maybe_min_notional(min_notional)
1125            .maybe_max_price(max_price)
1126            .maybe_min_price(min_price)
1127            .maybe_margin_init(margin_init)
1128            .maybe_margin_maint(margin_maint)
1129            .maybe_info(info.map(|info| info.0))
1130            .ts_event(ts_event)
1131            .ts_init(ts_init)
1132            .build()
1133            .unwrap();
1134        Ok(Self(inst))
1135    }
1136}
1137
1138impl<'r> FromRow<'r, PgRow> for IndexInstrumentRow {
1139    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1140        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1141        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1142        let currency = row
1143            .try_get::<String, _>("quote_currency")
1144            .map(Currency::from)?;
1145        let price_precision = read_u8(row, "price_precision")?;
1146        let size_precision = read_u8(row, "size_precision")?;
1147        let price_increment = row
1148            .try_get::<String, _>("price_increment")
1149            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1150        let size_increment = row
1151            .try_get::<String, _>("size_increment")
1152            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1153        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1154        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1155
1156        let info: Option<Json<Params>> = row.try_get("info")?;
1157
1158        let inst = IndexInstrument::builder()
1159            .instrument_id(id)
1160            .raw_symbol(raw_symbol)
1161            .currency(currency)
1162            .price_precision(price_precision)
1163            .size_precision(size_precision)
1164            .price_increment(price_increment)
1165            .size_increment(size_increment)
1166            .maybe_info(info.map(|info| info.0))
1167            .ts_event(ts_event)
1168            .ts_init(ts_init)
1169            .build()
1170            .unwrap();
1171        Ok(Self(inst))
1172    }
1173}
1174
1175impl<'r> FromRow<'r, PgRow> for CfdRow {
1176    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1177        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1178        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1179        let asset_class = row
1180            .try_get::<AssetClassPg, _>("asset_class")
1181            .map(|res| res.0)?;
1182        let base_currency = row
1183            .try_get::<Option<String>, _>("base_currency")
1184            .ok()
1185            .and_then(|res| res.map(Currency::from));
1186        let quote_currency = row
1187            .try_get::<String, _>("quote_currency")
1188            .map(Currency::from)?;
1189        let price_precision = read_u8(row, "price_precision")?;
1190        let size_precision = read_u8(row, "size_precision")?;
1191        let price_increment = row
1192            .try_get::<String, _>("price_increment")
1193            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1194        let size_increment = row
1195            .try_get::<String, _>("size_increment")
1196            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1197        let lot_size = row
1198            .try_get::<Option<String>, _>("lot_size")
1199            .ok()
1200            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1201        let max_quantity = row
1202            .try_get::<Option<String>, _>("max_quantity")
1203            .ok()
1204            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1205        let min_quantity = row
1206            .try_get::<Option<String>, _>("min_quantity")
1207            .ok()
1208            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1209        let max_notional = row
1210            .try_get::<Option<String>, _>("max_notional")
1211            .ok()
1212            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1213        let min_notional = row
1214            .try_get::<Option<String>, _>("min_notional")
1215            .ok()
1216            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1217        let max_price = row
1218            .try_get::<Option<String>, _>("max_price")
1219            .ok()
1220            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1221        let min_price = row
1222            .try_get::<Option<String>, _>("min_price")
1223            .ok()
1224            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1225        let margin_init = row
1226            .try_get::<String, _>("margin_init")
1227            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1228        let margin_maint = row
1229            .try_get::<String, _>("margin_maint")
1230            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1231        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1232        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1233
1234        let info: Option<Json<Params>> = row.try_get("info")?;
1235
1236        let inst = Cfd::builder()
1237            .instrument_id(id)
1238            .raw_symbol(raw_symbol)
1239            .asset_class(asset_class)
1240            .maybe_base_currency(base_currency)
1241            .quote_currency(quote_currency)
1242            .price_precision(price_precision)
1243            .size_precision(size_precision)
1244            .price_increment(price_increment)
1245            .size_increment(size_increment)
1246            .maybe_lot_size(lot_size)
1247            .maybe_max_quantity(max_quantity)
1248            .maybe_min_quantity(min_quantity)
1249            .maybe_max_notional(max_notional)
1250            .maybe_min_notional(min_notional)
1251            .maybe_max_price(max_price)
1252            .maybe_min_price(min_price)
1253            .maybe_margin_init(margin_init)
1254            .maybe_margin_maint(margin_maint)
1255            .maybe_info(info.map(|info| info.0))
1256            .ts_event(ts_event)
1257            .ts_init(ts_init)
1258            .build()
1259            .unwrap();
1260        Ok(Self(inst))
1261    }
1262}
1263
1264impl<'r> FromRow<'r, PgRow> for PerpetualContractRow {
1265    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1266        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1267        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1268        let underlying = row
1269            .try_get::<String, _>("underlying")
1270            .map(|res| Ustr::from(res.as_str()))?;
1271        let asset_class = row
1272            .try_get::<AssetClassPg, _>("asset_class")
1273            .map(|res| res.0)?;
1274        let base_currency = row
1275            .try_get::<Option<String>, _>("base_currency")
1276            .ok()
1277            .and_then(|res| res.map(Currency::from));
1278        let quote_currency = row
1279            .try_get::<String, _>("quote_currency")
1280            .map(Currency::from)?;
1281        let settlement_currency = row
1282            .try_get::<String, _>("settlement_currency")
1283            .map(Currency::from)?;
1284        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1285        let price_precision = read_u8(row, "price_precision")?;
1286        let size_precision = read_u8(row, "size_precision")?;
1287        let price_increment = row
1288            .try_get::<String, _>("price_increment")
1289            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1290        let size_increment = row
1291            .try_get::<String, _>("size_increment")
1292            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1293        let multiplier = row
1294            .try_get::<String, _>("multiplier")
1295            .map(|res| Quantity::from(res.as_str()))?;
1296        let lot_size = row
1297            .try_get::<String, _>("lot_size")
1298            .map(|res| Quantity::from(res.as_str()))?;
1299        let max_quantity = row
1300            .try_get::<Option<String>, _>("max_quantity")
1301            .ok()
1302            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1303        let min_quantity = row
1304            .try_get::<Option<String>, _>("min_quantity")
1305            .ok()
1306            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1307        let max_notional = row
1308            .try_get::<Option<String>, _>("max_notional")
1309            .ok()
1310            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1311        let min_notional = row
1312            .try_get::<Option<String>, _>("min_notional")
1313            .ok()
1314            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1315        let max_price = row
1316            .try_get::<Option<String>, _>("max_price")
1317            .ok()
1318            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1319        let min_price = row
1320            .try_get::<Option<String>, _>("min_price")
1321            .ok()
1322            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1323        let margin_init = row
1324            .try_get::<String, _>("margin_init")
1325            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1326        let margin_maint = row
1327            .try_get::<String, _>("margin_maint")
1328            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1329        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1330        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1331
1332        let info: Option<Json<Params>> = row.try_get("info")?;
1333
1334        let inst = PerpetualContract::builder()
1335            .instrument_id(id)
1336            .raw_symbol(raw_symbol)
1337            .underlying(underlying)
1338            .asset_class(asset_class)
1339            .maybe_base_currency(base_currency)
1340            .quote_currency(quote_currency)
1341            .settlement_currency(settlement_currency)
1342            .is_inverse(is_inverse)
1343            .price_precision(price_precision)
1344            .size_precision(size_precision)
1345            .price_increment(price_increment)
1346            .size_increment(size_increment)
1347            .multiplier(multiplier)
1348            .lot_size(lot_size)
1349            .maybe_max_quantity(max_quantity)
1350            .maybe_min_quantity(min_quantity)
1351            .maybe_max_notional(max_notional)
1352            .maybe_min_notional(min_notional)
1353            .maybe_max_price(max_price)
1354            .maybe_min_price(min_price)
1355            .maybe_margin_init(margin_init)
1356            .maybe_margin_maint(margin_maint)
1357            .maybe_info(info.map(|info| info.0))
1358            .ts_event(ts_event)
1359            .ts_init(ts_init)
1360            .build()
1361            .unwrap();
1362        Ok(Self(inst))
1363    }
1364}
1365
1366impl<'r> FromRow<'r, PgRow> for OptionSpreadRow {
1367    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
1368        todo!("Implement FromRow for OptionSpread")
1369    }
1370}
1371
1372impl<'r> FromRow<'r, PgRow> for CryptoFuturesSpreadRow {
1373    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1374        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1375        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1376        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
1377        let quote_currency = row
1378            .try_get::<String, _>("quote_currency")
1379            .map(Currency::from)?;
1380        let settlement_currency = row
1381            .try_get::<String, _>("settlement_currency")
1382            .map(Currency::from)?;
1383        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1384        let strategy_type = row
1385            .try_get::<String, _>("strategy_type")
1386            .map(|res| Ustr::from(res.as_str()))?;
1387        let activation_ns = row
1388            .try_get::<String, _>("activation_ns")
1389            .map(UnixNanos::from)?;
1390        let expiration_ns = row
1391            .try_get::<String, _>("expiration_ns")
1392            .map(UnixNanos::from)?;
1393        let price_precision = read_u8(row, "price_precision")?;
1394        let size_precision = read_u8(row, "size_precision")?;
1395        let price_increment = row
1396            .try_get::<String, _>("price_increment")
1397            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1398        let size_increment = row
1399            .try_get::<String, _>("size_increment")
1400            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1401        let multiplier = row
1402            .try_get::<String, _>("multiplier")
1403            .map(|res| Quantity::from(res.as_str()))?;
1404        let lot_size = row
1405            .try_get::<String, _>("lot_size")
1406            .map(|res| Quantity::from(res.as_str()))?;
1407        let max_quantity = row
1408            .try_get::<Option<String>, _>("max_quantity")
1409            .ok()
1410            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1411        let min_quantity = row
1412            .try_get::<Option<String>, _>("min_quantity")
1413            .ok()
1414            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1415        let max_notional = row
1416            .try_get::<Option<String>, _>("max_notional")
1417            .ok()
1418            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1419        let min_notional = row
1420            .try_get::<Option<String>, _>("min_notional")
1421            .ok()
1422            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1423        let max_price = row
1424            .try_get::<Option<String>, _>("max_price")
1425            .ok()
1426            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1427        let min_price = row
1428            .try_get::<Option<String>, _>("min_price")
1429            .ok()
1430            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1431        let margin_init = row
1432            .try_get::<String, _>("margin_init")
1433            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1434        let margin_maint = row
1435            .try_get::<String, _>("margin_maint")
1436            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1437        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1438        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1439
1440        let info: Option<Json<Params>> = row.try_get("info")?;
1441
1442        let inst = CryptoFuturesSpread::builder()
1443            .instrument_id(id)
1444            .raw_symbol(raw_symbol)
1445            .underlying(underlying)
1446            .quote_currency(quote_currency)
1447            .settlement_currency(settlement_currency)
1448            .is_inverse(is_inverse)
1449            .strategy_type(strategy_type)
1450            .activation_ns(activation_ns)
1451            .expiration_ns(expiration_ns)
1452            .price_precision(price_precision)
1453            .size_precision(size_precision)
1454            .price_increment(price_increment)
1455            .size_increment(size_increment)
1456            .multiplier(multiplier)
1457            .lot_size(lot_size)
1458            .maybe_max_quantity(max_quantity)
1459            .maybe_min_quantity(min_quantity)
1460            .maybe_max_notional(max_notional)
1461            .maybe_min_notional(min_notional)
1462            .maybe_max_price(max_price)
1463            .maybe_min_price(min_price)
1464            .maybe_margin_init(margin_init)
1465            .maybe_margin_maint(margin_maint)
1466            .maybe_info(info.map(|info| info.0))
1467            .ts_event(ts_event)
1468            .ts_init(ts_init)
1469            .build()
1470            .unwrap();
1471        Ok(Self(inst))
1472    }
1473}
1474
1475impl<'r> FromRow<'r, PgRow> for CryptoOptionSpreadRow {
1476    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1477        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1478        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1479        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
1480        let quote_currency = row
1481            .try_get::<String, _>("quote_currency")
1482            .map(Currency::from)?;
1483        let settlement_currency = row
1484            .try_get::<String, _>("settlement_currency")
1485            .map(Currency::from)?;
1486        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1487        let strategy_type = row
1488            .try_get::<String, _>("strategy_type")
1489            .map(|res| Ustr::from(res.as_str()))?;
1490        let activation_ns = row
1491            .try_get::<String, _>("activation_ns")
1492            .map(UnixNanos::from)?;
1493        let expiration_ns = row
1494            .try_get::<String, _>("expiration_ns")
1495            .map(UnixNanos::from)?;
1496        let price_precision = read_u8(row, "price_precision")?;
1497        let size_precision = read_u8(row, "size_precision")?;
1498        let price_increment = row
1499            .try_get::<String, _>("price_increment")
1500            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1501        let size_increment = row
1502            .try_get::<String, _>("size_increment")
1503            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1504        let multiplier = row
1505            .try_get::<String, _>("multiplier")
1506            .map(|res| Quantity::from(res.as_str()))?;
1507        let lot_size = row
1508            .try_get::<String, _>("lot_size")
1509            .map(|res| Quantity::from(res.as_str()))?;
1510        let max_quantity = row
1511            .try_get::<Option<String>, _>("max_quantity")
1512            .ok()
1513            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1514        let min_quantity = row
1515            .try_get::<Option<String>, _>("min_quantity")
1516            .ok()
1517            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1518        let max_notional = row
1519            .try_get::<Option<String>, _>("max_notional")
1520            .ok()
1521            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1522        let min_notional = row
1523            .try_get::<Option<String>, _>("min_notional")
1524            .ok()
1525            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1526        let max_price = row
1527            .try_get::<Option<String>, _>("max_price")
1528            .ok()
1529            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1530        let min_price = row
1531            .try_get::<Option<String>, _>("min_price")
1532            .ok()
1533            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1534        let margin_init = row
1535            .try_get::<String, _>("margin_init")
1536            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1537        let margin_maint = row
1538            .try_get::<String, _>("margin_maint")
1539            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1540        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1541        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1542
1543        let info: Option<Json<Params>> = row.try_get("info")?;
1544
1545        let inst = CryptoOptionSpread::builder()
1546            .instrument_id(id)
1547            .raw_symbol(raw_symbol)
1548            .underlying(underlying)
1549            .quote_currency(quote_currency)
1550            .settlement_currency(settlement_currency)
1551            .is_inverse(is_inverse)
1552            .strategy_type(strategy_type)
1553            .activation_ns(activation_ns)
1554            .expiration_ns(expiration_ns)
1555            .price_precision(price_precision)
1556            .size_precision(size_precision)
1557            .price_increment(price_increment)
1558            .size_increment(size_increment)
1559            .multiplier(multiplier)
1560            .lot_size(lot_size)
1561            .maybe_max_quantity(max_quantity)
1562            .maybe_min_quantity(min_quantity)
1563            .maybe_max_notional(max_notional)
1564            .maybe_min_notional(min_notional)
1565            .maybe_max_price(max_price)
1566            .maybe_min_price(min_price)
1567            .maybe_margin_init(margin_init)
1568            .maybe_margin_maint(margin_maint)
1569            .maybe_info(info.map(|info| info.0))
1570            .ts_event(ts_event)
1571            .ts_init(ts_init)
1572            .build()
1573            .unwrap();
1574        Ok(Self(inst))
1575    }
1576}
1577
1578impl<'r> FromRow<'r, PgRow> for TokenizedAssetRow {
1579    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1580        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1581        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1582        let asset_class = row.try_get::<String, _>("asset_class").and_then(|res| {
1583            AssetClass::from_str(res.as_str()).map_err(|e| {
1584                sqlx::Error::Decode(format!("Invalid asset class '{res}': {e}").into())
1585            })
1586        })?;
1587        // Tokenized base currencies (e.g. AAPLx) are not in the static currency
1588        // map, so use get_or_create_crypto which registers them dynamically.
1589        let base_currency = row
1590            .try_get::<String, _>("base_currency")
1591            .map(|code| Currency::get_or_create_crypto(&code))?;
1592        let quote_currency = row
1593            .try_get::<String, _>("quote_currency")
1594            .map(Currency::from)?;
1595        let isin = row
1596            .try_get::<Option<String>, _>("isin")
1597            .ok()
1598            .and_then(|res| res.map(|s| Ustr::from(s.as_str())));
1599        let price_precision = read_u8(row, "price_precision")?;
1600        let size_precision = read_u8(row, "size_precision")?;
1601        let price_increment = row
1602            .try_get::<String, _>("price_increment")
1603            .map(|res| Price::from(res.as_str()))?;
1604        let size_increment = row
1605            .try_get::<String, _>("size_increment")
1606            .map(|res| Quantity::from(res.as_str()))?;
1607        let multiplier = read_optional_quantity(row, "multiplier");
1608        let lot_size = read_optional_quantity(row, "lot_size");
1609        let max_quantity = read_optional_quantity(row, "max_quantity");
1610        let min_quantity = read_optional_quantity(row, "min_quantity");
1611        let max_notional = row
1612            .try_get::<Option<String>, _>("max_notional")
1613            .ok()
1614            .and_then(|res| res.map(|res| Money::from(res.as_str())));
1615        let min_notional = row
1616            .try_get::<Option<String>, _>("min_notional")
1617            .ok()
1618            .and_then(|res| res.map(|res| Money::from(res.as_str())));
1619        let max_price = row
1620            .try_get::<Option<String>, _>("max_price")
1621            .ok()
1622            .and_then(|res| res.map(|res| Price::from(res.as_str())));
1623        let min_price = row
1624            .try_get::<Option<String>, _>("min_price")
1625            .ok()
1626            .and_then(|res| res.map(|res| Price::from(res.as_str())));
1627        let margin_init = row
1628            .try_get::<String, _>("margin_init")
1629            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1630        let margin_maint = row
1631            .try_get::<String, _>("margin_maint")
1632            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1633        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1634        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1635
1636        let info: Option<Json<Params>> = row.try_get("info")?;
1637
1638        let inst = TokenizedAsset::builder()
1639            .instrument_id(id)
1640            .raw_symbol(raw_symbol)
1641            .asset_class(asset_class)
1642            .base_currency(base_currency)
1643            .quote_currency(quote_currency)
1644            .maybe_isin(isin)
1645            .price_precision(price_precision)
1646            .size_precision(size_precision)
1647            .price_increment(price_increment)
1648            .size_increment(size_increment)
1649            .maybe_multiplier(multiplier)
1650            .maybe_lot_size(lot_size)
1651            .maybe_max_quantity(max_quantity)
1652            .maybe_min_quantity(min_quantity)
1653            .maybe_max_notional(max_notional)
1654            .maybe_min_notional(min_notional)
1655            .maybe_max_price(max_price)
1656            .maybe_min_price(min_price)
1657            .maybe_margin_init(margin_init)
1658            .maybe_margin_maint(margin_maint)
1659            .maybe_info(info.map(|info| info.0))
1660            .ts_event(ts_event)
1661            .ts_init(ts_init)
1662            .build()
1663            .unwrap();
1664        Ok(Self(inst))
1665    }
1666}
1667
1668fn read_optional_quantity(row: &PgRow, column: &str) -> Option<Quantity> {
1669    row.try_get::<Option<String>, _>(column)
1670        .ok()
1671        .and_then(|res| res.map(|res| Quantity::from(res.as_str())))
1672}