Skip to main content

moex_client/models/
selectors.rs

1//! Fluent-утилиты для отбора и сортировки доменных коллекций.
2
3use std::cmp::Ordering;
4
5use super::{Index, IndexAnalytics, SecurityBoard};
6
7/// Fluent-операции над коллекцией индексов.
8pub trait IndexesExt {
9    /// Оставить только индексы с максимальным `till`.
10    fn retain_actual_by_till(&mut self);
11
12    /// Вернуть коллекцию индексов только с максимальным `till`.
13    fn into_actual_by_till(mut self) -> Self
14    where
15        Self: Sized,
16    {
17        self.retain_actual_by_till();
18        self
19    }
20}
21
22impl IndexesExt for Vec<Index> {
23    fn retain_actual_by_till(&mut self) {
24        let latest_till = self.iter().filter_map(Index::till).max();
25        self.retain(|index| index.till() == latest_till);
26    }
27}
28
29/// Fluent-операции над коллекцией состава индекса.
30pub trait IndexAnalyticsExt {
31    /// Оставить только актуальную торговую сессию:
32    /// максимальные `trade_session_date` и `tradingsession`.
33    fn retain_actual_by_session(&mut self);
34
35    /// Вернуть только актуальную торговую сессию.
36    fn into_actual_by_session(mut self) -> Self
37    where
38        Self: Sized,
39    {
40        self.retain_actual_by_session();
41        self
42    }
43
44    /// Отсортировать по убыванию `weight` и затем по `secid`.
45    fn sort_by_weight_desc(&mut self);
46
47    /// Вернуть отсортированную по убыванию `weight` коллекцию.
48    fn into_sorted_by_weight_desc(mut self) -> Self
49    where
50        Self: Sized,
51    {
52        self.sort_by_weight_desc();
53        self
54    }
55}
56
57impl IndexAnalyticsExt for Vec<IndexAnalytics> {
58    fn retain_actual_by_session(&mut self) {
59        // Сначала отбираем записи за последнюю дату торговой сессии.
60        let Some(latest_trade_session_date) =
61            self.iter().map(IndexAnalytics::trade_session_date).max()
62        else {
63            return;
64        };
65        self.retain(|item| item.trade_session_date() == latest_trade_session_date);
66
67        // После фильтра по дате оставляем максимальный номер сессии.
68        let Some(latest_tradingsession) = self.iter().map(IndexAnalytics::tradingsession).max()
69        else {
70            return;
71        };
72        self.retain(|item| item.tradingsession() == latest_tradingsession);
73    }
74
75    fn sort_by_weight_desc(&mut self) {
76        self.sort_by(|left, right| {
77            right
78                .weight()
79                // `weight` заранее валидируется как конечное число, но fallback
80                // защищает от нарушения инварианта в будущем.
81                .partial_cmp(&left.weight())
82                .unwrap_or(Ordering::Equal)
83                .then_with(|| left.secid().as_str().cmp(right.secid().as_str()))
84        });
85    }
86}
87
88/// Fluent-операции выбора режима торгов по коллекции `boards`.
89pub trait SecurityBoardsExt {
90    /// Найти первичный `stock`-режим (`is_primary=1`) или первый `stock`-режим.
91    fn stock_primary_or_first(&self) -> Option<&SecurityBoard>;
92
93    /// Вернуть первичный `stock`-режим (`is_primary=1`) или первый `stock`-режим.
94    fn into_stock_primary_or_first(self) -> Option<SecurityBoard>
95    where
96        Self: Sized;
97}
98
99impl SecurityBoardsExt for Vec<SecurityBoard> {
100    fn stock_primary_or_first(&self) -> Option<&SecurityBoard> {
101        let mut fallback = None;
102        for board in self {
103            if board.engine().as_str() != "stock" {
104                continue;
105            }
106            if board.is_primary() {
107                return Some(board);
108            }
109            if fallback.is_none() {
110                fallback = Some(board);
111            }
112        }
113        fallback
114    }
115
116    fn into_stock_primary_or_first(self) -> Option<SecurityBoard> {
117        let mut fallback = None;
118        for board in self {
119            if board.engine().as_str() != "stock" {
120                continue;
121            }
122            if board.is_primary() {
123                return Some(board);
124            }
125            if fallback.is_none() {
126                fallback = Some(board);
127            }
128        }
129        fallback
130    }
131}