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