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::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};
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 maker_fee = row
267            .try_get::<String, _>("maker_fee")
268            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
269        let taker_fee = row
270            .try_get::<String, _>("taker_fee")
271            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
272        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
273        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
274
275        let inst = BettingInstrument::builder()
276            .instrument_id(id)
277            .raw_symbol(raw_symbol)
278            .event_type_id(event_type_id)
279            .event_type_name(event_type_name)
280            .competition_id(competition_id)
281            .competition_name(competition_name)
282            .event_id(event_id)
283            .event_name(event_name)
284            .event_country_code(event_country_code)
285            .event_open_date(event_open_date)
286            .betting_type(betting_type)
287            .market_id(market_id)
288            .market_name(market_name)
289            .market_type(market_type)
290            .market_start_time(market_start_time)
291            .selection_id(selection_id)
292            .selection_name(selection_name)
293            .selection_handicap(selection_handicap)
294            .currency(currency)
295            .price_precision(price_precision)
296            .size_precision(size_precision)
297            .price_increment(price_increment)
298            .size_increment(size_increment)
299            .maybe_max_quantity(max_quantity)
300            .maybe_min_quantity(min_quantity)
301            .maybe_max_notional(max_notional)
302            .maybe_min_notional(min_notional)
303            .maybe_max_price(max_price)
304            .maybe_min_price(min_price)
305            .maybe_margin_init(margin_init)
306            .maybe_margin_maint(margin_maint)
307            .maybe_maker_fee(maker_fee)
308            .maybe_taker_fee(taker_fee)
309            .ts_event(ts_event)
310            .ts_init(ts_init)
311            .build()
312            .unwrap();
313        Ok(Self(inst))
314    }
315}
316
317impl<'r> FromRow<'r, PgRow> for BinaryOptionRow {
318    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
319        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
320        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
321        let asset_class = row
322            .try_get::<AssetClassPg, _>("asset_class")
323            .map(|res| res.0)?;
324        let currency = row
325            .try_get::<String, _>("quote_currency")
326            .map(Currency::from)?;
327        let activation_ns = row
328            .try_get::<String, _>("activation_ns")
329            .map(UnixNanos::from)?;
330        let expiration_ns = row
331            .try_get::<String, _>("expiration_ns")
332            .map(UnixNanos::from)?;
333        let price_precision = read_u8(row, "price_precision")?;
334        let size_precision = read_u8(row, "size_precision")?;
335        let price_increment = row
336            .try_get::<String, _>("price_increment")
337            .map(|res| Price::from_str(res.as_str()).unwrap())?;
338        let size_increment = row
339            .try_get::<String, _>("size_increment")
340            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
341        let outcome = row
342            .try_get::<Option<String>, _>("outcome")
343            .ok()
344            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
345        let description = row
346            .try_get::<Option<String>, _>("description")
347            .ok()
348            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
349        let max_quantity = row
350            .try_get::<Option<String>, _>("max_quantity")
351            .ok()
352            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
353        let min_quantity = row
354            .try_get::<Option<String>, _>("min_quantity")
355            .ok()
356            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
357        let max_notional = row
358            .try_get::<Option<String>, _>("max_notional")
359            .ok()
360            .and_then(|res| res.map(|value| Money::from(value.as_str())));
361        let min_notional = row
362            .try_get::<Option<String>, _>("min_notional")
363            .ok()
364            .and_then(|res| res.map(|value| Money::from(value.as_str())));
365        let max_price = row
366            .try_get::<Option<String>, _>("max_price")
367            .ok()
368            .and_then(|res| res.map(|value| Price::from(value.as_str())));
369        let min_price = row
370            .try_get::<Option<String>, _>("min_price")
371            .ok()
372            .and_then(|res| res.map(|value| Price::from(value.as_str())));
373        let margin_init = row
374            .try_get::<String, _>("margin_init")
375            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
376        let margin_maint = row
377            .try_get::<String, _>("margin_maint")
378            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
379        let maker_fee = row
380            .try_get::<String, _>("maker_fee")
381            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
382        let taker_fee = row
383            .try_get::<String, _>("taker_fee")
384            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
385        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
386        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
387
388        let inst = BinaryOption::builder()
389            .instrument_id(id)
390            .raw_symbol(raw_symbol)
391            .asset_class(asset_class)
392            .currency(currency)
393            .activation_ns(activation_ns)
394            .expiration_ns(expiration_ns)
395            .price_precision(price_precision)
396            .size_precision(size_precision)
397            .price_increment(price_increment)
398            .size_increment(size_increment)
399            .maybe_outcome(outcome)
400            .maybe_description(description)
401            .maybe_max_quantity(max_quantity)
402            .maybe_min_quantity(min_quantity)
403            .maybe_max_notional(max_notional)
404            .maybe_min_notional(min_notional)
405            .maybe_max_price(max_price)
406            .maybe_min_price(min_price)
407            .maybe_margin_init(margin_init)
408            .maybe_margin_maint(margin_maint)
409            .maybe_maker_fee(maker_fee)
410            .maybe_taker_fee(taker_fee)
411            .ts_event(ts_event)
412            .ts_init(ts_init)
413            .build()
414            .unwrap();
415        Ok(Self(inst))
416    }
417}
418
419impl<'r> FromRow<'r, PgRow> for CryptoFutureRow {
420    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
421        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
422        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
423        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
424        let quote_currency = row
425            .try_get::<String, _>("quote_currency")
426            .map(Currency::from)?;
427        let settlement_currency = row
428            .try_get::<String, _>("settlement_currency")
429            .map(Currency::from)?;
430        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
431        let activation_ns = row
432            .try_get::<String, _>("activation_ns")
433            .map(UnixNanos::from)?;
434        let expiration_ns = row
435            .try_get::<String, _>("expiration_ns")
436            .map(UnixNanos::from)?;
437        let price_precision = read_u8(row, "price_precision")?;
438        let size_precision = read_u8(row, "size_precision")?;
439        let price_increment = row
440            .try_get::<String, _>("price_increment")
441            .map(|res| Price::from_str(res.as_str()).unwrap())?;
442        let size_increment = row
443            .try_get::<String, _>("size_increment")
444            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
445        let multiplier = row
446            .try_get::<String, _>("multiplier")
447            .map(|res| Quantity::from(res.as_str()))?;
448        let lot_size = row
449            .try_get::<String, _>("lot_size")
450            .map(|res| Quantity::from(res.as_str()))?;
451        let max_quantity = row
452            .try_get::<Option<String>, _>("max_quantity")
453            .ok()
454            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
455        let min_quantity = row
456            .try_get::<Option<String>, _>("min_quantity")
457            .ok()
458            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
459        let max_notional = row
460            .try_get::<Option<String>, _>("max_notional")
461            .ok()
462            .and_then(|res| res.map(|value| Money::from(value.as_str())));
463        let min_notional = row
464            .try_get::<Option<String>, _>("min_notional")
465            .ok()
466            .and_then(|res| res.map(|value| Money::from(value.as_str())));
467        let max_price = row
468            .try_get::<Option<String>, _>("max_price")
469            .ok()
470            .and_then(|res| res.map(|value| Price::from(value.as_str())));
471        let min_price = row
472            .try_get::<Option<String>, _>("min_price")
473            .ok()
474            .and_then(|res| res.map(|value| Price::from(value.as_str())));
475        let margin_init = row
476            .try_get::<String, _>("margin_init")
477            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
478        let margin_maint = row
479            .try_get::<String, _>("margin_maint")
480            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
481        let maker_fee = row
482            .try_get::<String, _>("maker_fee")
483            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
484        let taker_fee = row
485            .try_get::<String, _>("taker_fee")
486            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
487        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
488        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
489
490        let inst = CryptoFuture::builder()
491            .instrument_id(id)
492            .raw_symbol(raw_symbol)
493            .underlying(underlying)
494            .quote_currency(quote_currency)
495            .settlement_currency(settlement_currency)
496            .is_inverse(is_inverse)
497            .activation_ns(activation_ns)
498            .expiration_ns(expiration_ns)
499            .price_precision(price_precision)
500            .size_precision(size_precision)
501            .price_increment(price_increment)
502            .size_increment(size_increment)
503            .multiplier(multiplier)
504            .lot_size(lot_size)
505            .maybe_max_quantity(max_quantity)
506            .maybe_min_quantity(min_quantity)
507            .maybe_max_notional(max_notional)
508            .maybe_min_notional(min_notional)
509            .maybe_max_price(max_price)
510            .maybe_min_price(min_price)
511            .maybe_margin_init(margin_init)
512            .maybe_margin_maint(margin_maint)
513            .maybe_maker_fee(maker_fee)
514            .maybe_taker_fee(taker_fee)
515            .ts_event(ts_event)
516            .ts_init(ts_init)
517            .build()
518            .unwrap();
519        Ok(Self(inst))
520    }
521}
522
523impl<'r> FromRow<'r, PgRow> for CryptoOptionRow {
524    #[expect(
525        clippy::too_many_lines,
526        reason = "SQL row mapping mirrors the full crypto option constructor"
527    )]
528    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
529        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
530        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
531        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
532        let quote_currency = row
533            .try_get::<String, _>("quote_currency")
534            .map(Currency::from)?;
535        let settlement_currency = row
536            .try_get::<String, _>("settlement_currency")
537            .map(Currency::from)?;
538        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
539        let option_kind = row
540            .try_get::<String, _>("option_kind")
541            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
542        let strike_price = row
543            .try_get::<String, _>("strike_price")
544            .map(|res| Price::from_str(res.as_str()).unwrap())?;
545        let activation_ns = row
546            .try_get::<String, _>("activation_ns")
547            .map(UnixNanos::from)?;
548        let expiration_ns = row
549            .try_get::<String, _>("expiration_ns")
550            .map(UnixNanos::from)?;
551        let price_precision = read_u8(row, "price_precision")?;
552        let size_precision = read_u8(row, "size_precision")?;
553        let price_increment = row
554            .try_get::<String, _>("price_increment")
555            .map(|res| Price::from_str(res.as_str()).unwrap())?;
556        let size_increment = row
557            .try_get::<String, _>("size_increment")
558            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
559        let multiplier = row
560            .try_get::<String, _>("multiplier")
561            .map(|res| Quantity::from(res.as_str()))?;
562        let lot_size = row
563            .try_get::<String, _>("lot_size")
564            .map(|res| Quantity::from(res.as_str()))?;
565        let max_quantity = row
566            .try_get::<Option<String>, _>("max_quantity")
567            .ok()
568            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
569        let min_quantity = row
570            .try_get::<Option<String>, _>("min_quantity")
571            .ok()
572            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
573        let max_notional = row
574            .try_get::<Option<String>, _>("max_notional")
575            .ok()
576            .and_then(|res| res.map(|value| Money::from(value.as_str())));
577        let min_notional = row
578            .try_get::<Option<String>, _>("min_notional")
579            .ok()
580            .and_then(|res| res.map(|value| Money::from(value.as_str())));
581        let max_price = row
582            .try_get::<Option<String>, _>("max_price")
583            .ok()
584            .and_then(|res| res.map(|value| Price::from(value.as_str())));
585        let min_price = row
586            .try_get::<Option<String>, _>("min_price")
587            .ok()
588            .and_then(|res| res.map(|value| Price::from(value.as_str())));
589        let margin_init = row
590            .try_get::<String, _>("margin_init")
591            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
592        let margin_maint = row
593            .try_get::<String, _>("margin_maint")
594            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
595        let maker_fee = row
596            .try_get::<String, _>("maker_fee")
597            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
598        let taker_fee = row
599            .try_get::<String, _>("taker_fee")
600            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
601        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
602        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
603
604        let inst = CryptoOption::builder()
605            .instrument_id(id)
606            .raw_symbol(raw_symbol)
607            .underlying(underlying)
608            .quote_currency(quote_currency)
609            .settlement_currency(settlement_currency)
610            .is_inverse(is_inverse)
611            .option_kind(option_kind)
612            .strike_price(strike_price)
613            .activation_ns(activation_ns)
614            .expiration_ns(expiration_ns)
615            .price_precision(price_precision)
616            .size_precision(size_precision)
617            .price_increment(price_increment)
618            .size_increment(size_increment)
619            .multiplier(multiplier)
620            .lot_size(lot_size)
621            .maybe_max_quantity(max_quantity)
622            .maybe_min_quantity(min_quantity)
623            .maybe_max_notional(max_notional)
624            .maybe_min_notional(min_notional)
625            .maybe_max_price(max_price)
626            .maybe_min_price(min_price)
627            .maybe_margin_init(margin_init)
628            .maybe_margin_maint(margin_maint)
629            .maybe_maker_fee(maker_fee)
630            .maybe_taker_fee(taker_fee)
631            .ts_event(ts_event)
632            .ts_init(ts_init)
633            .build()
634            .unwrap();
635        Ok(Self(inst))
636    }
637}
638
639impl<'r> FromRow<'r, PgRow> for CryptoPerpetualRow {
640    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
641        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
642        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
643        let base_currency = row
644            .try_get::<String, _>("base_currency")
645            .map(Currency::from)?;
646        let quote_currency = row
647            .try_get::<String, _>("quote_currency")
648            .map(Currency::from)?;
649        let settlement_currency = row
650            .try_get::<String, _>("settlement_currency")
651            .map(Currency::from)?;
652        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
653        let price_precision = read_u8(row, "price_precision")?;
654        let size_precision = read_u8(row, "size_precision")?;
655        let price_increment = row
656            .try_get::<String, _>("price_increment")
657            .map(|res| Price::from_str(res.as_str()).unwrap())?;
658        let size_increment = row
659            .try_get::<String, _>("size_increment")
660            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
661        let multiplier = row
662            .try_get::<String, _>("multiplier")
663            .map(|res| Quantity::from(res.as_str()))?;
664        let lot_size = row
665            .try_get::<String, _>("lot_size")
666            .map(|res| Quantity::from(res.as_str()))?;
667        let max_quantity = row
668            .try_get::<Option<String>, _>("max_quantity")
669            .ok()
670            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
671        let min_quantity = row
672            .try_get::<Option<String>, _>("min_quantity")
673            .ok()
674            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
675        let max_notional = row
676            .try_get::<Option<String>, _>("max_notional")
677            .ok()
678            .and_then(|res| res.map(|res| Money::from(res.as_str())));
679        let min_notional = row
680            .try_get::<Option<String>, _>("min_notional")
681            .ok()
682            .and_then(|res| res.map(|res| Money::from(res.as_str())));
683        let max_price = row
684            .try_get::<Option<String>, _>("max_price")
685            .ok()
686            .and_then(|res| res.map(|res| Price::from(res.as_str())));
687        let min_price = row
688            .try_get::<Option<String>, _>("min_price")
689            .ok()
690            .and_then(|res| res.map(|res| Price::from(res.as_str())));
691        let margin_init = row
692            .try_get::<String, _>("margin_init")
693            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
694        let margin_maint = row
695            .try_get::<String, _>("margin_maint")
696            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
697        let maker_fee = row
698            .try_get::<String, _>("maker_fee")
699            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
700        let taker_fee = row
701            .try_get::<String, _>("taker_fee")
702            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
703        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
704        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
705        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
706
707        let inst = CryptoPerpetual::builder()
708            .instrument_id(id)
709            .raw_symbol(raw_symbol)
710            .base_currency(base_currency)
711            .quote_currency(quote_currency)
712            .settlement_currency(settlement_currency)
713            .is_inverse(is_inverse)
714            .price_precision(price_precision)
715            .size_precision(size_precision)
716            .price_increment(price_increment)
717            .size_increment(size_increment)
718            .multiplier(multiplier)
719            .lot_size(lot_size)
720            .maybe_max_quantity(max_quantity)
721            .maybe_min_quantity(min_quantity)
722            .maybe_max_notional(max_notional)
723            .maybe_min_notional(min_notional)
724            .maybe_max_price(max_price)
725            .maybe_min_price(min_price)
726            .maybe_margin_init(margin_init)
727            .maybe_margin_maint(margin_maint)
728            .maybe_maker_fee(maker_fee)
729            .maybe_taker_fee(taker_fee)
730            .ts_event(ts_event)
731            .ts_init(ts_init)
732            .build()
733            .unwrap();
734        Ok(Self(inst))
735    }
736}
737
738impl<'r> FromRow<'r, PgRow> for CurrencyPairRow {
739    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
740        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
741        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
742        let base_currency = row
743            .try_get::<String, _>("base_currency")
744            .map(Currency::from)?;
745        let quote_currency = row
746            .try_get::<String, _>("quote_currency")
747            .map(Currency::from)?;
748        let price_precision = read_u8(row, "price_precision")?;
749        let size_precision = read_u8(row, "size_precision")?;
750        let price_increment = row
751            .try_get::<String, _>("price_increment")
752            .map(|res| Price::from(res.as_str()))?;
753        let size_increment = row
754            .try_get::<String, _>("size_increment")
755            .map(|res| Quantity::from(res.as_str()))?;
756        let multiplier = row
757            .try_get::<Option<String>, _>("multiplier")
758            .ok()
759            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
760        let lot_size = row
761            .try_get::<Option<String>, _>("lot_size")
762            .ok()
763            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
764        let max_quantity = row
765            .try_get::<Option<String>, _>("max_quantity")
766            .ok()
767            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
768        let min_quantity = row
769            .try_get::<Option<String>, _>("min_quantity")
770            .ok()
771            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
772        let max_notional = row
773            .try_get::<Option<String>, _>("max_notional")
774            .ok()
775            .and_then(|res| res.map(|res| Money::from(res.as_str())));
776        let min_notional = row
777            .try_get::<Option<String>, _>("min_notional")
778            .ok()
779            .and_then(|res| res.map(|res| Money::from(res.as_str())));
780        let max_price = row
781            .try_get::<Option<String>, _>("max_price")
782            .ok()
783            .and_then(|res| res.map(|res| Price::from(res.as_str())));
784        let min_price = row
785            .try_get::<Option<String>, _>("min_price")
786            .ok()
787            .and_then(|res| res.map(|res| Price::from(res.as_str())));
788        let margin_init = row
789            .try_get::<String, _>("margin_init")
790            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
791        let margin_maint = row
792            .try_get::<String, _>("margin_maint")
793            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
794        let maker_fee = row
795            .try_get::<String, _>("maker_fee")
796            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
797        let taker_fee = row
798            .try_get::<String, _>("taker_fee")
799            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
800        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
801        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
802
803        let inst = CurrencyPair::builder()
804            .instrument_id(id)
805            .raw_symbol(raw_symbol)
806            .base_currency(base_currency)
807            .quote_currency(quote_currency)
808            .price_precision(price_precision)
809            .size_precision(size_precision)
810            .price_increment(price_increment)
811            .size_increment(size_increment)
812            .maybe_multiplier(multiplier)
813            .maybe_lot_size(lot_size)
814            .maybe_max_quantity(max_quantity)
815            .maybe_min_quantity(min_quantity)
816            .maybe_max_notional(max_notional)
817            .maybe_min_notional(min_notional)
818            .maybe_max_price(max_price)
819            .maybe_min_price(min_price)
820            .maybe_margin_init(margin_init)
821            .maybe_margin_maint(margin_maint)
822            .maybe_maker_fee(maker_fee)
823            .maybe_taker_fee(taker_fee)
824            .ts_event(ts_event)
825            .ts_init(ts_init)
826            .build()
827            .unwrap();
828        Ok(Self(inst))
829    }
830}
831
832impl<'r> FromRow<'r, PgRow> for EquityRow {
833    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
834        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
835        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
836        let isin = row
837            .try_get::<Option<String>, _>("isin")
838            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
839        let currency = row
840            .try_get::<String, _>("quote_currency")
841            .map(Currency::from)?;
842        let price_precision = read_u8(row, "price_precision")?;
843        let price_increment = row
844            .try_get::<String, _>("price_increment")
845            .map(|res| Price::from_str(res.as_str()).unwrap())?;
846        let lot_size = row
847            .try_get::<Option<String>, _>("lot_size")
848            .map(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()))?;
849        let max_quantity = row
850            .try_get::<Option<String>, _>("max_quantity")
851            .ok()
852            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
853        let min_quantity = row
854            .try_get::<Option<String>, _>("min_quantity")
855            .ok()
856            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
857        let max_price = row
858            .try_get::<Option<String>, _>("max_price")
859            .ok()
860            .and_then(|res| res.map(|s| Price::from(s.as_str())));
861        let min_price = row
862            .try_get::<Option<String>, _>("min_price")
863            .ok()
864            .and_then(|res| res.map(|s| Price::from(s.as_str())));
865        let margin_init = row
866            .try_get::<String, _>("margin_init")
867            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
868        let margin_maint = row
869            .try_get::<String, _>("margin_maint")
870            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
871        let maker_fee = row
872            .try_get::<String, _>("maker_fee")
873            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
874        let taker_fee = row
875            .try_get::<String, _>("taker_fee")
876            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
877        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
878        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
879
880        let inst = Equity::builder()
881            .instrument_id(id)
882            .raw_symbol(raw_symbol)
883            .maybe_isin(isin)
884            .currency(currency)
885            .price_precision(price_precision)
886            .price_increment(price_increment)
887            .maybe_lot_size(lot_size)
888            .maybe_max_quantity(max_quantity)
889            .maybe_min_quantity(min_quantity)
890            .maybe_max_price(max_price)
891            .maybe_min_price(min_price)
892            .maybe_margin_init(margin_init)
893            .maybe_margin_maint(margin_maint)
894            .maybe_maker_fee(maker_fee)
895            .maybe_taker_fee(taker_fee)
896            .ts_event(ts_event)
897            .ts_init(ts_init)
898            .build()
899            .unwrap();
900        Ok(Self(inst))
901    }
902}
903
904impl<'r> FromRow<'r, PgRow> for FuturesContractRow {
905    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
906        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
907        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
908        let asset_class = row
909            .try_get::<AssetClassPg, _>("asset_class")
910            .map(|res| res.0)?;
911        let exchange = row
912            .try_get::<Option<String>, _>("exchange")
913            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
914        let underlying = row
915            .try_get::<String, _>("underlying")
916            .map(|res| Ustr::from(res.as_str()))?;
917        let currency = row
918            .try_get::<String, _>("quote_currency")
919            .map(Currency::from)?;
920        let activation_ns = row
921            .try_get::<String, _>("activation_ns")
922            .map(UnixNanos::from)?;
923        let expiration_ns = row
924            .try_get::<String, _>("expiration_ns")
925            .map(UnixNanos::from)?;
926        let price_precision = read_u8(row, "price_precision")?;
927        let price_increment = row
928            .try_get::<String, _>("price_increment")
929            .map(|res| Price::from(res.as_str()))?;
930        let multiplier = row
931            .try_get::<String, _>("multiplier")
932            .map(|res| Quantity::from(res.as_str()))?;
933        let lot_size = row
934            .try_get::<String, _>("lot_size")
935            .map(|res| Quantity::from(res.as_str()))?;
936        let max_quantity = row
937            .try_get::<Option<String>, _>("max_quantity")
938            .ok()
939            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
940        let min_quantity = row
941            .try_get::<Option<String>, _>("min_quantity")
942            .ok()
943            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
944        let max_price = row
945            .try_get::<Option<String>, _>("max_price")
946            .ok()
947            .and_then(|res| res.map(|s| Price::from(s.as_str())));
948        let min_price = row
949            .try_get::<Option<String>, _>("min_price")
950            .ok()
951            .and_then(|res| res.map(|s| Price::from(s.as_str())));
952        let margin_init = row
953            .try_get::<String, _>("margin_init")
954            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
955        let margin_maint = row
956            .try_get::<String, _>("margin_maint")
957            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
958        let maker_fee = row
959            .try_get::<String, _>("maker_fee")
960            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
961        let taker_fee = row
962            .try_get::<String, _>("taker_fee")
963            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
964        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
965        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
966
967        let inst = FuturesContract::builder()
968            .instrument_id(id)
969            .raw_symbol(raw_symbol)
970            .asset_class(asset_class)
971            .maybe_exchange(exchange)
972            .underlying(underlying)
973            .activation_ns(activation_ns)
974            .expiration_ns(expiration_ns)
975            .currency(currency)
976            .price_precision(price_precision)
977            .price_increment(price_increment)
978            .multiplier(multiplier)
979            .lot_size(lot_size)
980            .maybe_max_quantity(max_quantity)
981            .maybe_min_quantity(min_quantity)
982            .maybe_max_price(max_price)
983            .maybe_min_price(min_price)
984            .maybe_margin_init(margin_init)
985            .maybe_margin_maint(margin_maint)
986            .maybe_maker_fee(maker_fee)
987            .maybe_taker_fee(taker_fee)
988            .ts_event(ts_event)
989            .ts_init(ts_init)
990            .build()
991            .unwrap();
992        Ok(Self(inst))
993    }
994}
995
996impl<'r> FromRow<'r, PgRow> for FuturesSpreadRow {
997    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
998        todo!("Implement FromRow for FuturesSpread")
999    }
1000}
1001
1002impl<'r> FromRow<'r, PgRow> for OptionContractRow {
1003    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1004        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1005        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
1006        let asset_class = row
1007            .try_get::<AssetClassPg, _>("asset_class")
1008            .map(|res| res.0)?;
1009        let exchange = row
1010            .try_get::<Option<String>, _>("exchange")
1011            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
1012        let underlying = row
1013            .try_get::<String, _>("underlying")
1014            .map(|res| Ustr::from(res.as_str()))?;
1015        let option_kind = row
1016            .try_get::<String, _>("option_kind")
1017            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
1018        let activation_ns = row
1019            .try_get::<String, _>("activation_ns")
1020            .map(UnixNanos::from)?;
1021        let expiration_ns = row
1022            .try_get::<String, _>("expiration_ns")
1023            .map(UnixNanos::from)?;
1024        let strike_price = row
1025            .try_get::<String, _>("strike_price")
1026            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1027        let currency = row
1028            .try_get::<String, _>("quote_currency")
1029            .map(Currency::from)?;
1030        let price_precision = read_u8(row, "price_precision")?;
1031        let price_increment = row
1032            .try_get::<String, _>("price_increment")
1033            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1034        let multiplier = row
1035            .try_get::<String, _>("multiplier")
1036            .map(|res| Quantity::from(res.as_str()))?;
1037        let lot_size = row
1038            .try_get::<String, _>("lot_size")
1039            .map(|res| Quantity::from(res.as_str()))
1040            .unwrap();
1041        let max_quantity = row
1042            .try_get::<Option<String>, _>("max_quantity")
1043            .ok()
1044            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
1045        let min_quantity = row
1046            .try_get::<Option<String>, _>("min_quantity")
1047            .ok()
1048            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
1049        let max_price = row
1050            .try_get::<Option<String>, _>("max_price")
1051            .ok()
1052            .and_then(|res| res.map(|s| Price::from(s.as_str())));
1053        let min_price = row
1054            .try_get::<Option<String>, _>("min_price")
1055            .ok()
1056            .and_then(|res| res.map(|s| Price::from(s.as_str())));
1057        let margin_init = row
1058            .try_get::<String, _>("margin_init")
1059            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1060        let margin_maint = row
1061            .try_get::<String, _>("margin_maint")
1062            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1063        let maker_fee = row
1064            .try_get::<String, _>("maker_fee")
1065            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1066        let taker_fee = row
1067            .try_get::<String, _>("taker_fee")
1068            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1069        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1070        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1071
1072        let inst = OptionContract::builder()
1073            .instrument_id(id)
1074            .raw_symbol(raw_symbol)
1075            .asset_class(asset_class)
1076            .maybe_exchange(exchange)
1077            .underlying(underlying)
1078            .option_kind(option_kind)
1079            .strike_price(strike_price)
1080            .currency(currency)
1081            .activation_ns(activation_ns)
1082            .expiration_ns(expiration_ns)
1083            .price_precision(price_precision)
1084            .price_increment(price_increment)
1085            .multiplier(multiplier)
1086            .lot_size(lot_size)
1087            .maybe_max_quantity(max_quantity)
1088            .maybe_min_quantity(min_quantity)
1089            .maybe_max_price(max_price)
1090            .maybe_min_price(min_price)
1091            .maybe_margin_init(margin_init)
1092            .maybe_margin_maint(margin_maint)
1093            .maybe_maker_fee(maker_fee)
1094            .maybe_taker_fee(taker_fee)
1095            .ts_event(ts_event)
1096            .ts_init(ts_init)
1097            .build()
1098            .unwrap();
1099        Ok(Self(inst))
1100    }
1101}
1102
1103impl<'r> FromRow<'r, PgRow> for CommodityRow {
1104    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1105        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1106        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1107        let asset_class = row
1108            .try_get::<AssetClassPg, _>("asset_class")
1109            .map(|res| res.0)?;
1110        let quote_currency = row
1111            .try_get::<String, _>("quote_currency")
1112            .map(Currency::from)?;
1113        let price_precision = read_u8(row, "price_precision")?;
1114        let size_precision = read_u8(row, "size_precision")?;
1115        let price_increment = row
1116            .try_get::<String, _>("price_increment")
1117            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1118        let size_increment = row
1119            .try_get::<String, _>("size_increment")
1120            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1121        let lot_size = row
1122            .try_get::<Option<String>, _>("lot_size")
1123            .ok()
1124            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1125        let max_quantity = row
1126            .try_get::<Option<String>, _>("max_quantity")
1127            .ok()
1128            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1129        let min_quantity = row
1130            .try_get::<Option<String>, _>("min_quantity")
1131            .ok()
1132            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1133        let max_notional = row
1134            .try_get::<Option<String>, _>("max_notional")
1135            .ok()
1136            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1137        let min_notional = row
1138            .try_get::<Option<String>, _>("min_notional")
1139            .ok()
1140            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1141        let max_price = row
1142            .try_get::<Option<String>, _>("max_price")
1143            .ok()
1144            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1145        let min_price = row
1146            .try_get::<Option<String>, _>("min_price")
1147            .ok()
1148            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1149        let margin_init = row
1150            .try_get::<String, _>("margin_init")
1151            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1152        let margin_maint = row
1153            .try_get::<String, _>("margin_maint")
1154            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1155        let maker_fee = row
1156            .try_get::<String, _>("maker_fee")
1157            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1158        let taker_fee = row
1159            .try_get::<String, _>("taker_fee")
1160            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1161        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1162        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1163
1164        let inst = Commodity::builder()
1165            .instrument_id(id)
1166            .raw_symbol(raw_symbol)
1167            .asset_class(asset_class)
1168            .quote_currency(quote_currency)
1169            .price_precision(price_precision)
1170            .size_precision(size_precision)
1171            .price_increment(price_increment)
1172            .size_increment(size_increment)
1173            .maybe_lot_size(lot_size)
1174            .maybe_max_quantity(max_quantity)
1175            .maybe_min_quantity(min_quantity)
1176            .maybe_max_notional(max_notional)
1177            .maybe_min_notional(min_notional)
1178            .maybe_max_price(max_price)
1179            .maybe_min_price(min_price)
1180            .maybe_margin_init(margin_init)
1181            .maybe_margin_maint(margin_maint)
1182            .maybe_maker_fee(maker_fee)
1183            .maybe_taker_fee(taker_fee)
1184            .ts_event(ts_event)
1185            .ts_init(ts_init)
1186            .build()
1187            .unwrap();
1188        Ok(Self(inst))
1189    }
1190}
1191
1192impl<'r> FromRow<'r, PgRow> for IndexInstrumentRow {
1193    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1194        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1195        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1196        let currency = row
1197            .try_get::<String, _>("quote_currency")
1198            .map(Currency::from)?;
1199        let price_precision = read_u8(row, "price_precision")?;
1200        let size_precision = read_u8(row, "size_precision")?;
1201        let price_increment = row
1202            .try_get::<String, _>("price_increment")
1203            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1204        let size_increment = row
1205            .try_get::<String, _>("size_increment")
1206            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1207        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1208        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1209
1210        let inst = IndexInstrument::builder()
1211            .instrument_id(id)
1212            .raw_symbol(raw_symbol)
1213            .currency(currency)
1214            .price_precision(price_precision)
1215            .size_precision(size_precision)
1216            .price_increment(price_increment)
1217            .size_increment(size_increment)
1218            .ts_event(ts_event)
1219            .ts_init(ts_init)
1220            .build()
1221            .unwrap();
1222        Ok(Self(inst))
1223    }
1224}
1225
1226impl<'r> FromRow<'r, PgRow> for CfdRow {
1227    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1228        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1229        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1230        let asset_class = row
1231            .try_get::<AssetClassPg, _>("asset_class")
1232            .map(|res| res.0)?;
1233        let base_currency = row
1234            .try_get::<Option<String>, _>("base_currency")
1235            .ok()
1236            .and_then(|res| res.map(Currency::from));
1237        let quote_currency = row
1238            .try_get::<String, _>("quote_currency")
1239            .map(Currency::from)?;
1240        let price_precision = read_u8(row, "price_precision")?;
1241        let size_precision = read_u8(row, "size_precision")?;
1242        let price_increment = row
1243            .try_get::<String, _>("price_increment")
1244            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1245        let size_increment = row
1246            .try_get::<String, _>("size_increment")
1247            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1248        let lot_size = row
1249            .try_get::<Option<String>, _>("lot_size")
1250            .ok()
1251            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1252        let max_quantity = row
1253            .try_get::<Option<String>, _>("max_quantity")
1254            .ok()
1255            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1256        let min_quantity = row
1257            .try_get::<Option<String>, _>("min_quantity")
1258            .ok()
1259            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1260        let max_notional = row
1261            .try_get::<Option<String>, _>("max_notional")
1262            .ok()
1263            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1264        let min_notional = row
1265            .try_get::<Option<String>, _>("min_notional")
1266            .ok()
1267            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1268        let max_price = row
1269            .try_get::<Option<String>, _>("max_price")
1270            .ok()
1271            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1272        let min_price = row
1273            .try_get::<Option<String>, _>("min_price")
1274            .ok()
1275            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1276        let margin_init = row
1277            .try_get::<String, _>("margin_init")
1278            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1279        let margin_maint = row
1280            .try_get::<String, _>("margin_maint")
1281            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1282        let maker_fee = row
1283            .try_get::<String, _>("maker_fee")
1284            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1285        let taker_fee = row
1286            .try_get::<String, _>("taker_fee")
1287            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1288        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1289        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1290
1291        let inst = Cfd::builder()
1292            .instrument_id(id)
1293            .raw_symbol(raw_symbol)
1294            .asset_class(asset_class)
1295            .maybe_base_currency(base_currency)
1296            .quote_currency(quote_currency)
1297            .price_precision(price_precision)
1298            .size_precision(size_precision)
1299            .price_increment(price_increment)
1300            .size_increment(size_increment)
1301            .maybe_lot_size(lot_size)
1302            .maybe_max_quantity(max_quantity)
1303            .maybe_min_quantity(min_quantity)
1304            .maybe_max_notional(max_notional)
1305            .maybe_min_notional(min_notional)
1306            .maybe_max_price(max_price)
1307            .maybe_min_price(min_price)
1308            .maybe_margin_init(margin_init)
1309            .maybe_margin_maint(margin_maint)
1310            .maybe_maker_fee(maker_fee)
1311            .maybe_taker_fee(taker_fee)
1312            .ts_event(ts_event)
1313            .ts_init(ts_init)
1314            .build()
1315            .unwrap();
1316        Ok(Self(inst))
1317    }
1318}
1319
1320impl<'r> FromRow<'r, PgRow> for PerpetualContractRow {
1321    #[expect(
1322        clippy::too_many_lines,
1323        reason = "SQL row mapping mirrors the full perpetual contract constructor"
1324    )]
1325    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1326        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1327        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1328        let underlying = row
1329            .try_get::<String, _>("underlying")
1330            .map(|res| Ustr::from(res.as_str()))?;
1331        let asset_class = row
1332            .try_get::<AssetClassPg, _>("asset_class")
1333            .map(|res| res.0)?;
1334        let base_currency = row
1335            .try_get::<Option<String>, _>("base_currency")
1336            .ok()
1337            .and_then(|res| res.map(Currency::from));
1338        let quote_currency = row
1339            .try_get::<String, _>("quote_currency")
1340            .map(Currency::from)?;
1341        let settlement_currency = row
1342            .try_get::<String, _>("settlement_currency")
1343            .map(Currency::from)?;
1344        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1345        let price_precision = read_u8(row, "price_precision")?;
1346        let size_precision = read_u8(row, "size_precision")?;
1347        let price_increment = row
1348            .try_get::<String, _>("price_increment")
1349            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1350        let size_increment = row
1351            .try_get::<String, _>("size_increment")
1352            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1353        let multiplier = row
1354            .try_get::<String, _>("multiplier")
1355            .map(|res| Quantity::from(res.as_str()))?;
1356        let lot_size = row
1357            .try_get::<String, _>("lot_size")
1358            .map(|res| Quantity::from(res.as_str()))?;
1359        let max_quantity = row
1360            .try_get::<Option<String>, _>("max_quantity")
1361            .ok()
1362            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1363        let min_quantity = row
1364            .try_get::<Option<String>, _>("min_quantity")
1365            .ok()
1366            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1367        let max_notional = row
1368            .try_get::<Option<String>, _>("max_notional")
1369            .ok()
1370            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1371        let min_notional = row
1372            .try_get::<Option<String>, _>("min_notional")
1373            .ok()
1374            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1375        let max_price = row
1376            .try_get::<Option<String>, _>("max_price")
1377            .ok()
1378            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1379        let min_price = row
1380            .try_get::<Option<String>, _>("min_price")
1381            .ok()
1382            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1383        let margin_init = row
1384            .try_get::<String, _>("margin_init")
1385            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1386        let margin_maint = row
1387            .try_get::<String, _>("margin_maint")
1388            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1389        let maker_fee = row
1390            .try_get::<String, _>("maker_fee")
1391            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1392        let taker_fee = row
1393            .try_get::<String, _>("taker_fee")
1394            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1395        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1396        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1397
1398        let inst = PerpetualContract::builder()
1399            .instrument_id(id)
1400            .raw_symbol(raw_symbol)
1401            .underlying(underlying)
1402            .asset_class(asset_class)
1403            .maybe_base_currency(base_currency)
1404            .quote_currency(quote_currency)
1405            .settlement_currency(settlement_currency)
1406            .is_inverse(is_inverse)
1407            .price_precision(price_precision)
1408            .size_precision(size_precision)
1409            .price_increment(price_increment)
1410            .size_increment(size_increment)
1411            .multiplier(multiplier)
1412            .lot_size(lot_size)
1413            .maybe_max_quantity(max_quantity)
1414            .maybe_min_quantity(min_quantity)
1415            .maybe_max_notional(max_notional)
1416            .maybe_min_notional(min_notional)
1417            .maybe_max_price(max_price)
1418            .maybe_min_price(min_price)
1419            .maybe_margin_init(margin_init)
1420            .maybe_margin_maint(margin_maint)
1421            .maybe_maker_fee(maker_fee)
1422            .maybe_taker_fee(taker_fee)
1423            .ts_event(ts_event)
1424            .ts_init(ts_init)
1425            .build()
1426            .unwrap();
1427        Ok(Self(inst))
1428    }
1429}
1430
1431impl<'r> FromRow<'r, PgRow> for OptionSpreadRow {
1432    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
1433        todo!("Implement FromRow for OptionSpread")
1434    }
1435}
1436
1437impl<'r> FromRow<'r, PgRow> for CryptoFuturesSpreadRow {
1438    #[expect(
1439        clippy::too_many_lines,
1440        reason = "SQL row mapping mirrors the full crypto futures spread constructor"
1441    )]
1442    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1443        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1444        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1445        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
1446        let quote_currency = row
1447            .try_get::<String, _>("quote_currency")
1448            .map(Currency::from)?;
1449        let settlement_currency = row
1450            .try_get::<String, _>("settlement_currency")
1451            .map(Currency::from)?;
1452        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1453        let strategy_type = row
1454            .try_get::<String, _>("strategy_type")
1455            .map(|res| Ustr::from(res.as_str()))?;
1456        let activation_ns = row
1457            .try_get::<String, _>("activation_ns")
1458            .map(UnixNanos::from)?;
1459        let expiration_ns = row
1460            .try_get::<String, _>("expiration_ns")
1461            .map(UnixNanos::from)?;
1462        let price_precision = read_u8(row, "price_precision")?;
1463        let size_precision = read_u8(row, "size_precision")?;
1464        let price_increment = row
1465            .try_get::<String, _>("price_increment")
1466            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1467        let size_increment = row
1468            .try_get::<String, _>("size_increment")
1469            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1470        let multiplier = row
1471            .try_get::<String, _>("multiplier")
1472            .map(|res| Quantity::from(res.as_str()))?;
1473        let lot_size = row
1474            .try_get::<String, _>("lot_size")
1475            .map(|res| Quantity::from(res.as_str()))?;
1476        let max_quantity = row
1477            .try_get::<Option<String>, _>("max_quantity")
1478            .ok()
1479            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1480        let min_quantity = row
1481            .try_get::<Option<String>, _>("min_quantity")
1482            .ok()
1483            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1484        let max_notional = row
1485            .try_get::<Option<String>, _>("max_notional")
1486            .ok()
1487            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1488        let min_notional = row
1489            .try_get::<Option<String>, _>("min_notional")
1490            .ok()
1491            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1492        let max_price = row
1493            .try_get::<Option<String>, _>("max_price")
1494            .ok()
1495            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1496        let min_price = row
1497            .try_get::<Option<String>, _>("min_price")
1498            .ok()
1499            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1500        let margin_init = row
1501            .try_get::<String, _>("margin_init")
1502            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1503        let margin_maint = row
1504            .try_get::<String, _>("margin_maint")
1505            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1506        let maker_fee = row
1507            .try_get::<String, _>("maker_fee")
1508            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1509        let taker_fee = row
1510            .try_get::<String, _>("taker_fee")
1511            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1512        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1513        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1514
1515        let inst = CryptoFuturesSpread::builder()
1516            .instrument_id(id)
1517            .raw_symbol(raw_symbol)
1518            .underlying(underlying)
1519            .quote_currency(quote_currency)
1520            .settlement_currency(settlement_currency)
1521            .is_inverse(is_inverse)
1522            .strategy_type(strategy_type)
1523            .activation_ns(activation_ns)
1524            .expiration_ns(expiration_ns)
1525            .price_precision(price_precision)
1526            .size_precision(size_precision)
1527            .price_increment(price_increment)
1528            .size_increment(size_increment)
1529            .multiplier(multiplier)
1530            .lot_size(lot_size)
1531            .maybe_max_quantity(max_quantity)
1532            .maybe_min_quantity(min_quantity)
1533            .maybe_max_notional(max_notional)
1534            .maybe_min_notional(min_notional)
1535            .maybe_max_price(max_price)
1536            .maybe_min_price(min_price)
1537            .maybe_margin_init(margin_init)
1538            .maybe_margin_maint(margin_maint)
1539            .maybe_maker_fee(maker_fee)
1540            .maybe_taker_fee(taker_fee)
1541            .ts_event(ts_event)
1542            .ts_init(ts_init)
1543            .build()
1544            .unwrap();
1545        Ok(Self(inst))
1546    }
1547}
1548
1549impl<'r> FromRow<'r, PgRow> for CryptoOptionSpreadRow {
1550    #[expect(
1551        clippy::too_many_lines,
1552        reason = "SQL row mapping mirrors the full crypto option spread constructor"
1553    )]
1554    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1555        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1556        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1557        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
1558        let quote_currency = row
1559            .try_get::<String, _>("quote_currency")
1560            .map(Currency::from)?;
1561        let settlement_currency = row
1562            .try_get::<String, _>("settlement_currency")
1563            .map(Currency::from)?;
1564        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
1565        let strategy_type = row
1566            .try_get::<String, _>("strategy_type")
1567            .map(|res| Ustr::from(res.as_str()))?;
1568        let activation_ns = row
1569            .try_get::<String, _>("activation_ns")
1570            .map(UnixNanos::from)?;
1571        let expiration_ns = row
1572            .try_get::<String, _>("expiration_ns")
1573            .map(UnixNanos::from)?;
1574        let price_precision = read_u8(row, "price_precision")?;
1575        let size_precision = read_u8(row, "size_precision")?;
1576        let price_increment = row
1577            .try_get::<String, _>("price_increment")
1578            .map(|res| Price::from_str(res.as_str()).unwrap())?;
1579        let size_increment = row
1580            .try_get::<String, _>("size_increment")
1581            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
1582        let multiplier = row
1583            .try_get::<String, _>("multiplier")
1584            .map(|res| Quantity::from(res.as_str()))?;
1585        let lot_size = row
1586            .try_get::<String, _>("lot_size")
1587            .map(|res| Quantity::from(res.as_str()))?;
1588        let max_quantity = row
1589            .try_get::<Option<String>, _>("max_quantity")
1590            .ok()
1591            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1592        let min_quantity = row
1593            .try_get::<Option<String>, _>("min_quantity")
1594            .ok()
1595            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
1596        let max_notional = row
1597            .try_get::<Option<String>, _>("max_notional")
1598            .ok()
1599            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1600        let min_notional = row
1601            .try_get::<Option<String>, _>("min_notional")
1602            .ok()
1603            .and_then(|res| res.map(|value| Money::from(value.as_str())));
1604        let max_price = row
1605            .try_get::<Option<String>, _>("max_price")
1606            .ok()
1607            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1608        let min_price = row
1609            .try_get::<Option<String>, _>("min_price")
1610            .ok()
1611            .and_then(|res| res.map(|value| Price::from(value.as_str())));
1612        let margin_init = row
1613            .try_get::<String, _>("margin_init")
1614            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1615        let margin_maint = row
1616            .try_get::<String, _>("margin_maint")
1617            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1618        let maker_fee = row
1619            .try_get::<String, _>("maker_fee")
1620            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1621        let taker_fee = row
1622            .try_get::<String, _>("taker_fee")
1623            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1624        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1625        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1626
1627        let inst = CryptoOptionSpread::builder()
1628            .instrument_id(id)
1629            .raw_symbol(raw_symbol)
1630            .underlying(underlying)
1631            .quote_currency(quote_currency)
1632            .settlement_currency(settlement_currency)
1633            .is_inverse(is_inverse)
1634            .strategy_type(strategy_type)
1635            .activation_ns(activation_ns)
1636            .expiration_ns(expiration_ns)
1637            .price_precision(price_precision)
1638            .size_precision(size_precision)
1639            .price_increment(price_increment)
1640            .size_increment(size_increment)
1641            .multiplier(multiplier)
1642            .lot_size(lot_size)
1643            .maybe_max_quantity(max_quantity)
1644            .maybe_min_quantity(min_quantity)
1645            .maybe_max_notional(max_notional)
1646            .maybe_min_notional(min_notional)
1647            .maybe_max_price(max_price)
1648            .maybe_min_price(min_price)
1649            .maybe_margin_init(margin_init)
1650            .maybe_margin_maint(margin_maint)
1651            .maybe_maker_fee(maker_fee)
1652            .maybe_taker_fee(taker_fee)
1653            .ts_event(ts_event)
1654            .ts_init(ts_init)
1655            .build()
1656            .unwrap();
1657        Ok(Self(inst))
1658    }
1659}
1660
1661impl<'r> FromRow<'r, PgRow> for TokenizedAssetRow {
1662    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
1663        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
1664        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
1665        let asset_class = row.try_get::<String, _>("asset_class").and_then(|res| {
1666            AssetClass::from_str(res.as_str()).map_err(|e| {
1667                sqlx::Error::Decode(format!("Invalid asset class '{res}': {e}").into())
1668            })
1669        })?;
1670        // Tokenized base currencies (e.g. AAPLx) are not in the static currency
1671        // map, so use get_or_create_crypto which registers them dynamically.
1672        let base_currency = row
1673            .try_get::<String, _>("base_currency")
1674            .map(|code| Currency::get_or_create_crypto(&code))?;
1675        let quote_currency = row
1676            .try_get::<String, _>("quote_currency")
1677            .map(Currency::from)?;
1678        let isin = row
1679            .try_get::<Option<String>, _>("isin")
1680            .ok()
1681            .and_then(|res| res.map(|s| Ustr::from(s.as_str())));
1682        let price_precision = read_u8(row, "price_precision")?;
1683        let size_precision = read_u8(row, "size_precision")?;
1684        let price_increment = row
1685            .try_get::<String, _>("price_increment")
1686            .map(|res| Price::from(res.as_str()))?;
1687        let size_increment = row
1688            .try_get::<String, _>("size_increment")
1689            .map(|res| Quantity::from(res.as_str()))?;
1690        let multiplier = row
1691            .try_get::<Option<String>, _>("multiplier")
1692            .ok()
1693            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
1694        let lot_size = row
1695            .try_get::<Option<String>, _>("lot_size")
1696            .ok()
1697            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
1698        let max_quantity = row
1699            .try_get::<Option<String>, _>("max_quantity")
1700            .ok()
1701            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
1702        let min_quantity = row
1703            .try_get::<Option<String>, _>("min_quantity")
1704            .ok()
1705            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
1706        let max_notional = row
1707            .try_get::<Option<String>, _>("max_notional")
1708            .ok()
1709            .and_then(|res| res.map(|res| Money::from(res.as_str())));
1710        let min_notional = row
1711            .try_get::<Option<String>, _>("min_notional")
1712            .ok()
1713            .and_then(|res| res.map(|res| Money::from(res.as_str())));
1714        let max_price = row
1715            .try_get::<Option<String>, _>("max_price")
1716            .ok()
1717            .and_then(|res| res.map(|res| Price::from(res.as_str())));
1718        let min_price = row
1719            .try_get::<Option<String>, _>("min_price")
1720            .ok()
1721            .and_then(|res| res.map(|res| Price::from(res.as_str())));
1722        let margin_init = row
1723            .try_get::<String, _>("margin_init")
1724            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1725        let margin_maint = row
1726            .try_get::<String, _>("margin_maint")
1727            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1728        let maker_fee = row
1729            .try_get::<String, _>("maker_fee")
1730            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1731        let taker_fee = row
1732            .try_get::<String, _>("taker_fee")
1733            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1734        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1735        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1736
1737        let inst = TokenizedAsset::builder()
1738            .instrument_id(id)
1739            .raw_symbol(raw_symbol)
1740            .asset_class(asset_class)
1741            .base_currency(base_currency)
1742            .quote_currency(quote_currency)
1743            .maybe_isin(isin)
1744            .price_precision(price_precision)
1745            .size_precision(size_precision)
1746            .price_increment(price_increment)
1747            .size_increment(size_increment)
1748            .maybe_multiplier(multiplier)
1749            .maybe_lot_size(lot_size)
1750            .maybe_max_quantity(max_quantity)
1751            .maybe_min_quantity(min_quantity)
1752            .maybe_max_notional(max_notional)
1753            .maybe_min_notional(min_notional)
1754            .maybe_max_price(max_price)
1755            .maybe_min_price(min_price)
1756            .maybe_margin_init(margin_init)
1757            .maybe_margin_maint(margin_maint)
1758            .maybe_maker_fee(maker_fee)
1759            .maybe_taker_fee(taker_fee)
1760            .ts_event(ts_event)
1761            .ts_init(ts_init)
1762            .build()
1763            .unwrap();
1764        Ok(Self(inst))
1765    }
1766}