nautilus_infrastructure/sql/models/
instruments.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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::OptionKind,
25    identifiers::{InstrumentId, Symbol},
26    instruments::{
27        BettingInstrument, BinaryOption, CryptoFuture, CryptoOption, CryptoPerpetual, CurrencyPair,
28        Equity, FuturesContract, FuturesSpread, InstrumentAny, OptionContract, OptionSpread,
29    },
30    types::{Currency, Money, Price, Quantity},
31};
32use rust_decimal::Decimal;
33use sqlx::{FromRow, Row, postgres::PgRow};
34use ustr::Ustr;
35
36use crate::sql::models::enums::AssetClassModel;
37
38#[derive(Debug)]
39pub struct InstrumentAnyModel(pub InstrumentAny);
40
41#[derive(Debug)]
42pub struct BettingInstrumentModel(pub BettingInstrument);
43
44#[derive(Debug)]
45pub struct BinaryOptionModel(pub BinaryOption);
46
47#[derive(Debug)]
48pub struct CryptoFutureModel(pub CryptoFuture);
49
50#[derive(Debug)]
51pub struct CryptoOptionModel(pub CryptoOption);
52
53#[derive(Debug)]
54pub struct CryptoPerpetualModel(pub CryptoPerpetual);
55
56#[derive(Debug)]
57pub struct CurrencyPairModel(pub CurrencyPair);
58
59#[derive(Debug)]
60pub struct EquityModel(pub Equity);
61
62#[derive(Debug)]
63pub struct FuturesContractModel(pub FuturesContract);
64
65#[derive(Debug)]
66pub struct FuturesSpreadModel(pub FuturesSpread);
67
68#[derive(Debug)]
69pub struct OptionContractModel(pub OptionContract);
70
71#[derive(Debug)]
72pub struct OptionSpreadModel(pub OptionSpread);
73
74impl<'r> FromRow<'r, PgRow> for InstrumentAnyModel {
75    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
76        let kind = row.get::<String, _>("kind");
77        if kind == "BETTING" {
78            Ok(Self(InstrumentAny::Betting(
79                BettingInstrumentModel::from_row(row).unwrap().0,
80            )))
81        } else if kind == "BINARY_OPTION" {
82            Ok(Self(InstrumentAny::BinaryOption(
83                BinaryOptionModel::from_row(row).unwrap().0,
84            )))
85        } else if kind == "CRYPTO_FUTURE" {
86            Ok(Self(InstrumentAny::CryptoFuture(
87                CryptoFutureModel::from_row(row).unwrap().0,
88            )))
89        } else if kind == "CRYPTO_OPTION" {
90            Ok(Self(InstrumentAny::CryptoOption(
91                CryptoOptionModel::from_row(row).unwrap().0,
92            )))
93        } else if kind == "CRYPTO_PERPETUAL" {
94            Ok(Self(InstrumentAny::CryptoPerpetual(
95                CryptoPerpetualModel::from_row(row).unwrap().0,
96            )))
97        } else if kind == "CURRENCY_PAIR" {
98            Ok(Self(InstrumentAny::CurrencyPair(
99                CurrencyPairModel::from_row(row).unwrap().0,
100            )))
101        } else if kind == "EQUITY" {
102            Ok(Self(InstrumentAny::Equity(
103                EquityModel::from_row(row).unwrap().0,
104            )))
105        } else if kind == "FUTURES_CONTRACT" {
106            Ok(Self(InstrumentAny::FuturesContract(
107                FuturesContractModel::from_row(row).unwrap().0,
108            )))
109        } else if kind == "FUTURES_SPREAD" {
110            Ok(Self(InstrumentAny::FuturesSpread(
111                FuturesSpreadModel::from_row(row).unwrap().0,
112            )))
113        } else if kind == "OPTION_CONTRACT" {
114            Ok(Self(InstrumentAny::OptionContract(
115                OptionContractModel::from_row(row).unwrap().0,
116            )))
117        } else if kind == "OPTION_SPREAD" {
118            Ok(Self(InstrumentAny::OptionSpread(
119                OptionSpreadModel::from_row(row).unwrap().0,
120            )))
121        } else {
122            Err(sqlx::Error::Decode(
123                format!("Unknown instrument type: {kind}").into(),
124            ))
125        }
126    }
127}
128
129// TODO: New/updated schema required to support betting instrument loading
130impl<'r> FromRow<'r, PgRow> for BettingInstrumentModel {
131    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
132        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
133        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
134        let event_type_id = row.try_get::<i64, _>("event_type_id")? as u64;
135        let event_type_name = row
136            .try_get::<String, _>("event_type_name")
137            .map(|res| Ustr::from(res.as_str()))?;
138        let competition_id = row.try_get::<i64, _>("competition_id")? as u64;
139        let competition_name = row
140            .try_get::<String, _>("competition_name")
141            .map(|res| Ustr::from(res.as_str()))?;
142        let event_id = row.try_get::<i64, _>("event_id")? as u64;
143        let event_name = row
144            .try_get::<String, _>("event_name")
145            .map(|res| Ustr::from(res.as_str()))?;
146        let event_country_code = row
147            .try_get::<String, _>("event_country_code")
148            .map(|res| Ustr::from(res.as_str()))?;
149        let event_open_date = row
150            .try_get::<String, _>("event_open_date")
151            .map(UnixNanos::from)?;
152        let betting_type = row
153            .try_get::<String, _>("betting_type")
154            .map(|res| Ustr::from(res.as_str()))?;
155        let market_id = row
156            .try_get::<String, _>("market_id")
157            .map(|res| Ustr::from(res.as_str()))?;
158        let market_name = row
159            .try_get::<String, _>("market_name")
160            .map(|res| Ustr::from(res.as_str()))?;
161        let market_type = row
162            .try_get::<String, _>("market_type")
163            .map(|res| Ustr::from(res.as_str()))?;
164        let market_start_time = row
165            .try_get::<String, _>("market_start_time")
166            .map(UnixNanos::from)?;
167        let selection_id = row.try_get::<i64, _>("selection_id")? as u64;
168        let selection_name = row
169            .try_get::<String, _>("selection_name")
170            .map(|res| Ustr::from(res.as_str()))?;
171        let selection_handicap = row.try_get::<f64, _>("selection_handicap")?;
172        let currency = row
173            .try_get::<String, _>("quote_currency")
174            .map(Currency::from)?;
175        let price_precision = row.try_get::<i32, _>("price_precision")? as u8;
176        let size_precision = row.try_get::<i32, _>("size_precision")? as u8;
177        let price_increment = row
178            .try_get::<String, _>("price_increment")
179            .map(Price::from)?;
180        let size_increment = row
181            .try_get::<String, _>("size_increment")
182            .map(Quantity::from)?;
183        let max_quantity = row
184            .try_get::<Option<String>, _>("max_quantity")
185            .ok()
186            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
187        let min_quantity = row
188            .try_get::<Option<String>, _>("min_quantity")
189            .ok()
190            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
191        let max_notional = row
192            .try_get::<Option<String>, _>("max_notional")
193            .ok()
194            .and_then(|res| res.map(|value| Money::from(value.as_str())));
195        let min_notional = row
196            .try_get::<Option<String>, _>("min_notional")
197            .ok()
198            .and_then(|res| res.map(|value| Money::from(value.as_str())));
199        let max_price = row
200            .try_get::<Option<String>, _>("max_price")
201            .ok()
202            .and_then(|res| res.map(|value| Price::from(value.as_str())));
203        let min_price = row
204            .try_get::<Option<String>, _>("min_price")
205            .ok()
206            .and_then(|res| res.map(|value| Price::from(value.as_str())));
207        let margin_init = row
208            .try_get::<String, _>("margin_init")
209            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
210        let margin_maint = row
211            .try_get::<String, _>("margin_maint")
212            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
213        let maker_fee = row
214            .try_get::<String, _>("maker_fee")
215            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
216        let taker_fee = row
217            .try_get::<String, _>("taker_fee")
218            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
219        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
220        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
221
222        let inst = BettingInstrument::new(
223            id,
224            raw_symbol,
225            event_type_id,
226            event_type_name,
227            competition_id,
228            competition_name,
229            event_id,
230            event_name,
231            event_country_code,
232            event_open_date,
233            betting_type,
234            market_id,
235            market_name,
236            market_type,
237            market_start_time,
238            selection_id,
239            selection_name,
240            selection_handicap,
241            currency,
242            price_precision,
243            size_precision,
244            price_increment,
245            size_increment,
246            max_quantity,
247            min_quantity,
248            max_notional,
249            min_notional,
250            max_price,
251            min_price,
252            margin_init,
253            margin_maint,
254            maker_fee,
255            taker_fee,
256            ts_event,
257            ts_init,
258        );
259        Ok(Self(inst))
260    }
261}
262
263impl<'r> FromRow<'r, PgRow> for BinaryOptionModel {
264    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
265        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
266        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
267        let asset_class = row
268            .try_get::<AssetClassModel, _>("asset_class")
269            .map(|res| res.0)?;
270        let currency = row
271            .try_get::<String, _>("quote_currency")
272            .map(Currency::from)?;
273        let activation_ns = row
274            .try_get::<String, _>("activation_ns")
275            .map(UnixNanos::from)?;
276        let expiration_ns = row
277            .try_get::<String, _>("expiration_ns")
278            .map(UnixNanos::from)?;
279        let price_precision = row.try_get::<i32, _>("price_precision")? as u8;
280        let size_precision = row.try_get::<i32, _>("size_precision")? as u8;
281        let price_increment = row
282            .try_get::<String, _>("price_increment")
283            .map(|res| Price::from_str(res.as_str()).unwrap())?;
284        let size_increment = row
285            .try_get::<String, _>("size_increment")
286            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
287        let outcome = row
288            .try_get::<Option<String>, _>("outcome")
289            .ok()
290            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
291        let description = row
292            .try_get::<Option<String>, _>("description")
293            .ok()
294            .and_then(|res| res.map(|value| Ustr::from(value.as_str())));
295        let max_quantity = row
296            .try_get::<Option<String>, _>("max_quantity")
297            .ok()
298            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
299        let min_quantity = row
300            .try_get::<Option<String>, _>("min_quantity")
301            .ok()
302            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
303        let max_notional = row
304            .try_get::<Option<String>, _>("max_notional")
305            .ok()
306            .and_then(|res| res.map(|value| Money::from(value.as_str())));
307        let min_notional = row
308            .try_get::<Option<String>, _>("min_notional")
309            .ok()
310            .and_then(|res| res.map(|value| Money::from(value.as_str())));
311        let max_price = row
312            .try_get::<Option<String>, _>("max_price")
313            .ok()
314            .and_then(|res| res.map(|value| Price::from(value.as_str())));
315        let min_price = row
316            .try_get::<Option<String>, _>("min_price")
317            .ok()
318            .and_then(|res| res.map(|value| Price::from(value.as_str())));
319        let margin_init = row
320            .try_get::<String, _>("margin_init")
321            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
322        let margin_maint = row
323            .try_get::<String, _>("margin_maint")
324            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
325        let maker_fee = row
326            .try_get::<String, _>("maker_fee")
327            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
328        let taker_fee = row
329            .try_get::<String, _>("taker_fee")
330            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
331        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
332        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
333
334        let inst = BinaryOption::new(
335            id,
336            raw_symbol,
337            asset_class,
338            currency,
339            activation_ns,
340            expiration_ns,
341            price_precision,
342            size_precision,
343            price_increment,
344            size_increment,
345            outcome,
346            description,
347            max_quantity,
348            min_quantity,
349            max_notional,
350            min_notional,
351            max_price,
352            min_price,
353            margin_init,
354            margin_maint,
355            maker_fee,
356            taker_fee,
357            ts_event,
358            ts_init,
359        );
360        Ok(Self(inst))
361    }
362}
363
364impl<'r> FromRow<'r, PgRow> for CryptoFutureModel {
365    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
366        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
367        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
368        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
369        let quote_currency = row
370            .try_get::<String, _>("quote_currency")
371            .map(Currency::from)?;
372        let settlement_currency = row
373            .try_get::<String, _>("settlement_currency")
374            .map(Currency::from)?;
375        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
376        let activation_ns = row
377            .try_get::<String, _>("activation_ns")
378            .map(UnixNanos::from)?;
379        let expiration_ns = row
380            .try_get::<String, _>("expiration_ns")
381            .map(UnixNanos::from)?;
382        let price_precision = row.try_get::<i32, _>("price_precision")?;
383        let size_precision = row.try_get::<i32, _>("size_precision")?;
384        let price_increment = row
385            .try_get::<String, _>("price_increment")
386            .map(|res| Price::from_str(res.as_str()).unwrap())?;
387        let size_increment = row
388            .try_get::<String, _>("size_increment")
389            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
390        let multiplier = row
391            .try_get::<String, _>("multiplier")
392            .map(|res| Quantity::from(res.as_str()))?;
393        let lot_size = row
394            .try_get::<String, _>("lot_size")
395            .map(|res| Quantity::from(res.as_str()))?;
396        let max_quantity = row
397            .try_get::<Option<String>, _>("max_quantity")
398            .ok()
399            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
400        let min_quantity = row
401            .try_get::<Option<String>, _>("min_quantity")
402            .ok()
403            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
404        let max_notional = row
405            .try_get::<Option<String>, _>("max_notional")
406            .ok()
407            .and_then(|res| res.map(|value| Money::from(value.as_str())));
408        let min_notional = row
409            .try_get::<Option<String>, _>("min_notional")
410            .ok()
411            .and_then(|res| res.map(|value| Money::from(value.as_str())));
412        let max_price = row
413            .try_get::<Option<String>, _>("max_price")
414            .ok()
415            .and_then(|res| res.map(|value| Price::from(value.as_str())));
416        let min_price = row
417            .try_get::<Option<String>, _>("min_price")
418            .ok()
419            .and_then(|res| res.map(|value| Price::from(value.as_str())));
420        let margin_init = row
421            .try_get::<String, _>("margin_init")
422            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
423        let margin_maint = row
424            .try_get::<String, _>("margin_maint")
425            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
426        let maker_fee = row
427            .try_get::<String, _>("maker_fee")
428            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
429        let taker_fee = row
430            .try_get::<String, _>("taker_fee")
431            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
432        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
433        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
434
435        let inst = CryptoFuture::new(
436            id,
437            raw_symbol,
438            underlying,
439            quote_currency,
440            settlement_currency,
441            is_inverse,
442            activation_ns,
443            expiration_ns,
444            price_precision as u8,
445            size_precision as u8,
446            price_increment,
447            size_increment,
448            Some(multiplier),
449            Some(lot_size),
450            max_quantity,
451            min_quantity,
452            max_notional,
453            min_notional,
454            max_price,
455            min_price,
456            margin_init,
457            margin_maint,
458            maker_fee,
459            taker_fee,
460            ts_event,
461            ts_init,
462        );
463        Ok(Self(inst))
464    }
465}
466
467impl<'r> FromRow<'r, PgRow> for CryptoOptionModel {
468    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
469        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
470        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
471        let underlying = row.try_get::<String, _>("underlying").map(Currency::from)?;
472        let quote_currency = row
473            .try_get::<String, _>("quote_currency")
474            .map(Currency::from)?;
475        let settlement_currency = row
476            .try_get::<String, _>("settlement_currency")
477            .map(Currency::from)?;
478        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
479        let option_kind = row
480            .try_get::<String, _>("option_kind")
481            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
482        let strike_price = row
483            .try_get::<String, _>("strike_price")
484            .map(|res| Price::from_str(res.as_str()).unwrap())?;
485        let activation_ns = row
486            .try_get::<String, _>("activation_ns")
487            .map(UnixNanos::from)?;
488        let expiration_ns = row
489            .try_get::<String, _>("expiration_ns")
490            .map(UnixNanos::from)?;
491        let price_precision = row.try_get::<i32, _>("price_precision")?;
492        let size_precision = row.try_get::<i32, _>("size_precision")?;
493        let price_increment = row
494            .try_get::<String, _>("price_increment")
495            .map(|res| Price::from_str(res.as_str()).unwrap())?;
496        let size_increment = row
497            .try_get::<String, _>("size_increment")
498            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
499        let multiplier = row
500            .try_get::<String, _>("multiplier")
501            .map(|res| Quantity::from(res.as_str()))?;
502        let lot_size = row
503            .try_get::<String, _>("lot_size")
504            .map(|res| Quantity::from(res.as_str()))?;
505        let max_quantity = row
506            .try_get::<Option<String>, _>("max_quantity")
507            .ok()
508            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
509        let min_quantity = row
510            .try_get::<Option<String>, _>("min_quantity")
511            .ok()
512            .and_then(|res| res.map(|value| Quantity::from(value.as_str())));
513        let max_notional = row
514            .try_get::<Option<String>, _>("max_notional")
515            .ok()
516            .and_then(|res| res.map(|value| Money::from(value.as_str())));
517        let min_notional = row
518            .try_get::<Option<String>, _>("min_notional")
519            .ok()
520            .and_then(|res| res.map(|value| Money::from(value.as_str())));
521        let max_price = row
522            .try_get::<Option<String>, _>("max_price")
523            .ok()
524            .and_then(|res| res.map(|value| Price::from(value.as_str())));
525        let min_price = row
526            .try_get::<Option<String>, _>("min_price")
527            .ok()
528            .and_then(|res| res.map(|value| Price::from(value.as_str())));
529        let margin_init = row
530            .try_get::<String, _>("margin_init")
531            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
532        let margin_maint = row
533            .try_get::<String, _>("margin_maint")
534            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
535        let maker_fee = row
536            .try_get::<String, _>("maker_fee")
537            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
538        let taker_fee = row
539            .try_get::<String, _>("taker_fee")
540            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
541        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
542        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
543
544        let inst = CryptoOption::new(
545            id,
546            raw_symbol,
547            underlying,
548            quote_currency,
549            settlement_currency,
550            is_inverse,
551            option_kind,
552            strike_price,
553            activation_ns,
554            expiration_ns,
555            price_precision as u8,
556            size_precision as u8,
557            price_increment,
558            size_increment,
559            Some(multiplier),
560            Some(lot_size),
561            max_quantity,
562            min_quantity,
563            max_notional,
564            min_notional,
565            max_price,
566            min_price,
567            margin_init,
568            margin_maint,
569            maker_fee,
570            taker_fee,
571            ts_event,
572            ts_init,
573        );
574        Ok(Self(inst))
575    }
576}
577
578impl<'r> FromRow<'r, PgRow> for CryptoPerpetualModel {
579    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
580        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
581        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
582        let base_currency = row
583            .try_get::<String, _>("base_currency")
584            .map(Currency::from)?;
585        let quote_currency = row
586            .try_get::<String, _>("quote_currency")
587            .map(Currency::from)?;
588        let settlement_currency = row
589            .try_get::<String, _>("settlement_currency")
590            .map(Currency::from)?;
591        let is_inverse = row.try_get::<bool, _>("is_inverse")?;
592        let price_precision = row.try_get::<i32, _>("price_precision")?;
593        let size_precision = row.try_get::<i32, _>("size_precision")?;
594        let price_increment = row
595            .try_get::<String, _>("price_increment")
596            .map(|res| Price::from_str(res.as_str()).unwrap())?;
597        let size_increment = row
598            .try_get::<String, _>("size_increment")
599            .map(|res| Quantity::from_str(res.as_str()).unwrap())?;
600        let multiplier = row
601            .try_get::<String, _>("multiplier")
602            .map(|res| Quantity::from(res.as_str()))?;
603        let lot_size = row
604            .try_get::<String, _>("lot_size")
605            .map(|res| Quantity::from(res.as_str()))?;
606        let max_quantity = row
607            .try_get::<Option<String>, _>("max_quantity")
608            .ok()
609            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
610        let min_quantity = row
611            .try_get::<Option<String>, _>("min_quantity")
612            .ok()
613            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
614        let max_notional = row
615            .try_get::<Option<String>, _>("max_notional")
616            .ok()
617            .and_then(|res| res.map(|res| Money::from(res.as_str())));
618        let min_notional = row
619            .try_get::<Option<String>, _>("min_notional")
620            .ok()
621            .and_then(|res| res.map(|res| Money::from(res.as_str())));
622        let max_price = row
623            .try_get::<Option<String>, _>("max_price")
624            .ok()
625            .and_then(|res| res.map(|res| Price::from(res.as_str())));
626        let min_price = row
627            .try_get::<Option<String>, _>("min_price")
628            .ok()
629            .and_then(|res| res.map(|res| Price::from(res.as_str())));
630        let margin_init = row
631            .try_get::<String, _>("margin_init")
632            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
633        let margin_maint = row
634            .try_get::<String, _>("margin_maint")
635            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
636        let maker_fee = row
637            .try_get::<String, _>("maker_fee")
638            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
639        let taker_fee = row
640            .try_get::<String, _>("taker_fee")
641            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
642        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
643        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
644        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
645
646        let inst = CryptoPerpetual::new(
647            id,
648            raw_symbol,
649            base_currency,
650            quote_currency,
651            settlement_currency,
652            is_inverse,
653            price_precision as u8,
654            size_precision as u8,
655            price_increment,
656            size_increment,
657            Some(multiplier),
658            Some(lot_size),
659            max_quantity,
660            min_quantity,
661            max_notional,
662            min_notional,
663            max_price,
664            min_price,
665            margin_init,
666            margin_maint,
667            maker_fee,
668            taker_fee,
669            ts_event,
670            ts_init,
671        );
672        Ok(Self(inst))
673    }
674}
675
676impl<'r> FromRow<'r, PgRow> for CurrencyPairModel {
677    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
678        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
679        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
680        let base_currency = row
681            .try_get::<String, _>("base_currency")
682            .map(Currency::from)?;
683        let quote_currency = row
684            .try_get::<String, _>("quote_currency")
685            .map(Currency::from)?;
686        let price_precision = row.try_get::<i32, _>("price_precision")?;
687        let size_precision = row.try_get::<i32, _>("size_precision")?;
688        let price_increment = row
689            .try_get::<String, _>("price_increment")
690            .map(|res| Price::from(res.as_str()))?;
691        let size_increment = row
692            .try_get::<String, _>("size_increment")
693            .map(|res| Quantity::from(res.as_str()))?;
694        let multiplier = row
695            .try_get::<Option<String>, _>("multiplier")
696            .ok()
697            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
698        let lot_size = row
699            .try_get::<Option<String>, _>("lot_size")
700            .ok()
701            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
702        let max_quantity = row
703            .try_get::<Option<String>, _>("max_quantity")
704            .ok()
705            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
706        let min_quantity = row
707            .try_get::<Option<String>, _>("min_quantity")
708            .ok()
709            .and_then(|res| res.map(|res| Quantity::from(res.as_str())));
710        let max_notional = row
711            .try_get::<Option<String>, _>("max_notional")
712            .ok()
713            .and_then(|res| res.map(|res| Money::from(res.as_str())));
714        let min_notional = row
715            .try_get::<Option<String>, _>("min_notional")
716            .ok()
717            .and_then(|res| res.map(|res| Money::from(res.as_str())));
718        let max_price = row
719            .try_get::<Option<String>, _>("max_price")
720            .ok()
721            .and_then(|res| res.map(|res| Price::from(res.as_str())));
722        let min_price = row
723            .try_get::<Option<String>, _>("min_price")
724            .ok()
725            .and_then(|res| res.map(|res| Price::from(res.as_str())));
726        let margin_init = row
727            .try_get::<String, _>("margin_init")
728            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
729        let margin_maint = row
730            .try_get::<String, _>("margin_maint")
731            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
732        let maker_fee = row
733            .try_get::<String, _>("maker_fee")
734            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
735        let taker_fee = row
736            .try_get::<String, _>("taker_fee")
737            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
738        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
739        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
740
741        let inst = CurrencyPair::new(
742            id,
743            raw_symbol,
744            base_currency,
745            quote_currency,
746            price_precision as u8,
747            size_precision as u8,
748            price_increment,
749            size_increment,
750            multiplier,
751            lot_size,
752            max_quantity,
753            min_quantity,
754            max_notional,
755            min_notional,
756            max_price,
757            min_price,
758            margin_init,
759            margin_maint,
760            maker_fee,
761            taker_fee,
762            ts_event,
763            ts_init,
764        );
765        Ok(Self(inst))
766    }
767}
768
769impl<'r> FromRow<'r, PgRow> for EquityModel {
770    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
771        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
772        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::from)?;
773        let isin = row
774            .try_get::<Option<String>, _>("isin")
775            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
776        let currency = row
777            .try_get::<String, _>("quote_currency")
778            .map(Currency::from)?;
779        let price_precision = row.try_get::<i32, _>("price_precision")?;
780        let price_increment = row
781            .try_get::<String, _>("price_increment")
782            .map(|res| Price::from_str(res.as_str()).unwrap())?;
783        let lot_size = row
784            .try_get::<Option<String>, _>("lot_size")
785            .map(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()))?;
786        let max_quantity = row
787            .try_get::<Option<String>, _>("max_quantity")
788            .ok()
789            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
790        let min_quantity = row
791            .try_get::<Option<String>, _>("min_quantity")
792            .ok()
793            .and_then(|res| res.map(|s| Quantity::from_str(s.as_str()).unwrap()));
794        let max_price = row
795            .try_get::<Option<String>, _>("max_price")
796            .ok()
797            .and_then(|res| res.map(|s| Price::from(s.as_str())));
798        let min_price = row
799            .try_get::<Option<String>, _>("min_price")
800            .ok()
801            .and_then(|res| res.map(|s| Price::from(s.as_str())));
802        let margin_init = row
803            .try_get::<String, _>("margin_init")
804            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
805        let margin_maint = row
806            .try_get::<String, _>("margin_maint")
807            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
808        let maker_fee = row
809            .try_get::<String, _>("maker_fee")
810            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
811        let taker_fee = row
812            .try_get::<String, _>("taker_fee")
813            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
814        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
815        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
816
817        let inst = Equity::new(
818            id,
819            raw_symbol,
820            isin,
821            currency,
822            price_precision as u8,
823            price_increment,
824            lot_size,
825            max_quantity,
826            min_quantity,
827            max_price,
828            min_price,
829            margin_init,
830            margin_maint,
831            maker_fee,
832            taker_fee,
833            ts_event,
834            ts_init,
835        );
836        Ok(Self(inst))
837    }
838}
839
840impl<'r> FromRow<'r, PgRow> for FuturesContractModel {
841    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
842        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
843        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
844        let asset_class = row
845            .try_get::<AssetClassModel, _>("asset_class")
846            .map(|res| res.0)?;
847        let exchange = row
848            .try_get::<Option<String>, _>("exchange")
849            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
850        let underlying = row
851            .try_get::<String, _>("underlying")
852            .map(|res| Ustr::from(res.as_str()))?;
853        let currency = row
854            .try_get::<String, _>("quote_currency")
855            .map(Currency::from)?;
856        let activation_ns = row
857            .try_get::<String, _>("activation_ns")
858            .map(UnixNanos::from)?;
859        let expiration_ns = row
860            .try_get::<String, _>("expiration_ns")
861            .map(UnixNanos::from)?;
862        let price_precision = row.try_get::<i32, _>("price_precision")?;
863        let price_increment = row
864            .try_get::<String, _>("price_increment")
865            .map(|res| Price::from(res.as_str()))?;
866        let multiplier = row
867            .try_get::<String, _>("multiplier")
868            .map(|res| Quantity::from(res.as_str()))?;
869        let lot_size = row
870            .try_get::<String, _>("lot_size")
871            .map(|res| Quantity::from(res.as_str()))?;
872        let max_quantity = row
873            .try_get::<Option<String>, _>("max_quantity")
874            .ok()
875            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
876        let min_quantity = row
877            .try_get::<Option<String>, _>("min_quantity")
878            .ok()
879            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
880        let max_price = row
881            .try_get::<Option<String>, _>("max_price")
882            .ok()
883            .and_then(|res| res.map(|s| Price::from(s.as_str())));
884        let min_price = row
885            .try_get::<Option<String>, _>("min_price")
886            .ok()
887            .and_then(|res| res.map(|s| Price::from(s.as_str())));
888        let margin_init = row
889            .try_get::<String, _>("margin_init")
890            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
891        let margin_maint = row
892            .try_get::<String, _>("margin_maint")
893            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
894        let maker_fee = row
895            .try_get::<String, _>("maker_fee")
896            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
897        let taker_fee = row
898            .try_get::<String, _>("taker_fee")
899            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
900        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
901        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
902
903        let inst = FuturesContract::new(
904            id,
905            raw_symbol,
906            asset_class,
907            exchange,
908            underlying,
909            activation_ns,
910            expiration_ns,
911            currency,
912            price_precision as u8,
913            price_increment,
914            multiplier,
915            lot_size,
916            max_quantity,
917            min_quantity,
918            max_price,
919            min_price,
920            margin_init,
921            margin_maint,
922            maker_fee,
923            taker_fee,
924            ts_event,
925            ts_init,
926        );
927        Ok(Self(inst))
928    }
929}
930
931impl<'r> FromRow<'r, PgRow> for FuturesSpreadModel {
932    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
933        todo!("Implement FromRow for FuturesSpread")
934    }
935}
936
937impl<'r> FromRow<'r, PgRow> for OptionContractModel {
938    fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
939        let id = row.try_get::<String, _>("id").map(InstrumentId::from)?;
940        let raw_symbol = row.try_get::<String, _>("raw_symbol").map(Symbol::new)?;
941        let asset_class = row
942            .try_get::<AssetClassModel, _>("asset_class")
943            .map(|res| res.0)?;
944        let exchange = row
945            .try_get::<Option<String>, _>("exchange")
946            .map(|res| res.map(|s| Ustr::from(s.as_str())))?;
947        let underlying = row
948            .try_get::<String, _>("underlying")
949            .map(|res| Ustr::from(res.as_str()))?;
950        let option_kind = row
951            .try_get::<String, _>("option_kind")
952            .map(|res| OptionKind::from_str(res.as_str()).unwrap())?;
953        let activation_ns = row
954            .try_get::<String, _>("activation_ns")
955            .map(UnixNanos::from)?;
956        let expiration_ns = row
957            .try_get::<String, _>("expiration_ns")
958            .map(UnixNanos::from)?;
959        let strike_price = row
960            .try_get::<String, _>("strike_price")
961            .map(|res| Price::from_str(res.as_str()).unwrap())?;
962        let currency = row
963            .try_get::<String, _>("quote_currency")
964            .map(Currency::from)?;
965        let price_precision = row.try_get::<i32, _>("price_precision").unwrap();
966        let price_increment = row
967            .try_get::<String, _>("price_increment")
968            .map(|res| Price::from_str(res.as_str()).unwrap())?;
969        let multiplier = row
970            .try_get::<String, _>("multiplier")
971            .map(|res| Quantity::from(res.as_str()))?;
972        let lot_size = row
973            .try_get::<String, _>("lot_size")
974            .map(|res| Quantity::from(res.as_str()))
975            .unwrap();
976        let max_quantity = row
977            .try_get::<Option<String>, _>("max_quantity")
978            .ok()
979            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
980        let min_quantity = row
981            .try_get::<Option<String>, _>("min_quantity")
982            .ok()
983            .and_then(|res| res.map(|s| Quantity::from(s.as_str())));
984        let max_price = row
985            .try_get::<Option<String>, _>("max_price")
986            .ok()
987            .and_then(|res| res.map(|s| Price::from(s.as_str())));
988        let min_price = row
989            .try_get::<Option<String>, _>("min_price")
990            .ok()
991            .and_then(|res| res.map(|s| Price::from(s.as_str())));
992        let margin_init = row
993            .try_get::<String, _>("margin_init")
994            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
995        let margin_maint = row
996            .try_get::<String, _>("margin_maint")
997            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
998        let maker_fee = row
999            .try_get::<String, _>("maker_fee")
1000            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1001        let taker_fee = row
1002            .try_get::<String, _>("taker_fee")
1003            .map(|res| Some(Decimal::from_str(res.as_str()).unwrap()))?;
1004        let ts_event = row.try_get::<String, _>("ts_event").map(UnixNanos::from)?;
1005        let ts_init = row.try_get::<String, _>("ts_init").map(UnixNanos::from)?;
1006
1007        let inst = OptionContract::new(
1008            id,
1009            raw_symbol,
1010            asset_class,
1011            exchange,
1012            underlying,
1013            option_kind,
1014            strike_price,
1015            currency,
1016            activation_ns,
1017            expiration_ns,
1018            price_precision as u8,
1019            price_increment,
1020            multiplier,
1021            lot_size,
1022            max_quantity,
1023            min_quantity,
1024            max_price,
1025            min_price,
1026            margin_init,
1027            margin_maint,
1028            maker_fee,
1029            taker_fee,
1030            ts_event,
1031            ts_init,
1032        );
1033        Ok(Self(inst))
1034    }
1035}
1036
1037impl<'r> FromRow<'r, PgRow> for OptionSpreadModel {
1038    fn from_row(_row: &'r PgRow) -> Result<Self, sqlx::Error> {
1039        todo!("Implement FromRow for OptionSpread")
1040    }
1041}