Skip to main content

brokapi_alor/
util.rs

1//! Small helpers: the identity of an instrument, the timeframe labels and the time conversions.
2
3use brokapi_core::{
4    ConfigError, Error, InstrumentId, InstrumentKey, NativeId, Timeframe, UnixNanos,
5};
6use chrono::{DateTime, TimeZone, Utc};
7
8/// The ticker and the trading mode (board) of an instrument. ALOR needs them as two fields, so the native id
9/// must be [`NativeId::TickerClass`] (the class code is the board, for example `TQBR`).
10pub 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
19/// The exchange an instrument trades on: `SPBX` for the board `SPBX`, `MOEX` for every other board.
20pub 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
28/// The id of an instrument by ticker and board.
29pub 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
39/// The `tf` of the broker for a timeframe: a number of seconds, or `D`, `W`, `M`.
40pub 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
49/// A [`UnixNanos`] as a date.
50pub 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
58/// Unix seconds of a [`UnixNanos`].
59pub 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}