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