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