use crate::{errors::UtilsError, freq_data::freq_end_time};
use anyhow::Context;
use chrono::{DateTime, Utc};
use czsc_core::czsc_bail;
use czsc_core::objects::{
bar::{RawBar, RawBarBuilder, Symbol},
freq::Freq,
market::Market,
};
use parking_lot::{RwLock, RwLockWriteGuard};
use std::collections::{BTreeMap, VecDeque};
#[cfg(feature = "python")]
use pyo3::prelude::PyDictMethods;
#[cfg(feature = "python")]
use pyo3::types::{PyAnyMethods, PyDict, PyListMethods};
#[cfg(feature = "python")]
use pyo3::{IntoPyObject, PyResult, pyclass, pymethods};
#[cfg(feature = "python")]
use pyo3::{PyObject, Python};
#[cfg(feature = "python")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(module = "czsc._native"))]
pub struct BarGenerator {
market: Market,
base_freq: Freq,
max_count: usize,
pub freq_bars: BTreeMap<Freq, RwLock<VecDeque<RawBar>>>,
}
impl Clone for BarGenerator {
fn clone(&self) -> Self {
let freq_bars = self
.freq_bars
.iter()
.map(|(freq, bars_lock)| {
let bars = bars_lock.read().clone();
(*freq, RwLock::new(bars))
})
.collect();
Self {
market: self.market,
base_freq: self.base_freq,
max_count: self.max_count,
freq_bars,
}
}
}
impl BarGenerator {
pub fn new(
base_freq: Freq,
freqs: Vec<Freq>,
max_count: usize,
market: Market,
) -> Result<Self, UtilsError> {
let bars = freqs
.into_iter()
.chain(std::iter::once(base_freq))
.map(|f| (f, RwLock::new(VecDeque::with_capacity(max_count))))
.collect();
let bg = BarGenerator {
market,
base_freq,
max_count,
freq_bars: bars,
};
Ok(bg)
}
pub fn init_freq_with_bars<I>(&mut self, freq: Freq, bars: I) -> Result<(), UtilsError>
where
I: IntoIterator<Item = RawBar>,
{
if !self.freq_bars.contains_key(&freq) {
czsc_bail!("周期 {} 不在self.bars", freq);
}
if let Some(existing_bars) = self.freq_bars.get(&freq)
&& !existing_bars.read().is_empty()
{
czsc_bail!("self.bars['{}'] 不为空,不允许执行初始化", freq);
}
let bars = bars
.into_iter()
.enumerate()
.map(|(id, mut bar)| {
bar.id = id as i32;
bar
})
.collect();
self.freq_bars.insert(freq, RwLock::new(bars));
Ok(())
}
fn update_freq(
&self,
bar: &RawBar,
freq: Freq,
mut bars: RwLockWriteGuard<'_, VecDeque<RawBar>>,
) -> Result<(), UtilsError> {
let freq_edt = freq_end_time(bar.dt, freq, self.market)?;
if bars.is_empty() {
let new_bar = RawBarBuilder::default()
.symbol(bar.symbol.clone())
.id(0)
.dt(freq_edt)
.freq(freq)
.open(bar.open)
.close(bar.close)
.high(bar.high)
.low(bar.low)
.vol(bar.vol)
.amount(bar.amount)
.build()
.context("Failed to create the first rawbar")?;
if bars.len() == self.max_count {
bars.pop_front();
}
bars.push_back(new_bar);
return Ok(());
}
let last = bars.back().unwrap();
let new_bar = if freq_edt != last.dt {
RawBarBuilder::default()
.symbol(bar.symbol.clone())
.id(last.id + 1)
.dt(freq_edt)
.freq(freq)
.open(bar.open)
.close(bar.close)
.high(bar.high)
.low(bar.low)
.vol(bar.vol)
.amount(bar.amount)
.build()
.context("Failed to create a new rawbar")?
} else {
RawBarBuilder::default()
.symbol(bar.symbol.clone())
.id(last.id)
.dt(freq_edt)
.freq(freq)
.open(last.open)
.close(bar.close)
.high(last.high.max(bar.high))
.low(last.low.min(bar.low))
.vol(last.vol + bar.vol)
.amount(last.amount + bar.amount)
.build()
.context("Failed to create a new rawbar")?
};
if freq_edt != last.dt {
if bars.len() == self.max_count {
bars.pop_front();
}
bars.push_back(new_bar);
} else {
let last_index = bars.len() - 1;
bars[last_index] = new_bar;
}
Ok(())
}
pub fn latest_date(&self) -> Option<DateTime<Utc>> {
self.freq_bars
.values()
.next()
.and_then(|v| v.read().back().cloned())
.map(|b| b.dt)
}
pub fn symbol(&self) -> Option<Symbol> {
self.freq_bars
.values()
.next()
.and_then(|v| v.read().back().cloned())
.map(|b| b.symbol)
}
pub fn update_bar(&self, bar: &RawBar) -> Result<(), UtilsError> {
if bar.freq != self.base_freq {
czsc_bail!(
"输入周期和基准周期不匹配. Expected {}, got {}",
self.base_freq,
bar.freq.to_string()
);
}
if let Some(base_bars) = self.freq_bars.get(&self.base_freq)
&& let Some(last_bar) = base_bars.read().back()
&& last_bar.dt == bar.dt
{
return Ok(());
}
for (freq, bars) in self.freq_bars.iter() {
self.update_freq(bar, *freq, bars.write())?;
}
Ok(())
}
}
#[cfg(feature = "python")]
#[cfg_attr(feature = "python", gen_stub_pymethods)]
#[cfg_attr(feature = "python", pymethods)]
impl BarGenerator {
#[new]
#[pyo3(signature = (base_freq, freqs, max_count = 2000, market = None))]
fn new_py(
base_freq: PyObject,
freqs: PyObject,
max_count: usize,
market: Option<PyObject>,
) -> PyResult<Self> {
use std::str::FromStr;
Python::with_gil(|py| {
let base_freq =
if let Ok(py_str) = base_freq.downcast_bound::<pyo3::types::PyString>(py) {
let py_str = py_str.to_string();
Freq::from_str(&py_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("解析base_freq失败: {e}"))
})?
} else if let Ok(freq) = base_freq.extract::<Freq>(py) {
freq
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"base_freq必须是字符串或Freq枚举",
));
};
let freqs_list = freqs
.downcast_bound::<pyo3::types::PyList>(py)
.map_err(|_| pyo3::exceptions::PyValueError::new_err("freqs必须是列表"))?;
let mut converted_freqs = Vec::new();
for freq_item in freqs_list.iter() {
let freq = if let Ok(py_str) = freq_item.downcast::<pyo3::types::PyString>() {
let py_str = py_str.to_string();
Freq::from_str(&py_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("解析freqs失败: {e}"))
})?
} else if let Ok(freq) = freq_item.extract::<Freq>() {
freq
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"freqs中的每个元素必须是字符串或Freq枚举",
));
};
converted_freqs.push(freq);
}
let market = if let Some(market_obj) = market {
if let Ok(py_str) = market_obj.downcast_bound::<pyo3::types::PyString>(py) {
let py_str = py_str.to_string();
Market::from_str(&py_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("解析market失败: {e}"))
})?
} else if let Ok(market) = market_obj.extract::<Market>(py) {
market
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"market必须是字符串或Market枚举",
));
}
} else {
Market::Default };
let bg = Self::new(base_freq, converted_freqs, max_count, market)?;
Ok(bg)
})
}
fn init_freq_bars(&mut self, freq: PyObject, bars: Vec<RawBar>) -> PyResult<()> {
use std::str::FromStr;
Python::with_gil(|py| {
let freq = if let Ok(py_str) = freq.downcast_bound::<pyo3::types::PyString>(py) {
let py_str = py_str.to_string();
Freq::from_str(&py_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("解析freq失败: {e}"))
})?
} else if let Ok(freq) = freq.extract::<Freq>(py) {
freq
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"freq必须是字符串或Freq枚举",
));
};
self.init_freq_with_bars(freq, bars)?;
Ok(())
})
}
pub fn get_latest_date(&self) -> Option<String> {
self.latest_date().map(|dt| dt.to_string())
}
#[getter]
#[pyo3(name = "symbol")]
fn get_symbol_py(&self) -> Option<String> {
self.freq_bars
.values()
.next()
.and_then(|v| v.read().back().cloned())
.map(|b| b.symbol.to_string())
}
#[getter]
fn base_freq(&self) -> String {
match self.base_freq {
Freq::F1 => "1分钟",
Freq::F2 => "2分钟",
Freq::F3 => "3分钟",
Freq::F4 => "4分钟",
Freq::F5 => "5分钟",
Freq::F6 => "6分钟",
Freq::F10 => "10分钟",
Freq::F12 => "12分钟",
Freq::F15 => "15分钟",
Freq::F20 => "20分钟",
Freq::F30 => "30分钟",
Freq::F60 => "60分钟",
Freq::F120 => "120分钟",
Freq::F240 => "240分钟",
Freq::F360 => "360分钟",
Freq::D => "日线",
Freq::W => "周线",
Freq::M => "月线",
Freq::S => "季线",
Freq::Y => "年线",
Freq::Tick => "Tick",
}
.to_string()
}
#[getter]
fn end_dt(&self, py: Python) -> PyResult<Option<PyObject>> {
match self.latest_date() {
Some(dt) => {
let timestamp = czsc_core::utils::common::create_naive_pandas_timestamp(py, dt)?;
Ok(Some(timestamp))
}
None => Ok(None),
}
}
#[getter]
fn bars(&self, py: Python) -> PyResult<PyObject> {
let dict = PyDict::new(py);
for (freq, bars_lock) in &self.freq_bars {
let bars = bars_lock.read();
let freq_str = match freq {
Freq::Tick => "Tick",
Freq::F1 => "1分钟",
Freq::F2 => "2分钟",
Freq::F3 => "3分钟",
Freq::F4 => "4分钟",
Freq::F5 => "5分钟",
Freq::F6 => "6分钟",
Freq::F10 => "10分钟",
Freq::F12 => "12分钟",
Freq::F15 => "15分钟",
Freq::F20 => "20分钟",
Freq::F30 => "30分钟",
Freq::F60 => "60分钟",
Freq::F120 => "120分钟",
Freq::F240 => "240分钟",
Freq::F360 => "360分钟",
Freq::D => "日线",
Freq::W => "周线",
Freq::M => "月线",
Freq::S => "季线",
Freq::Y => "年线",
};
let py_bars: Vec<RawBar> = bars.iter().cloned().collect();
dict.set_item(freq_str, py_bars)?;
}
Ok(dict.into())
}
#[pyo3(signature = (bar))]
fn update(&self, bar: &RawBar) -> PyResult<()> {
self.update_bar(bar)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
}
fn __reduce__(&self, py: Python) -> PyResult<PyObject> {
let freqs: Vec<String> = self
.freq_bars
.keys()
.filter(|&freq| *freq != self.base_freq) .map(|freq| freq.to_string())
.collect();
let args = (self.base_freq.to_string(), freqs, self.max_count).into_pyobject(py)?;
let state = PyDict::new(py);
state.set_item("market", self.market.to_string())?;
let freq_bars_dict = PyDict::new(py);
for (freq, bars_lock) in &self.freq_bars {
let bars = bars_lock.read();
let bars_list: Vec<_> = bars.iter().cloned().collect();
freq_bars_dict.set_item(freq.to_string(), bars_list)?;
}
state.set_item("freq_bars", freq_bars_dict)?;
let constructor = py.get_type::<Self>();
let result = (constructor, args, state).into_pyobject(py)?;
Ok(result.into())
}
fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> {
use std::str::FromStr;
let state_dict = state.downcast_bound::<PyDict>(py)?;
if let Some(market_item) = state_dict.get_item("market")? {
let market_str: String = market_item.extract()?;
self.market = Market::from_str(&market_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Failed to parse market: {e}"))
})?;
}
if let Some(freq_bars_item) = state_dict.get_item("freq_bars")? {
let freq_bars_dict = freq_bars_item.downcast::<PyDict>()?;
self.freq_bars.clear();
for (freq_str, bars_obj) in freq_bars_dict.iter() {
let freq_str: String = freq_str.extract()?;
let freq = Freq::from_str(&freq_str).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Failed to parse freq: {e}"))
})?;
let bars_list: Vec<RawBar> = bars_obj.extract()?;
let bars_deque: VecDeque<RawBar> = bars_list.into_iter().collect();
self.freq_bars.insert(freq, RwLock::new(bars_deque));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use chrono::{NaiveDateTime, TimeZone};
use super::*;
use std::sync::Arc;
#[test]
fn test_init_freq_bars() {
let mut bg =
BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::Default).unwrap();
let test_bars = vec![
RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 0:0:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F5)
.open(4000.0)
.close(4010.0)
.high(4020.0)
.low(3990.0)
.vol(1000.0)
.amount(4000.0)
.build()
.unwrap(),
RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(2)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 0:6:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F5)
.open(4010.0)
.close(4020.0)
.high(4030.0)
.low(4000.0)
.vol(1200.0)
.amount(4800.0)
.build()
.unwrap(),
];
let result = bg.init_freq_with_bars(Freq::F5, test_bars.clone());
assert!(result.is_ok());
let bars = bg.freq_bars.get(&Freq::F5).unwrap().read();
assert_eq!(bars.len(), 2);
assert_eq!(bars[0].open, 4000.0);
assert_eq!(bars[1].close, 4020.0);
drop(bars);
let result = bg.init_freq_with_bars(Freq::F5, test_bars);
assert!(result.is_err());
let result = bg.init_freq_with_bars(Freq::F15, vec![]);
assert!(result.is_ok());
let bars = bg.freq_bars.get(&Freq::F15).unwrap().read();
assert!(bars.is_empty());
}
#[test]
fn test_update_freq_new_bar() {
let bg = BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::AShare).unwrap();
let bar1 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 2:1:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4000.0)
.close(4010.0)
.high(4020.0)
.low(3990.0)
.vol(1000.0)
.amount(4000.0)
.build()
.unwrap();
let result = bg.update_bar(&bar1);
assert!(result.is_ok());
let five_min_bars = bg.freq_bars.get(&Freq::F5).unwrap().read();
assert_eq!(five_min_bars.len(), 1, "K线柱数量应该为1");
assert_eq!(five_min_bars[0].open, 4000.0, "开盘价应该为4000.0");
assert_eq!(five_min_bars[0].close, 4010.0, "收盘价应该为4010.0");
assert_eq!(five_min_bars[0].high, 4020.0, "最高价应该为4020.0");
assert_eq!(five_min_bars[0].low, 3990.0, "最低价应该为3990.0");
assert_eq!(five_min_bars[0].vol, 1000.0, "成交量应该为1000.0");
}
#[test]
fn test_update_freq_same_bar() {
let bg = BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::AShare).unwrap();
let bar1 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 2:1:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4000.0)
.close(4010.0)
.high(4020.0)
.low(3990.0)
.vol(10.0)
.amount(40.0)
.build()
.unwrap();
bg.update_bar(&bar1).unwrap();
let bar2 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 2:2:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4006.0)
.high(4020.0)
.low(4000.0)
.close(4015.0)
.vol(15.0)
.amount(60.0)
.build()
.unwrap();
bg.update_bar(&bar2).unwrap();
let five_min_bars = bg.freq_bars.get(&Freq::F5).unwrap().read();
assert_eq!(five_min_bars.len(), 1);
assert_eq!(five_min_bars[0].open, 4000.0, "开盘价应该保持不变");
assert_eq!(five_min_bars[0].close, 4015.0, "收盘价应该更新");
assert_eq!(five_min_bars[0].high, 4020.0, "最高价应该取两者的最大值");
assert_eq!(five_min_bars[0].low, 3990.0, "最低价应该取两者的最小值");
assert_eq!(five_min_bars[0].vol, 25.0, "成交量应该累加");
assert_eq!(five_min_bars[0].amount, 100.0, "检查成交额应该累加");
}
#[test]
fn test_update_freq_new_period() {
let bg = BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::AShare).unwrap();
let bar1 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-2 09:31:00", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4000.0)
.high(4010.0)
.low(3990.0)
.close(4005.0)
.vol(10.0)
.amount(40.0)
.build()
.unwrap();
bg.update_bar(&bar1).unwrap();
let bar2 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-2 09:36:00", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4010.0)
.high(4030.0)
.low(4000.0)
.close(4020.0)
.vol(20.0)
.amount(60.0)
.build()
.unwrap();
bg.update_bar(&bar2).unwrap();
let five_min_bars = bg.freq_bars.get(&Freq::F5).unwrap().read();
assert_eq!(five_min_bars.len(), 2, "K线柱数量应该为2");
assert_eq!(five_min_bars[1].open, 4010.0, "开盘价应该保持不变");
assert_eq!(five_min_bars[1].close, 4020.0, "收盘价应该为4020.0");
assert_eq!(five_min_bars[1].high, 4030.0, "最高价应该为4030.0");
assert_eq!(five_min_bars[1].low, 4000.0, "最低价应该为4000.0");
assert_eq!(five_min_bars[1].vol, 20.0, "成交量应该为20.0");
assert_eq!(five_min_bars[1].amount, 60.0, "成交额应该为60.0");
}
#[test]
fn test_update_freq_edge_cases() {
let bg = BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::AShare).unwrap();
let bar1 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-1 2:6:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4000.0)
.high(4010.0)
.low(3990.0)
.close(4005.0)
.vol(10.0)
.amount(40.0)
.build()
.unwrap();
bg.update_bar(&bar1).unwrap();
let bar2 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-1-2 2:6:0", "%Y-%m-%d %H:%M:%S").unwrap(),
))
.freq(Freq::F1)
.open(4010.0)
.high(4030.0)
.low(4000.0)
.close(4020.0)
.vol(20.0)
.amount(60.0)
.build()
.unwrap();
bg.update_bar(&bar2).unwrap();
let five_min_bars = bg.freq_bars.get(&Freq::F5).unwrap().read();
assert_eq!(five_min_bars.len(), 2, "K线柱数量应该为2");
assert_eq!(
five_min_bars[1].id,
five_min_bars[0].id + 1,
"K线柱ID应该连续,第二个K线柱的ID应该比第一个多1"
);
}
#[test]
fn test_update() {
let dt = Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str("2024-12-12 10:01:00", "%Y-%m-%d %H:%M:%S").unwrap(),
);
let bg = BarGenerator::new(Freq::F1, vec![Freq::F5, Freq::F15], 5, Market::AShare).unwrap();
let invalid_freq_bar = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(dt)
.freq(Freq::F5)
.open(4000.0)
.high(4010.0)
.low(3990.0)
.close(4005.0)
.vol(1000.0)
.amount(4000.0)
.build()
.unwrap();
assert!(
bg.update_bar(&invalid_freq_bar).is_err(),
"更新函数应该返回错误,因为传入的K线柱频率无效"
);
let bar1 = RawBarBuilder::default()
.symbol("000016.SH".to_string())
.id(1)
.dt(dt)
.freq(Freq::F1)
.open(4000.0)
.high(4010.0)
.low(3990.0)
.close(4005.0)
.vol(1000.0)
.amount(4000.0)
.build()
.unwrap();
assert!(
bg.update_bar(&bar1).is_ok(),
"更新函数应该成功处理有效的K线柱"
);
assert_eq!(
bg.symbol(),
Some(Arc::from("000016.SH".to_string())),
"更新后的符号应该与K线柱的符号匹配"
);
assert_eq!(
bg.latest_date(),
Some(bar1.dt),
"更新后的结束时间应该与K线柱的时间匹配"
);
assert!(
bg.update_bar(&bar1).is_ok(),
"重复数据应该被成功处理,即更新函数应该返回成功,但重复数据不应影响状态"
);
assert_eq!(
bg.freq_bars.get(&Freq::F1).unwrap().read().len(),
1,
"1分钟周期的K线柱数量应该为1"
);
assert_eq!(
bg.freq_bars.get(&Freq::F5).unwrap().read().len(),
1,
"5分钟周期的K线柱数量应该为1"
);
assert_eq!(
bg.freq_bars.get(&Freq::F15).unwrap().read().len(),
1,
"15分钟周期的K线柱数量应该为1"
);
for i in 2..8 {
let bar = RawBarBuilder::default()
.symbol(Arc::from("000016.SH".to_string()))
.id(i)
.dt(Utc.from_utc_datetime(
&NaiveDateTime::parse_from_str(
format!("2024-1-2 9:3{i}:0").as_str(),
"%Y-%m-%d %H:%M:%S",
)
.unwrap(),
))
.freq(Freq::F1)
.open(4000.0 + i as f64)
.high(4010.0 + i as f64)
.low(3990.0 + i as f64)
.close(4005.0 + i as f64)
.vol(1000.0)
.amount(4000.0)
.build()
.unwrap();
assert!(bg.update_bar(&bar).is_ok(), "更新数据失败");
}
for (_, bars) in bg.freq_bars.iter() {
assert!(bars.read().len() <= 5, "K线数量超过了max_count限制");
}
let bars_5min = bg.freq_bars.get(&Freq::F5).unwrap().read();
let last_5min = bars_5min.back().unwrap();
assert_eq!(
last_5min.freq,
Freq::F5,
"最后一个5分钟周期的K线柱频率应该为F5"
);
let bars_15min = bg.freq_bars.get(&Freq::F15).unwrap().read();
let last_15min = bars_15min.back().unwrap();
assert_eq!(
last_15min.freq,
Freq::F15,
"最后一个15分钟周期的K线柱频率应该为F15"
);
let bg_other_market =
BarGenerator::new(Freq::F1, vec![Freq::F15], 5, Market::Futures).unwrap();
let bar_futures = RawBarBuilder::default()
.symbol("IF2403".to_string())
.id(1)
.dt(dt)
.freq(Freq::F1)
.open(5180.0)
.high(5185.0)
.low(5178.0)
.close(5182.0)
.vol(100.0)
.amount(518200.0)
.build()
.unwrap();
assert!(
bg_other_market.update_bar(&bar_futures).is_ok(),
"更新其他市场数据失败"
);
assert_eq!(
bg_other_market.symbol(),
Some(Arc::from("IF2403".to_string())),
"市场符号应该更新为IF2403"
);
}
}