1use std::convert::Infallible;
2use std::fmt;
3use std::num::NonZeroU32;
4use std::str::FromStr;
5
6use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
7use thiserror::Error;
8
9#[derive(Debug, Error, Clone, PartialEq, Eq)]
10pub enum ParseIndexError {
12 #[error("index id must not be empty")]
14 EmptyIndexId,
15 #[error("index short name must not be empty")]
17 EmptyShortName,
18 #[error("invalid index date range: from={from} is after till={till}")]
20 InvalidDateRange {
21 from: NaiveDate,
23 till: NaiveDate,
25 },
26}
27
28impl From<Infallible> for ParseIndexError {
29 fn from(value: Infallible) -> Self {
30 match value {}
31 }
32}
33
34#[derive(Debug, Error, Clone, PartialEq, Eq)]
35pub enum ParseHistoryDatesError {
37 #[error("invalid history dates range: from={from} is after till={till}")]
39 InvalidDateRange {
40 from: NaiveDate,
42 till: NaiveDate,
44 },
45}
46
47#[derive(Debug, Error, Clone, PartialEq, Eq)]
48pub enum ParseHistoryRecordError {
50 #[error(transparent)]
52 InvalidBoardId(#[from] ParseBoardIdError),
53 #[error(transparent)]
55 InvalidSecId(#[from] ParseSecIdError),
56 #[error("history numtrades must not be negative, got {0}")]
58 NegativeNumTrades(i64),
59 #[error("history volume must not be negative, got {0}")]
61 NegativeVolume(i64),
62}
63
64#[derive(Debug, Error, Clone, PartialEq, Eq)]
65pub enum ParseTurnoverError {
67 #[error("turnover name must not be empty")]
69 EmptyName,
70 #[error("turnover id must be positive, got {0}")]
72 NonPositiveId(i64),
73 #[error("turnover id is out of range for u32, got {0}")]
75 IdOutOfRange(i64),
76 #[error("turnover numtrades must not be negative, got {0}")]
78 NegativeNumTrades(i64),
79 #[error("turnover title must not be empty")]
81 EmptyTitle,
82}
83
84#[derive(Debug, Error, Clone, PartialEq, Eq)]
85pub enum ParseSecStatError {
87 #[error(transparent)]
89 InvalidSecId(#[from] ParseSecIdError),
90 #[error(transparent)]
92 InvalidBoardId(#[from] ParseBoardIdError),
93 #[error("secstats voltoday must not be negative, got {0}")]
95 NegativeVolToday(i64),
96 #[error("secstats numtrades must not be negative, got {0}")]
98 NegativeNumTrades(i64),
99}
100
101#[derive(Debug, Error, Clone, PartialEq, Eq)]
102pub enum ParseSiteNewsError {
104 #[error("sitenews id must be positive, got {0}")]
106 NonPositiveId(i64),
107 #[error("sitenews id is out of range for u64, got {0}")]
109 IdOutOfRange(i64),
110 #[error("sitenews tag must not be empty")]
112 EmptyTag,
113 #[error("sitenews title must not be empty")]
115 EmptyTitle,
116}
117
118#[derive(Debug, Error, Clone, PartialEq, Eq)]
119pub enum ParseEventError {
121 #[error("events id must be positive, got {0}")]
123 NonPositiveId(i64),
124 #[error("events id is out of range for u64, got {0}")]
126 IdOutOfRange(i64),
127 #[error("events tag must not be empty")]
129 EmptyTag,
130 #[error("events title must not be empty")]
132 EmptyTitle,
133}
134
135#[derive(Debug, Error, Clone, PartialEq, Eq)]
136pub enum ParseIndexAnalyticsError {
138 #[error(transparent)]
140 InvalidIndexId(#[from] ParseIndexError),
141 #[error("ticker is invalid: {0}")]
143 InvalidTicker(ParseSecIdError),
144 #[error("secid is invalid: {0}")]
146 InvalidSecId(ParseSecIdError),
147 #[error("shortnames must not be empty")]
149 EmptyShortnames,
150 #[error("weight must be finite")]
152 NonFiniteWeight,
153 #[error("weight must not be negative")]
155 NegativeWeight,
156 #[error("tradingsession must be 1, 2 or 3, got {0}")]
158 InvalidTradingsession(i64),
159}
160
161#[derive(Debug, Error, Clone, PartialEq, Eq)]
162pub enum ParseEngineError {
164 #[error("engine id must be positive, got {0}")]
166 NonPositiveId(i64),
167 #[error("engine id is out of range for u32, got {0}")]
169 IdOutOfRange(i64),
170 #[error(transparent)]
172 InvalidName(#[from] ParseEngineNameError),
173 #[error("engine title must not be empty")]
175 EmptyTitle,
176}
177
178#[derive(Debug, Error, Clone, PartialEq, Eq)]
179pub enum ParseEngineNameError {
181 #[error("engine name must not be empty")]
183 Empty,
184 #[error("engine name must not contain '/'")]
186 ContainsSlash,
187}
188
189impl From<Infallible> for ParseEngineNameError {
190 fn from(value: Infallible) -> Self {
191 match value {}
192 }
193}
194
195#[derive(Debug, Error, Clone, PartialEq, Eq)]
196pub enum ParseMarketError {
198 #[error("market id must be positive, got {0}")]
200 NonPositiveId(i64),
201 #[error("market id is out of range for u32, got {0}")]
203 IdOutOfRange(i64),
204 #[error(transparent)]
206 InvalidName(#[from] ParseMarketNameError),
207 #[error("market title must not be empty")]
209 EmptyTitle,
210}
211
212#[derive(Debug, Error, Clone, PartialEq, Eq)]
213pub enum ParseBoardError {
215 #[error("board id must be positive, got {0}")]
217 NonPositiveId(i64),
218 #[error("board id is out of range for u32, got {0}")]
220 IdOutOfRange(i64),
221 #[error("board_group_id must not be negative, got {0}")]
223 NegativeBoardGroupId(i64),
224 #[error("board_group_id is out of range for u32, got {0}")]
226 BoardGroupIdOutOfRange(i64),
227 #[error(transparent)]
229 InvalidBoardId(#[from] ParseBoardIdError),
230 #[error("board title must not be empty")]
232 EmptyTitle,
233 #[error("is_traded must be 0 or 1, got {0}")]
235 InvalidIsTraded(i64),
236}
237
238#[derive(Debug, Error, Clone, PartialEq, Eq)]
239pub enum ParseSecurityError {
241 #[error(transparent)]
243 InvalidSecId(#[from] ParseSecIdError),
244 #[error("security shortname must not be empty")]
246 EmptyShortname,
247 #[error("security secname must not be empty")]
249 EmptySecname,
250 #[error("security status must not be empty")]
252 EmptyStatus,
253}
254
255#[derive(Debug, Error, Clone, PartialEq, Eq)]
256pub enum ParseSecurityBoardError {
258 #[error(transparent)]
260 InvalidEngine(#[from] ParseEngineNameError),
261 #[error(transparent)]
263 InvalidMarket(#[from] ParseMarketNameError),
264 #[error(transparent)]
266 InvalidBoardId(#[from] ParseBoardIdError),
267 #[error("is_primary must be 0 or 1, got {0}")]
269 InvalidIsPrimary(i64),
270}
271
272#[derive(Debug, Error, Clone, PartialEq)]
273pub enum ParseSecuritySnapshotError {
275 #[error(transparent)]
277 InvalidSecId(#[from] ParseSecIdError),
278 #[error("lot size must not be negative, got {0}")]
280 NegativeLotSize(i64),
281 #[error("lot size is out of range for u32, got {0}")]
283 LotSizeOutOfRange(i64),
284 #[error("last must be finite")]
286 NonFiniteLast(f64),
287}
288
289#[derive(Debug, Error, Clone, PartialEq, Eq)]
290pub enum ParseCandleError {
292 #[error("invalid candle datetime range: begin={begin} is after end={end}")]
294 InvalidDateRange {
295 begin: NaiveDateTime,
297 end: NaiveDateTime,
299 },
300 #[error("candle volume must not be negative, got {0}")]
302 NegativeVolume(i64),
303}
304
305#[derive(Debug, Error, Clone, PartialEq, Eq)]
306pub enum ParseCandleIntervalError {
308 #[error("invalid candle interval code, got {0}")]
310 InvalidCode(i64),
311}
312
313#[derive(Debug, Error, Clone, PartialEq, Eq)]
314pub enum ParseCandleQueryError {
316 #[error("invalid candle query datetime range: from={from} is after till={till}")]
318 InvalidDateRange {
319 from: NaiveDateTime,
321 till: NaiveDateTime,
323 },
324}
325
326#[derive(Debug, Error, Clone, PartialEq, Eq)]
327pub enum ParseCandleBorderError {
329 #[error("invalid candle borders range: begin={begin} is after end={end}")]
331 InvalidDateRange {
332 begin: NaiveDateTime,
334 end: NaiveDateTime,
336 },
337 #[error(transparent)]
339 InvalidInterval(#[from] ParseCandleIntervalError),
340 #[error("board_group_id must not be negative, got {0}")]
342 NegativeBoardGroupId(i64),
343 #[error("board_group_id is out of range for u32, got {0}")]
345 BoardGroupIdOutOfRange(i64),
346}
347
348#[derive(Debug, Error, Clone, PartialEq, Eq)]
349pub enum ParseTradeError {
351 #[error("trade number must be positive, got {0}")]
353 NonPositiveTradeNo(i64),
354 #[error("trade number is out of range for u64, got {0}")]
356 TradeNoOutOfRange(i64),
357 #[error("trade quantity must not be negative, got {0}")]
359 NegativeQuantity(i64),
360}
361
362#[derive(Debug, Error, Clone, PartialEq, Eq)]
363pub enum ParseOrderbookError {
365 #[error("orderbook side must be 'B' or 'S', got '{0}'")]
367 InvalidSide(Box<str>),
368 #[error("orderbook price must be present")]
370 MissingPrice,
371 #[error("orderbook price must not be negative")]
373 NegativePrice,
374 #[error("orderbook quantity must be present")]
376 MissingQuantity,
377 #[error("orderbook quantity must not be negative, got {0}")]
379 NegativeQuantity(i64),
380}
381
382#[derive(Debug, Error, Clone, PartialEq, Eq)]
383pub enum ParseSecIdError {
385 #[error("secid must not be empty")]
387 Empty,
388 #[error("secid must not contain '/'")]
390 ContainsSlash,
391}
392
393impl From<Infallible> for ParseSecIdError {
394 fn from(value: Infallible) -> Self {
395 match value {}
396 }
397}
398
399#[derive(Debug, Error, Clone, PartialEq, Eq)]
400pub enum ParseBoardIdError {
402 #[error("boardid must not be empty")]
404 Empty,
405 #[error("boardid must not contain '/'")]
407 ContainsSlash,
408}
409
410impl From<Infallible> for ParseBoardIdError {
411 fn from(value: Infallible) -> Self {
412 match value {}
413 }
414}
415
416#[derive(Debug, Error, Clone, PartialEq, Eq)]
417pub enum ParseMarketNameError {
419 #[error("market name must not be empty")]
421 Empty,
422 #[error("market name must not contain '/'")]
424 ContainsSlash,
425}
426
427impl From<Infallible> for ParseMarketNameError {
428 fn from(value: Infallible) -> Self {
429 match value {}
430 }
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434pub enum CandleInterval {
436 Minute1,
438 Minute10,
440 Hour1,
442 Day1,
444 Week1,
446 Month1,
448 Quarter1,
450}
451
452impl CandleInterval {
453 pub fn as_str(self) -> &'static str {
455 match self {
456 Self::Minute1 => "1",
457 Self::Minute10 => "10",
458 Self::Hour1 => "60",
459 Self::Day1 => "24",
460 Self::Week1 => "7",
461 Self::Month1 => "31",
462 Self::Quarter1 => "4",
463 }
464 }
465}
466
467impl TryFrom<i64> for CandleInterval {
468 type Error = ParseCandleIntervalError;
469
470 fn try_from(value: i64) -> Result<Self, Self::Error> {
471 match value {
472 1 => Ok(Self::Minute1),
473 10 => Ok(Self::Minute10),
474 60 => Ok(Self::Hour1),
475 24 => Ok(Self::Day1),
476 7 => Ok(Self::Week1),
477 31 => Ok(Self::Month1),
478 4 => Ok(Self::Quarter1),
479 other => Err(ParseCandleIntervalError::InvalidCode(other)),
480 }
481 }
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
485pub enum PageRequest {
490 #[default]
492 FirstPage,
493 Page(Pagination),
495 All {
497 page_limit: NonZeroU32,
499 },
500}
501
502impl PageRequest {
503 pub fn first_page() -> Self {
505 Self::FirstPage
506 }
507
508 pub fn page(pagination: Pagination) -> Self {
510 Self::Page(pagination)
511 }
512
513 pub fn all(page_limit: NonZeroU32) -> Self {
515 Self::All { page_limit }
516 }
517}
518
519#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
520pub enum BuySell {
522 Buy,
524 Sell,
526}
527
528impl BuySell {
529 pub fn as_str(self) -> &'static str {
531 match self {
532 Self::Buy => "B",
533 Self::Sell => "S",
534 }
535 }
536}
537
538impl TryFrom<String> for BuySell {
539 type Error = ParseOrderbookError;
540
541 fn try_from(value: String) -> Result<Self, Self::Error> {
542 let value = value.trim();
543 match value {
544 "B" => Ok(Self::Buy),
545 "S" => Ok(Self::Sell),
546 _ => Err(ParseOrderbookError::InvalidSide(
547 value.to_owned().into_boxed_str(),
548 )),
549 }
550 }
551}
552
553#[derive(Debug, Clone, PartialEq, Eq, Hash)]
554pub struct IndexId(Box<str>);
556
557impl IndexId {
558 pub fn as_str(&self) -> &str {
560 self.0.as_ref()
561 }
562}
563
564impl AsRef<str> for IndexId {
565 fn as_ref(&self) -> &str {
566 self.as_str()
567 }
568}
569
570impl From<&IndexId> for IndexId {
571 fn from(value: &IndexId) -> Self {
572 value.clone()
573 }
574}
575
576impl TryFrom<String> for IndexId {
577 type Error = ParseIndexError;
578
579 fn try_from(value: String) -> Result<Self, Self::Error> {
580 Self::try_from(value.as_str())
581 }
582}
583
584impl TryFrom<&str> for IndexId {
585 type Error = ParseIndexError;
586
587 fn try_from(value: &str) -> Result<Self, Self::Error> {
588 let value = value.trim();
589 if value.is_empty() {
590 return Err(ParseIndexError::EmptyIndexId);
591 }
592 Ok(Self(value.to_owned().into_boxed_str()))
593 }
594}
595
596impl FromStr for IndexId {
597 type Err = ParseIndexError;
598
599 fn from_str(value: &str) -> Result<Self, Self::Err> {
600 Self::try_from(value)
601 }
602}
603
604impl fmt::Display for IndexId {
605 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606 f.write_str(self.as_str())
607 }
608}
609
610#[derive(Debug, Clone, PartialEq, Eq, Hash)]
611pub struct EngineName(Box<str>);
613
614impl EngineName {
615 pub fn as_str(&self) -> &str {
617 self.0.as_ref()
618 }
619}
620
621impl AsRef<str> for EngineName {
622 fn as_ref(&self) -> &str {
623 self.as_str()
624 }
625}
626
627impl From<&EngineName> for EngineName {
628 fn from(value: &EngineName) -> Self {
629 value.clone()
630 }
631}
632
633impl TryFrom<String> for EngineName {
634 type Error = ParseEngineNameError;
635
636 fn try_from(value: String) -> Result<Self, Self::Error> {
637 Self::try_from(value.as_str())
638 }
639}
640
641impl TryFrom<&str> for EngineName {
642 type Error = ParseEngineNameError;
643
644 fn try_from(value: &str) -> Result<Self, Self::Error> {
645 let value = value.trim();
646 if value.is_empty() {
647 return Err(ParseEngineNameError::Empty);
648 }
649 if value.contains('/') {
650 return Err(ParseEngineNameError::ContainsSlash);
651 }
652 Ok(Self(value.to_owned().into_boxed_str()))
653 }
654}
655
656impl FromStr for EngineName {
657 type Err = ParseEngineNameError;
658
659 fn from_str(value: &str) -> Result<Self, Self::Err> {
660 Self::try_from(value)
661 }
662}
663
664impl fmt::Display for EngineName {
665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666 f.write_str(self.as_str())
667 }
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Hash)]
671pub struct SecId(Box<str>);
673
674impl SecId {
675 pub fn as_str(&self) -> &str {
677 self.0.as_ref()
678 }
679}
680
681impl AsRef<str> for SecId {
682 fn as_ref(&self) -> &str {
683 self.as_str()
684 }
685}
686
687impl From<&SecId> for SecId {
688 fn from(value: &SecId) -> Self {
689 value.clone()
690 }
691}
692
693impl TryFrom<String> for SecId {
694 type Error = ParseSecIdError;
695
696 fn try_from(value: String) -> Result<Self, Self::Error> {
697 Self::try_from(value.as_str())
698 }
699}
700
701impl TryFrom<&str> for SecId {
702 type Error = ParseSecIdError;
703
704 fn try_from(value: &str) -> Result<Self, Self::Error> {
705 let value = value.trim();
706 if value.is_empty() {
707 return Err(ParseSecIdError::Empty);
708 }
709 if value.contains('/') {
710 return Err(ParseSecIdError::ContainsSlash);
711 }
712 Ok(Self(value.to_owned().into_boxed_str()))
713 }
714}
715
716impl FromStr for SecId {
717 type Err = ParseSecIdError;
718
719 fn from_str(value: &str) -> Result<Self, Self::Err> {
720 Self::try_from(value)
721 }
722}
723
724impl fmt::Display for SecId {
725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726 f.write_str(self.as_str())
727 }
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Hash)]
731pub struct BoardId(Box<str>);
733
734impl BoardId {
735 pub fn as_str(&self) -> &str {
737 self.0.as_ref()
738 }
739}
740
741impl AsRef<str> for BoardId {
742 fn as_ref(&self) -> &str {
743 self.as_str()
744 }
745}
746
747impl From<&BoardId> for BoardId {
748 fn from(value: &BoardId) -> Self {
749 value.clone()
750 }
751}
752
753impl TryFrom<String> for BoardId {
754 type Error = ParseBoardIdError;
755
756 fn try_from(value: String) -> Result<Self, Self::Error> {
757 Self::try_from(value.as_str())
758 }
759}
760
761impl TryFrom<&str> for BoardId {
762 type Error = ParseBoardIdError;
763
764 fn try_from(value: &str) -> Result<Self, Self::Error> {
765 let value = value.trim();
766 if value.is_empty() {
767 return Err(ParseBoardIdError::Empty);
768 }
769 if value.contains('/') {
770 return Err(ParseBoardIdError::ContainsSlash);
771 }
772 Ok(Self(value.to_owned().into_boxed_str()))
773 }
774}
775
776impl FromStr for BoardId {
777 type Err = ParseBoardIdError;
778
779 fn from_str(value: &str) -> Result<Self, Self::Err> {
780 Self::try_from(value)
781 }
782}
783
784impl fmt::Display for BoardId {
785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786 f.write_str(self.as_str())
787 }
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, Hash)]
791pub struct MarketName(Box<str>);
793
794impl MarketName {
795 pub fn as_str(&self) -> &str {
797 self.0.as_ref()
798 }
799}
800
801impl AsRef<str> for MarketName {
802 fn as_ref(&self) -> &str {
803 self.as_str()
804 }
805}
806
807impl From<&MarketName> for MarketName {
808 fn from(value: &MarketName) -> Self {
809 value.clone()
810 }
811}
812
813impl TryFrom<String> for MarketName {
814 type Error = ParseMarketNameError;
815
816 fn try_from(value: String) -> Result<Self, Self::Error> {
817 Self::try_from(value.as_str())
818 }
819}
820
821impl TryFrom<&str> for MarketName {
822 type Error = ParseMarketNameError;
823
824 fn try_from(value: &str) -> Result<Self, Self::Error> {
825 let value = value.trim();
826 if value.is_empty() {
827 return Err(ParseMarketNameError::Empty);
828 }
829 if value.contains('/') {
830 return Err(ParseMarketNameError::ContainsSlash);
831 }
832 Ok(Self(value.to_owned().into_boxed_str()))
833 }
834}
835
836impl FromStr for MarketName {
837 type Err = ParseMarketNameError;
838
839 fn from_str(value: &str) -> Result<Self, Self::Err> {
840 Self::try_from(value)
841 }
842}
843
844impl fmt::Display for MarketName {
845 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846 f.write_str(self.as_str())
847 }
848}
849
850#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
851pub struct EngineId(u32);
853
854impl EngineId {
855 pub fn get(self) -> u32 {
857 self.0
858 }
859}
860
861impl fmt::Display for EngineId {
862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
863 write!(f, "{}", self.0)
864 }
865}
866
867#[derive(Debug, Clone, PartialEq, Eq)]
868pub struct Engine {
870 id: EngineId,
871 name: EngineName,
872 title: Box<str>,
873}
874
875impl Engine {
876 pub fn try_new(id: i64, name: String, title: String) -> Result<Self, ParseEngineError> {
878 if id <= 0 {
879 return Err(ParseEngineError::NonPositiveId(id));
880 }
881 let id = u32::try_from(id)
882 .map(EngineId)
883 .map_err(|_| ParseEngineError::IdOutOfRange(id))?;
884
885 let name = EngineName::try_from(name)?;
886
887 let title = title.trim();
888 if title.is_empty() {
889 return Err(ParseEngineError::EmptyTitle);
890 }
891
892 Ok(Self {
893 id,
894 name,
895 title: title.to_owned().into_boxed_str(),
896 })
897 }
898
899 pub fn id(&self) -> EngineId {
901 self.id
902 }
903
904 pub fn name(&self) -> &EngineName {
906 &self.name
907 }
908
909 pub fn title(&self) -> &str {
911 self.title.as_ref()
912 }
913}
914
915#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
916pub struct MarketId(u32);
918
919impl MarketId {
920 pub fn get(self) -> u32 {
922 self.0
923 }
924}
925
926impl fmt::Display for MarketId {
927 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
928 write!(f, "{}", self.0)
929 }
930}
931
932#[derive(Debug, Clone, PartialEq, Eq)]
933pub struct Market {
935 id: MarketId,
936 name: MarketName,
937 title: Box<str>,
938}
939
940impl Market {
941 pub fn try_new(id: i64, name: String, title: String) -> Result<Self, ParseMarketError> {
943 if id <= 0 {
944 return Err(ParseMarketError::NonPositiveId(id));
945 }
946 let id = u32::try_from(id)
947 .map(MarketId)
948 .map_err(|_| ParseMarketError::IdOutOfRange(id))?;
949
950 let name = MarketName::try_from(name)?;
951
952 let title = title.trim();
953 if title.is_empty() {
954 return Err(ParseMarketError::EmptyTitle);
955 }
956
957 Ok(Self {
958 id,
959 name,
960 title: title.to_owned().into_boxed_str(),
961 })
962 }
963
964 pub fn id(&self) -> MarketId {
966 self.id
967 }
968
969 pub fn name(&self) -> &MarketName {
971 &self.name
972 }
973
974 pub fn title(&self) -> &str {
976 self.title.as_ref()
977 }
978}
979
980#[derive(Debug, Clone, PartialEq, Eq)]
981pub struct Board {
983 id: u32,
984 board_group_id: u32,
985 boardid: BoardId,
986 title: Box<str>,
987 is_traded: bool,
988}
989
990impl Board {
991 pub fn try_new(
993 id: i64,
994 board_group_id: i64,
995 boardid: String,
996 title: String,
997 is_traded: i64,
998 ) -> Result<Self, ParseBoardError> {
999 if id <= 0 {
1000 return Err(ParseBoardError::NonPositiveId(id));
1001 }
1002 let id = u32::try_from(id).map_err(|_| ParseBoardError::IdOutOfRange(id))?;
1003
1004 if board_group_id < 0 {
1005 return Err(ParseBoardError::NegativeBoardGroupId(board_group_id));
1006 }
1007 let board_group_id = u32::try_from(board_group_id)
1008 .map_err(|_| ParseBoardError::BoardGroupIdOutOfRange(board_group_id))?;
1009
1010 let boardid = BoardId::try_from(boardid)?;
1011
1012 let title = title.trim();
1013 if title.is_empty() {
1014 return Err(ParseBoardError::EmptyTitle);
1015 }
1016
1017 let is_traded = match is_traded {
1018 0 => false,
1019 1 => true,
1020 other => return Err(ParseBoardError::InvalidIsTraded(other)),
1021 };
1022
1023 Ok(Self {
1024 id,
1025 board_group_id,
1026 boardid,
1027 title: title.to_owned().into_boxed_str(),
1028 is_traded,
1029 })
1030 }
1031
1032 pub fn id(&self) -> u32 {
1034 self.id
1035 }
1036
1037 pub fn board_group_id(&self) -> u32 {
1039 self.board_group_id
1040 }
1041
1042 pub fn boardid(&self) -> &BoardId {
1044 &self.boardid
1045 }
1046
1047 pub fn title(&self) -> &str {
1049 self.title.as_ref()
1050 }
1051
1052 pub fn is_traded(&self) -> bool {
1054 self.is_traded
1055 }
1056}
1057
1058#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1059pub struct SecurityBoard {
1061 engine: EngineName,
1062 market: MarketName,
1063 boardid: BoardId,
1064 is_primary: bool,
1065}
1066
1067impl SecurityBoard {
1068 pub fn try_new(
1070 engine: String,
1071 market: String,
1072 boardid: String,
1073 is_primary: i64,
1074 ) -> Result<Self, ParseSecurityBoardError> {
1075 let engine = EngineName::try_from(engine)?;
1076 let market = MarketName::try_from(market)?;
1077 let boardid = BoardId::try_from(boardid)?;
1078 let is_primary = match is_primary {
1079 0 => false,
1080 1 => true,
1081 other => return Err(ParseSecurityBoardError::InvalidIsPrimary(other)),
1082 };
1083
1084 Ok(Self {
1085 engine,
1086 market,
1087 boardid,
1088 is_primary,
1089 })
1090 }
1091
1092 pub fn engine(&self) -> &EngineName {
1094 &self.engine
1095 }
1096
1097 pub fn market(&self) -> &MarketName {
1099 &self.market
1100 }
1101
1102 pub fn boardid(&self) -> &BoardId {
1104 &self.boardid
1105 }
1106
1107 pub fn is_primary(&self) -> bool {
1109 self.is_primary
1110 }
1111}
1112
1113#[derive(Debug, Clone, PartialEq, Eq)]
1114pub struct Security {
1116 secid: SecId,
1117 shortname: Box<str>,
1118 secname: Box<str>,
1119 status: Box<str>,
1120}
1121
1122impl Security {
1123 pub fn try_new(
1125 secid: String,
1126 shortname: String,
1127 secname: String,
1128 status: String,
1129 ) -> Result<Self, ParseSecurityError> {
1130 let secid = SecId::try_from(secid)?;
1131
1132 let shortname = shortname.trim();
1133 if shortname.is_empty() {
1134 return Err(ParseSecurityError::EmptyShortname);
1135 }
1136
1137 let secname = secname.trim();
1138 if secname.is_empty() {
1139 return Err(ParseSecurityError::EmptySecname);
1140 }
1141
1142 let status = status.trim();
1143 if status.is_empty() {
1144 return Err(ParseSecurityError::EmptyStatus);
1145 }
1146
1147 Ok(Self {
1148 secid,
1149 shortname: shortname.to_owned().into_boxed_str(),
1150 secname: secname.to_owned().into_boxed_str(),
1151 status: status.to_owned().into_boxed_str(),
1152 })
1153 }
1154
1155 pub fn secid(&self) -> &SecId {
1157 &self.secid
1158 }
1159
1160 pub fn shortname(&self) -> &str {
1162 self.shortname.as_ref()
1163 }
1164
1165 pub fn secname(&self) -> &str {
1167 self.secname.as_ref()
1168 }
1169
1170 pub fn status(&self) -> &str {
1172 self.status.as_ref()
1173 }
1174}
1175
1176#[derive(Debug, Clone, PartialEq)]
1177pub struct SecuritySnapshot {
1179 secid: SecId,
1180 lot_size: Option<u32>,
1181 last: Option<f64>,
1182}
1183
1184impl SecuritySnapshot {
1185 pub fn try_new(
1187 secid: String,
1188 lot_size: Option<i64>,
1189 last: Option<f64>,
1190 ) -> Result<Self, ParseSecuritySnapshotError> {
1191 let secid = SecId::try_from(secid).map_err(ParseSecuritySnapshotError::InvalidSecId)?;
1192 let lot_size = match lot_size {
1193 None => None,
1194 Some(raw) if raw < 0 => return Err(ParseSecuritySnapshotError::NegativeLotSize(raw)),
1195 Some(raw) => Some(
1196 u32::try_from(raw)
1197 .map_err(|_| ParseSecuritySnapshotError::LotSizeOutOfRange(raw))?,
1198 ),
1199 };
1200 Self::try_from_parts(secid, lot_size, last)
1201 }
1202
1203 pub(crate) fn try_from_parts(
1205 secid: SecId,
1206 lot_size: Option<u32>,
1207 last: Option<f64>,
1208 ) -> Result<Self, ParseSecuritySnapshotError> {
1209 if let Some(last) = last
1211 && !last.is_finite()
1212 {
1213 return Err(ParseSecuritySnapshotError::NonFiniteLast(last));
1214 }
1215
1216 Ok(Self {
1217 secid,
1218 lot_size,
1219 last,
1220 })
1221 }
1222
1223 pub fn secid(&self) -> &SecId {
1225 &self.secid
1226 }
1227
1228 pub fn lot_size(&self) -> Option<u32> {
1230 self.lot_size
1231 }
1232
1233 pub fn last(&self) -> Option<f64> {
1235 self.last
1236 }
1237}
1238
1239#[derive(Debug, Clone, PartialEq, Eq)]
1240pub struct CandleBorder {
1242 begin: NaiveDateTime,
1243 end: NaiveDateTime,
1244 interval: CandleInterval,
1245 board_group_id: u32,
1246}
1247
1248impl CandleBorder {
1249 pub fn try_new(
1251 begin: NaiveDateTime,
1252 end: NaiveDateTime,
1253 interval: i64,
1254 board_group_id: i64,
1255 ) -> Result<Self, ParseCandleBorderError> {
1256 if begin > end {
1257 return Err(ParseCandleBorderError::InvalidDateRange { begin, end });
1258 }
1259
1260 let interval = CandleInterval::try_from(interval)?;
1261 if board_group_id < 0 {
1262 return Err(ParseCandleBorderError::NegativeBoardGroupId(board_group_id));
1263 }
1264 let board_group_id = u32::try_from(board_group_id)
1265 .map_err(|_| ParseCandleBorderError::BoardGroupIdOutOfRange(board_group_id))?;
1266
1267 Ok(Self {
1268 begin,
1269 end,
1270 interval,
1271 board_group_id,
1272 })
1273 }
1274
1275 pub fn begin(&self) -> NaiveDateTime {
1277 self.begin
1278 }
1279
1280 pub fn end(&self) -> NaiveDateTime {
1282 self.end
1283 }
1284
1285 pub fn interval(&self) -> CandleInterval {
1287 self.interval
1288 }
1289
1290 pub fn board_group_id(&self) -> u32 {
1292 self.board_group_id
1293 }
1294}
1295
1296#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1297pub struct CandleQuery {
1299 from: Option<NaiveDateTime>,
1300 till: Option<NaiveDateTime>,
1301 interval: Option<CandleInterval>,
1302}
1303
1304impl CandleQuery {
1305 pub fn try_new(
1307 from: Option<NaiveDateTime>,
1308 till: Option<NaiveDateTime>,
1309 interval: Option<CandleInterval>,
1310 ) -> Result<Self, ParseCandleQueryError> {
1311 if let (Some(from), Some(till)) = (from, till)
1312 && from > till
1313 {
1314 return Err(ParseCandleQueryError::InvalidDateRange { from, till });
1315 }
1316
1317 Ok(Self {
1318 from,
1319 till,
1320 interval,
1321 })
1322 }
1323
1324 pub fn from(&self) -> Option<NaiveDateTime> {
1326 self.from
1327 }
1328
1329 pub fn till(&self) -> Option<NaiveDateTime> {
1331 self.till
1332 }
1333
1334 pub fn interval(&self) -> Option<CandleInterval> {
1336 self.interval
1337 }
1338
1339 pub fn with_from(self, from: NaiveDateTime) -> Result<Self, ParseCandleQueryError> {
1341 Self::try_new(Some(from), self.till, self.interval)
1342 }
1343
1344 pub fn with_till(self, till: NaiveDateTime) -> Result<Self, ParseCandleQueryError> {
1346 Self::try_new(self.from, Some(till), self.interval)
1347 }
1348
1349 pub fn with_interval(mut self, interval: CandleInterval) -> Self {
1351 self.interval = Some(interval);
1352 self
1353 }
1354}
1355
1356#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
1357pub struct Pagination {
1359 pub start: Option<u32>,
1361 pub limit: Option<NonZeroU32>,
1363}
1364
1365impl Pagination {
1366 pub fn with_start(mut self, start: u32) -> Self {
1368 self.start = Some(start);
1369 self
1370 }
1371
1372 pub fn with_limit(mut self, limit: NonZeroU32) -> Self {
1374 self.limit = Some(limit);
1375 self
1376 }
1377}
1378
1379#[derive(Debug, Clone, PartialEq)]
1380pub struct Candle {
1382 begin: NaiveDateTime,
1383 end: NaiveDateTime,
1384 open: Option<f64>,
1385 close: Option<f64>,
1386 high: Option<f64>,
1387 low: Option<f64>,
1388 value: Option<f64>,
1389 volume: Option<u64>,
1390}
1391
1392impl Candle {
1393 pub fn try_new(
1395 begin: NaiveDateTime,
1396 end: NaiveDateTime,
1397 ohlcv: CandleOhlcv,
1398 ) -> Result<Self, ParseCandleError> {
1399 if begin > end {
1400 return Err(ParseCandleError::InvalidDateRange { begin, end });
1401 }
1402
1403 let volume = match ohlcv.volume {
1404 None => None,
1405 Some(raw) if raw >= 0 => Some(raw as u64),
1406 Some(raw) => return Err(ParseCandleError::NegativeVolume(raw)),
1407 };
1408
1409 Ok(Self {
1410 begin,
1411 end,
1412 open: ohlcv.open,
1413 close: ohlcv.close,
1414 high: ohlcv.high,
1415 low: ohlcv.low,
1416 value: ohlcv.value,
1417 volume,
1418 })
1419 }
1420
1421 pub fn begin(&self) -> NaiveDateTime {
1423 self.begin
1424 }
1425
1426 pub fn end(&self) -> NaiveDateTime {
1428 self.end
1429 }
1430
1431 pub fn open(&self) -> Option<f64> {
1433 self.open
1434 }
1435
1436 pub fn close(&self) -> Option<f64> {
1438 self.close
1439 }
1440
1441 pub fn high(&self) -> Option<f64> {
1443 self.high
1444 }
1445
1446 pub fn low(&self) -> Option<f64> {
1448 self.low
1449 }
1450
1451 pub fn value(&self) -> Option<f64> {
1453 self.value
1454 }
1455
1456 pub fn volume(&self) -> Option<u64> {
1458 self.volume
1459 }
1460}
1461
1462#[derive(Debug, Clone, Copy, PartialEq)]
1463pub struct CandleOhlcv {
1465 open: Option<f64>,
1466 close: Option<f64>,
1467 high: Option<f64>,
1468 low: Option<f64>,
1469 value: Option<f64>,
1470 volume: Option<i64>,
1471}
1472
1473impl CandleOhlcv {
1474 pub fn new(
1476 open: Option<f64>,
1477 close: Option<f64>,
1478 high: Option<f64>,
1479 low: Option<f64>,
1480 value: Option<f64>,
1481 volume: Option<i64>,
1482 ) -> Self {
1483 Self {
1484 open,
1485 close,
1486 high,
1487 low,
1488 value,
1489 volume,
1490 }
1491 }
1492}
1493
1494#[derive(Debug, Clone, PartialEq)]
1495pub struct Trade {
1497 tradeno: u64,
1498 tradetime: NaiveTime,
1499 price: Option<f64>,
1500 quantity: Option<u64>,
1501 value: Option<f64>,
1502}
1503
1504impl Trade {
1505 pub fn try_new(
1507 tradeno: i64,
1508 tradetime: NaiveTime,
1509 price: Option<f64>,
1510 quantity: Option<i64>,
1511 value: Option<f64>,
1512 ) -> Result<Self, ParseTradeError> {
1513 if tradeno <= 0 {
1514 return Err(ParseTradeError::NonPositiveTradeNo(tradeno));
1515 }
1516 let tradeno =
1517 u64::try_from(tradeno).map_err(|_| ParseTradeError::TradeNoOutOfRange(tradeno))?;
1518
1519 let quantity = match quantity {
1520 None => None,
1521 Some(raw) if raw >= 0 => Some(raw as u64),
1522 Some(raw) => return Err(ParseTradeError::NegativeQuantity(raw)),
1523 };
1524
1525 Ok(Self {
1526 tradeno,
1527 tradetime,
1528 price,
1529 quantity,
1530 value,
1531 })
1532 }
1533
1534 pub fn tradeno(&self) -> u64 {
1536 self.tradeno
1537 }
1538
1539 pub fn tradetime(&self) -> NaiveTime {
1541 self.tradetime
1542 }
1543
1544 pub fn price(&self) -> Option<f64> {
1546 self.price
1547 }
1548
1549 pub fn quantity(&self) -> Option<u64> {
1551 self.quantity
1552 }
1553
1554 pub fn value(&self) -> Option<f64> {
1556 self.value
1557 }
1558}
1559
1560#[derive(Debug, Clone, PartialEq)]
1561pub struct OrderbookLevel {
1563 buy_sell: BuySell,
1564 price: f64,
1565 quantity: u64,
1566}
1567
1568impl OrderbookLevel {
1569 pub fn try_new(
1571 buy_sell: String,
1572 price: Option<f64>,
1573 quantity: Option<i64>,
1574 ) -> Result<Self, ParseOrderbookError> {
1575 let buy_sell = BuySell::try_from(buy_sell)?;
1576
1577 let Some(price) = price else {
1578 return Err(ParseOrderbookError::MissingPrice);
1579 };
1580 if price.is_sign_negative() {
1581 return Err(ParseOrderbookError::NegativePrice);
1582 }
1583
1584 let Some(quantity) = quantity else {
1585 return Err(ParseOrderbookError::MissingQuantity);
1586 };
1587 let quantity = match quantity {
1588 raw if raw >= 0 => raw as u64,
1589 raw => return Err(ParseOrderbookError::NegativeQuantity(raw)),
1590 };
1591
1592 Ok(Self {
1593 buy_sell,
1594 price,
1595 quantity,
1596 })
1597 }
1598
1599 pub fn buy_sell(&self) -> BuySell {
1601 self.buy_sell
1602 }
1603
1604 pub fn price(&self) -> f64 {
1606 self.price
1607 }
1608
1609 pub fn quantity(&self) -> u64 {
1611 self.quantity
1612 }
1613}
1614
1615#[derive(Debug, Clone, PartialEq, Eq)]
1616pub struct Index {
1618 id: IndexId,
1619 short_name: Box<str>,
1620 from: Option<NaiveDate>,
1621 till: Option<NaiveDate>,
1622}
1623
1624impl Index {
1625 pub fn try_new(
1627 id: String,
1628 short_name: String,
1629 from: Option<NaiveDate>,
1630 till: Option<NaiveDate>,
1631 ) -> Result<Self, ParseIndexError> {
1632 let id = IndexId::try_from(id)?;
1633 let short_name = short_name.trim();
1634 if short_name.is_empty() {
1635 return Err(ParseIndexError::EmptyShortName);
1636 }
1637 if let (Some(from_date), Some(till_date)) = (from, till)
1638 && from_date > till_date
1639 {
1640 return Err(ParseIndexError::InvalidDateRange {
1641 from: from_date,
1642 till: till_date,
1643 });
1644 }
1645
1646 Ok(Self {
1647 id,
1648 short_name: short_name.to_owned().into_boxed_str(),
1649 from,
1650 till,
1651 })
1652 }
1653
1654 pub fn id(&self) -> &IndexId {
1656 &self.id
1657 }
1658
1659 pub fn short_name(&self) -> &str {
1661 self.short_name.as_ref()
1662 }
1663
1664 pub fn from(&self) -> Option<NaiveDate> {
1666 self.from
1667 }
1668
1669 pub fn till(&self) -> Option<NaiveDate> {
1671 self.till
1672 }
1673
1674 pub fn is_active_on(&self, date: NaiveDate) -> bool {
1676 self.from.is_none_or(|from| from <= date) && self.till.is_none_or(|till| date <= till)
1677 }
1678}
1679
1680#[derive(Debug, Clone, PartialEq, Eq)]
1681pub struct HistoryDates {
1683 from: NaiveDate,
1684 till: NaiveDate,
1685}
1686
1687impl HistoryDates {
1688 pub fn try_new(from: NaiveDate, till: NaiveDate) -> Result<Self, ParseHistoryDatesError> {
1690 if from > till {
1691 return Err(ParseHistoryDatesError::InvalidDateRange { from, till });
1692 }
1693 Ok(Self { from, till })
1694 }
1695
1696 pub fn from(&self) -> NaiveDate {
1698 self.from
1699 }
1700
1701 pub fn till(&self) -> NaiveDate {
1703 self.till
1704 }
1705}
1706
1707#[derive(Debug, Clone, PartialEq)]
1708pub struct HistoryRecord {
1710 boardid: BoardId,
1711 tradedate: NaiveDate,
1712 secid: SecId,
1713 numtrades: Option<u64>,
1714 value: Option<f64>,
1715 open: Option<f64>,
1716 low: Option<f64>,
1717 high: Option<f64>,
1718 close: Option<f64>,
1719 volume: Option<u64>,
1720}
1721
1722impl HistoryRecord {
1723 pub(crate) fn try_new(input: HistoryRecordInput) -> Result<Self, ParseHistoryRecordError> {
1725 let HistoryRecordInput {
1726 boardid,
1727 tradedate,
1728 secid,
1729 numtrades,
1730 value,
1731 open,
1732 low,
1733 high,
1734 close,
1735 volume,
1736 } = input;
1737
1738 let boardid = BoardId::try_from(boardid)?;
1739 let secid = SecId::try_from(secid)?;
1740
1741 let numtrades = match numtrades {
1742 None => None,
1743 Some(raw) if raw >= 0 => Some(raw as u64),
1744 Some(raw) => return Err(ParseHistoryRecordError::NegativeNumTrades(raw)),
1745 };
1746
1747 let volume = match volume {
1748 None => None,
1749 Some(raw) if raw >= 0 => Some(raw as u64),
1750 Some(raw) => return Err(ParseHistoryRecordError::NegativeVolume(raw)),
1751 };
1752
1753 Ok(Self {
1754 boardid,
1755 tradedate,
1756 secid,
1757 numtrades,
1758 value,
1759 open,
1760 low,
1761 high,
1762 close,
1763 volume,
1764 })
1765 }
1766
1767 pub fn boardid(&self) -> &BoardId {
1769 &self.boardid
1770 }
1771
1772 pub fn tradedate(&self) -> NaiveDate {
1774 self.tradedate
1775 }
1776
1777 pub fn secid(&self) -> &SecId {
1779 &self.secid
1780 }
1781
1782 pub fn numtrades(&self) -> Option<u64> {
1784 self.numtrades
1785 }
1786
1787 pub fn value(&self) -> Option<f64> {
1789 self.value
1790 }
1791
1792 pub fn open(&self) -> Option<f64> {
1794 self.open
1795 }
1796
1797 pub fn low(&self) -> Option<f64> {
1799 self.low
1800 }
1801
1802 pub fn high(&self) -> Option<f64> {
1804 self.high
1805 }
1806
1807 pub fn close(&self) -> Option<f64> {
1809 self.close
1810 }
1811
1812 pub fn volume(&self) -> Option<u64> {
1814 self.volume
1815 }
1816}
1817
1818#[derive(Debug, Clone, PartialEq)]
1819pub struct Turnover {
1821 name: Box<str>,
1822 id: u32,
1823 valtoday: Option<f64>,
1824 valtoday_usd: Option<f64>,
1825 numtrades: Option<u64>,
1826 updatetime: NaiveDateTime,
1827 title: Box<str>,
1828}
1829
1830impl Turnover {
1831 pub fn try_new(
1833 name: String,
1834 id: i64,
1835 valtoday: Option<f64>,
1836 valtoday_usd: Option<f64>,
1837 numtrades: Option<i64>,
1838 updatetime: NaiveDateTime,
1839 title: String,
1840 ) -> Result<Self, ParseTurnoverError> {
1841 let name = name.trim();
1842 if name.is_empty() {
1843 return Err(ParseTurnoverError::EmptyName);
1844 }
1845
1846 if id <= 0 {
1847 return Err(ParseTurnoverError::NonPositiveId(id));
1848 }
1849 let id = u32::try_from(id).map_err(|_| ParseTurnoverError::IdOutOfRange(id))?;
1850
1851 let numtrades = match numtrades {
1852 None => None,
1853 Some(raw) if raw >= 0 => Some(raw as u64),
1854 Some(raw) => return Err(ParseTurnoverError::NegativeNumTrades(raw)),
1855 };
1856
1857 let title = title.trim();
1858 if title.is_empty() {
1859 return Err(ParseTurnoverError::EmptyTitle);
1860 }
1861
1862 Ok(Self {
1863 name: name.to_owned().into_boxed_str(),
1864 id,
1865 valtoday,
1866 valtoday_usd,
1867 numtrades,
1868 updatetime,
1869 title: title.to_owned().into_boxed_str(),
1870 })
1871 }
1872
1873 pub fn name(&self) -> &str {
1875 self.name.as_ref()
1876 }
1877
1878 pub fn id(&self) -> u32 {
1880 self.id
1881 }
1882
1883 pub fn valtoday(&self) -> Option<f64> {
1885 self.valtoday
1886 }
1887
1888 pub fn valtoday_usd(&self) -> Option<f64> {
1890 self.valtoday_usd
1891 }
1892
1893 pub fn numtrades(&self) -> Option<u64> {
1895 self.numtrades
1896 }
1897
1898 pub fn updatetime(&self) -> NaiveDateTime {
1900 self.updatetime
1901 }
1902
1903 pub fn title(&self) -> &str {
1905 self.title.as_ref()
1906 }
1907}
1908
1909#[derive(Debug, Clone, PartialEq)]
1910pub struct SecStat {
1912 secid: SecId,
1913 boardid: BoardId,
1914 voltoday: Option<u64>,
1915 valtoday: Option<f64>,
1916 highbid: Option<f64>,
1917 lowoffer: Option<f64>,
1918 lastoffer: Option<f64>,
1919 lastbid: Option<f64>,
1920 open: Option<f64>,
1921 low: Option<f64>,
1922 high: Option<f64>,
1923 last: Option<f64>,
1924 numtrades: Option<u64>,
1925 waprice: Option<f64>,
1926}
1927
1928impl SecStat {
1929 pub(crate) fn try_new(input: SecStatInput) -> Result<Self, ParseSecStatError> {
1931 let SecStatInput {
1932 secid,
1933 boardid,
1934 voltoday,
1935 valtoday,
1936 highbid,
1937 lowoffer,
1938 lastoffer,
1939 lastbid,
1940 open,
1941 low,
1942 high,
1943 last,
1944 numtrades,
1945 waprice,
1946 } = input;
1947
1948 let secid = SecId::try_from(secid)?;
1949 let boardid = BoardId::try_from(boardid)?;
1950
1951 let voltoday = match voltoday {
1952 None => None,
1953 Some(raw) if raw >= 0 => Some(raw as u64),
1954 Some(raw) => return Err(ParseSecStatError::NegativeVolToday(raw)),
1955 };
1956
1957 let numtrades = match numtrades {
1958 None => None,
1959 Some(raw) if raw >= 0 => Some(raw as u64),
1960 Some(raw) => return Err(ParseSecStatError::NegativeNumTrades(raw)),
1961 };
1962
1963 Ok(Self {
1964 secid,
1965 boardid,
1966 voltoday,
1967 valtoday,
1968 highbid,
1969 lowoffer,
1970 lastoffer,
1971 lastbid,
1972 open,
1973 low,
1974 high,
1975 last,
1976 numtrades,
1977 waprice,
1978 })
1979 }
1980
1981 pub fn secid(&self) -> &SecId {
1983 &self.secid
1984 }
1985
1986 pub fn boardid(&self) -> &BoardId {
1988 &self.boardid
1989 }
1990
1991 pub fn voltoday(&self) -> Option<u64> {
1993 self.voltoday
1994 }
1995
1996 pub fn valtoday(&self) -> Option<f64> {
1998 self.valtoday
1999 }
2000
2001 pub fn highbid(&self) -> Option<f64> {
2003 self.highbid
2004 }
2005
2006 pub fn lowoffer(&self) -> Option<f64> {
2008 self.lowoffer
2009 }
2010
2011 pub fn lastoffer(&self) -> Option<f64> {
2013 self.lastoffer
2014 }
2015
2016 pub fn lastbid(&self) -> Option<f64> {
2018 self.lastbid
2019 }
2020
2021 pub fn open(&self) -> Option<f64> {
2023 self.open
2024 }
2025
2026 pub fn low(&self) -> Option<f64> {
2028 self.low
2029 }
2030
2031 pub fn high(&self) -> Option<f64> {
2033 self.high
2034 }
2035
2036 pub fn last(&self) -> Option<f64> {
2038 self.last
2039 }
2040
2041 pub fn numtrades(&self) -> Option<u64> {
2043 self.numtrades
2044 }
2045
2046 pub fn waprice(&self) -> Option<f64> {
2048 self.waprice
2049 }
2050}
2051
2052#[derive(Debug, Clone, PartialEq, Eq)]
2053pub struct SiteNews {
2055 id: u64,
2056 tag: Box<str>,
2057 title: Box<str>,
2058 published_at: NaiveDateTime,
2059 modified_at: NaiveDateTime,
2060}
2061
2062impl SiteNews {
2063 pub fn try_new(
2065 id: i64,
2066 tag: String,
2067 title: String,
2068 published_at: NaiveDateTime,
2069 modified_at: NaiveDateTime,
2070 ) -> Result<Self, ParseSiteNewsError> {
2071 if id <= 0 {
2072 return Err(ParseSiteNewsError::NonPositiveId(id));
2073 }
2074 let id = u64::try_from(id).map_err(|_| ParseSiteNewsError::IdOutOfRange(id))?;
2075
2076 let tag = tag.trim();
2077 if tag.is_empty() {
2078 return Err(ParseSiteNewsError::EmptyTag);
2079 }
2080 let title = title.trim();
2081 if title.is_empty() {
2082 return Err(ParseSiteNewsError::EmptyTitle);
2083 }
2084
2085 Ok(Self {
2086 id,
2087 tag: tag.to_owned().into_boxed_str(),
2088 title: title.to_owned().into_boxed_str(),
2089 published_at,
2090 modified_at,
2091 })
2092 }
2093
2094 pub fn id(&self) -> u64 {
2096 self.id
2097 }
2098
2099 pub fn tag(&self) -> &str {
2101 self.tag.as_ref()
2102 }
2103
2104 pub fn title(&self) -> &str {
2106 self.title.as_ref()
2107 }
2108
2109 pub fn published_at(&self) -> NaiveDateTime {
2111 self.published_at
2112 }
2113
2114 pub fn modified_at(&self) -> NaiveDateTime {
2116 self.modified_at
2117 }
2118}
2119
2120#[derive(Debug, Clone, PartialEq, Eq)]
2121pub struct Event {
2123 id: u64,
2124 tag: Box<str>,
2125 title: Box<str>,
2126 from: Option<NaiveDateTime>,
2127 modified_at: NaiveDateTime,
2128}
2129
2130impl Event {
2131 pub fn try_new(
2133 id: i64,
2134 tag: String,
2135 title: String,
2136 from: Option<NaiveDateTime>,
2137 modified_at: NaiveDateTime,
2138 ) -> Result<Self, ParseEventError> {
2139 if id <= 0 {
2140 return Err(ParseEventError::NonPositiveId(id));
2141 }
2142 let id = u64::try_from(id).map_err(|_| ParseEventError::IdOutOfRange(id))?;
2143
2144 let tag = tag.trim();
2145 if tag.is_empty() {
2146 return Err(ParseEventError::EmptyTag);
2147 }
2148 let title = title.trim();
2149 if title.is_empty() {
2150 return Err(ParseEventError::EmptyTitle);
2151 }
2152
2153 Ok(Self {
2154 id,
2155 tag: tag.to_owned().into_boxed_str(),
2156 title: title.to_owned().into_boxed_str(),
2157 from,
2158 modified_at,
2159 })
2160 }
2161
2162 pub fn id(&self) -> u64 {
2164 self.id
2165 }
2166
2167 pub fn tag(&self) -> &str {
2169 self.tag.as_ref()
2170 }
2171
2172 pub fn title(&self) -> &str {
2174 self.title.as_ref()
2175 }
2176
2177 pub fn from(&self) -> Option<NaiveDateTime> {
2179 self.from
2180 }
2181
2182 pub fn modified_at(&self) -> NaiveDateTime {
2184 self.modified_at
2185 }
2186}
2187
2188pub(crate) struct SecStatInput {
2193 pub(crate) secid: String,
2194 pub(crate) boardid: String,
2195 pub(crate) voltoday: Option<i64>,
2196 pub(crate) valtoday: Option<f64>,
2197 pub(crate) highbid: Option<f64>,
2198 pub(crate) lowoffer: Option<f64>,
2199 pub(crate) lastoffer: Option<f64>,
2200 pub(crate) lastbid: Option<f64>,
2201 pub(crate) open: Option<f64>,
2202 pub(crate) low: Option<f64>,
2203 pub(crate) high: Option<f64>,
2204 pub(crate) last: Option<f64>,
2205 pub(crate) numtrades: Option<i64>,
2206 pub(crate) waprice: Option<f64>,
2207}
2208
2209pub(crate) struct HistoryRecordInput {
2211 pub(crate) boardid: String,
2212 pub(crate) tradedate: NaiveDate,
2213 pub(crate) secid: String,
2214 pub(crate) numtrades: Option<i64>,
2215 pub(crate) value: Option<f64>,
2216 pub(crate) open: Option<f64>,
2217 pub(crate) low: Option<f64>,
2218 pub(crate) high: Option<f64>,
2219 pub(crate) close: Option<f64>,
2220 pub(crate) volume: Option<i64>,
2221}
2222
2223#[derive(Debug, Clone, PartialEq)]
2224pub struct IndexAnalytics {
2226 indexid: IndexId,
2227 tradedate: NaiveDate,
2228 ticker: SecId,
2229 shortnames: Box<str>,
2230 secid: SecId,
2231 weight: f64,
2232 tradingsession: u8,
2233 trade_session_date: NaiveDate,
2234}
2235
2236impl IndexAnalytics {
2237 pub(crate) fn try_new(input: IndexAnalyticsInput) -> Result<Self, ParseIndexAnalyticsError> {
2239 let IndexAnalyticsInput {
2240 indexid,
2241 tradedate,
2242 ticker,
2243 shortnames,
2244 secid,
2245 weight,
2246 tradingsession,
2247 trade_session_date,
2248 } = input;
2249
2250 let indexid = IndexId::try_from(indexid)?;
2251 let ticker = SecId::try_from(ticker).map_err(ParseIndexAnalyticsError::InvalidTicker)?;
2252 let secid = SecId::try_from(secid).map_err(ParseIndexAnalyticsError::InvalidSecId)?;
2253
2254 let shortnames = shortnames.trim();
2255 if shortnames.is_empty() {
2256 return Err(ParseIndexAnalyticsError::EmptyShortnames);
2257 }
2258 if !weight.is_finite() {
2259 return Err(ParseIndexAnalyticsError::NonFiniteWeight);
2260 }
2261 if weight.is_sign_negative() {
2262 return Err(ParseIndexAnalyticsError::NegativeWeight);
2263 }
2264 if !(1..=3).contains(&tradingsession) {
2265 return Err(ParseIndexAnalyticsError::InvalidTradingsession(
2266 tradingsession,
2267 ));
2268 }
2269
2270 Ok(Self {
2271 indexid,
2272 tradedate,
2273 ticker,
2274 shortnames: shortnames.to_owned().into_boxed_str(),
2275 secid,
2276 weight,
2277 tradingsession: tradingsession as u8,
2278 trade_session_date,
2279 })
2280 }
2281
2282 pub fn indexid(&self) -> &IndexId {
2284 &self.indexid
2285 }
2286
2287 pub fn tradedate(&self) -> NaiveDate {
2289 self.tradedate
2290 }
2291
2292 pub fn ticker(&self) -> &SecId {
2294 &self.ticker
2295 }
2296
2297 pub fn shortnames(&self) -> &str {
2299 self.shortnames.as_ref()
2300 }
2301
2302 pub fn secid(&self) -> &SecId {
2304 &self.secid
2305 }
2306
2307 pub fn weight(&self) -> f64 {
2309 self.weight
2310 }
2311
2312 pub fn tradingsession(&self) -> u8 {
2314 self.tradingsession
2315 }
2316
2317 pub fn trade_session_date(&self) -> NaiveDate {
2319 self.trade_session_date
2320 }
2321}
2322
2323pub(crate) struct IndexAnalyticsInput {
2325 pub(crate) indexid: String,
2326 pub(crate) tradedate: NaiveDate,
2327 pub(crate) ticker: String,
2328 pub(crate) shortnames: String,
2329 pub(crate) secid: String,
2330 pub(crate) weight: f64,
2331 pub(crate) tradingsession: i64,
2332 pub(crate) trade_session_date: NaiveDate,
2333}
2334
2335pub fn actual_indexes(indexes: &[Index]) -> impl Iterator<Item = &Index> {
2337 let latest_till = indexes.iter().filter_map(Index::till).max();
2338 indexes
2339 .iter()
2340 .filter(move |index| index.till() == latest_till)
2341}