Skip to main content

pine_core/
bar.rs

1//! A bar of market data, and the series a script runs over.
2
3use crate::SymInfo;
4
5/// Represents a single bar/candle of market data
6#[derive(Debug, Clone, Default)]
7pub struct Bar {
8    pub open: f64,
9    pub high: f64,
10    pub low: f64,
11    pub close: f64,
12    pub volume: f64,
13    pub index: u64,
14    /// The bar's opening time as a UNIX timestamp in milliseconds, exposed to
15    /// scripts as the `time` variable.
16    pub time: i64,
17    /// Barstate flags the host supplies, exposed to scripts as `barstate.*`.
18    /// The first bar of the dataset (`barstate.isfirst`).
19    pub is_first: bool,
20    /// The last bar of the dataset (`barstate.islast`).
21    pub is_last: bool,
22    /// A new bar has just opened (`barstate.isnew`).
23    pub is_new: bool,
24    /// The bar is closed/confirmed (`barstate.isconfirmed`).
25    pub is_confirmed: bool,
26    /// A historical bar (`barstate.ishistory`).
27    pub is_history: bool,
28    /// A real-time bar (`barstate.isrealtime`).
29    pub is_realtime: bool,
30    /// The last historical bar before real-time (`barstate.islastconfirmedhistory`).
31    pub is_last_confirmed_history: bool,
32}
33
34/// One row of raw market data, before it is placed in a series.
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct Ohlcv {
37    /// Opening time as a UNIX timestamp in milliseconds.
38    pub time: i64,
39    pub open: f64,
40    pub high: f64,
41    pub low: f64,
42    pub close: f64,
43    pub volume: f64,
44}
45
46/// Everything a script needs to know about the market it is running on: the
47/// bars themselves, and the symbol they belong to.
48///
49/// These travel together because they come from the same place — whatever hands
50/// you BTCUSD candles also knows it is BTCUSD. Splitting them would let a caller
51/// describe bars as something they are not.
52#[derive(Debug, Clone, Default)]
53pub struct Data {
54    /// Exposed to the script as `syminfo.*`.
55    pub syminfo: SymInfo,
56    /// Oldest first. A script is replayed over all of them.
57    pub bars: Vec<Bar>,
58}
59
60impl Data {
61    /// Bars for an unnamed symbol. Use [`with_syminfo`](Self::with_syminfo) to
62    /// say what they actually are.
63    pub fn new(bars: Vec<Bar>) -> Self {
64        Self {
65            syminfo: SymInfo::default(),
66            bars,
67        }
68    }
69
70    /// Build a series from raw rows, stamping on the positional metadata: the
71    /// bar index, and the barstate flags that follow from where a bar sits.
72    ///
73    /// Every bar of a completed series is closed, so they are all confirmed
74    /// history; only the first and last are distinguished.
75    pub fn from_ohlcv(rows: impl IntoIterator<Item = Ohlcv>) -> Self {
76        let rows: Vec<Ohlcv> = rows.into_iter().collect();
77        let last = rows.len().saturating_sub(1);
78
79        let bars = rows
80            .into_iter()
81            .enumerate()
82            .map(|(index, row)| Bar {
83                open: row.open,
84                high: row.high,
85                low: row.low,
86                close: row.close,
87                volume: row.volume,
88                index: index as u64,
89                time: row.time,
90                is_first: index == 0,
91                is_last: index == last,
92                is_new: true,
93                is_confirmed: true,
94                is_history: true,
95                is_realtime: false,
96                is_last_confirmed_history: index == last,
97            })
98            .collect();
99
100        Self { ..Self::new(bars) }
101    }
102
103    pub fn with_syminfo(mut self, syminfo: SymInfo) -> Self {
104        self.syminfo = syminfo;
105        self
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    fn row(time: i64) -> Ohlcv {
114        Ohlcv {
115            time,
116            open: 1.0,
117            high: 2.0,
118            low: 0.5,
119            close: 1.5,
120            volume: 10.0,
121        }
122    }
123
124    #[test]
125    fn flags_mark_the_ends_of_the_series() {
126        let data = Data::from_ohlcv([row(0), row(1), row(2)]);
127
128        assert_eq!(data.bars.len(), 3);
129        assert!(data.bars[0].is_first && !data.bars[0].is_last);
130        assert!(!data.bars[1].is_first && !data.bars[1].is_last);
131        assert!(!data.bars[2].is_first && data.bars[2].is_last);
132        assert_eq!(data.bars[2].index, 2);
133        assert!(data
134            .bars
135            .iter()
136            .all(|bar| bar.is_history && bar.is_confirmed));
137    }
138
139    #[test]
140    fn a_single_bar_is_both_ends() {
141        let data = Data::from_ohlcv([row(0)]);
142        assert!(data.bars[0].is_first && data.bars[0].is_last);
143    }
144
145    #[test]
146    fn an_empty_series_has_no_bars() {
147        assert!(Data::from_ohlcv([]).bars.is_empty());
148    }
149}