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