1use brokapi_core::{
4 ConfigError, Error, InstrumentId, InstrumentKey, NativeId, Timeframe, UnixNanos,
5};
6use chrono::{DateTime, TimeZone, Utc};
7
8pub fn ticker_board(id: &InstrumentId) -> Result<(String, String), Error> {
11 match &id.native {
12 NativeId::TickerClass { ticker, class_code } => Ok((ticker.clone(), class_code.clone())),
13 _ => Err(Error::Config(ConfigError::Message(
14 "the native id of an instrument must be TickerClass (ticker and board) for ALOR".into(),
15 ))),
16 }
17}
18
19pub fn exchange_of_board(board: &str) -> &'static str {
21 if board.eq_ignore_ascii_case("SPBX") {
22 "SPBX"
23 } else {
24 "MOEX"
25 }
26}
27
28pub fn instrument_id(ticker: &str, board: &str) -> InstrumentId {
30 InstrumentId::new(
31 InstrumentKey::new(ticker, board),
32 NativeId::TickerClass {
33 ticker: ticker.to_string(),
34 class_code: board.to_string(),
35 },
36 )
37}
38
39pub fn timeframe_label(tf: Timeframe) -> String {
41 match tf {
42 Timeframe::D1 => "D".to_string(),
43 Timeframe::W1 => "W".to_string(),
44 Timeframe::MN => "M".to_string(),
45 other => other.seconds().to_string(),
46 }
47}
48
49pub fn nanos_to_datetime(nanos: UnixNanos) -> DateTime<Utc> {
51 let secs = nanos.as_nanos().div_euclid(1_000_000_000);
52 let nsec = nanos.as_nanos().rem_euclid(1_000_000_000) as u32;
53 Utc.timestamp_opt(secs, nsec)
54 .single()
55 .unwrap_or_else(Utc::now)
56}
57
58pub fn nanos_to_secs(nanos: UnixNanos) -> i64 {
60 nanos.as_nanos().div_euclid(1_000_000_000)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn timeframes_are_seconds_or_letters() {
69 assert_eq!(timeframe_label(Timeframe::M1), "60");
70 assert_eq!(timeframe_label(Timeframe::M10), "600");
71 assert_eq!(timeframe_label(Timeframe::H4), "14400");
72 assert_eq!(timeframe_label(Timeframe::D1), "D");
73 assert_eq!(timeframe_label(Timeframe::MN), "M");
74 }
75
76 #[test]
77 fn the_exchange_follows_the_board() {
78 assert_eq!(exchange_of_board("TQBR"), "MOEX");
79 assert_eq!(exchange_of_board("SPBFUT"), "MOEX");
80 assert_eq!(exchange_of_board("SPBX"), "SPBX");
81 }
82
83 #[test]
84 fn an_id_of_another_broker_is_refused() {
85 let id = InstrumentId::new(
86 InstrumentKey::new("SBER", "MISX"),
87 NativeId::SymbolMic {
88 symbol: "SBER".into(),
89 mic: "MISX".into(),
90 },
91 );
92 assert!(matches!(ticker_board(&id), Err(Error::Config(_))));
93 }
94}