Skip to main content

nautilus_infrastructure/sql/models/
instruments.rs

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