Skip to content

Commit

Permalink
fix #1; update rustdx version in rustdx-cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
zjp-CN committed Oct 6, 2021
1 parent 0baeca8 commit 5f2bbc9
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
assets
target
rustdx.log
rustdx-cmd/rustdx.log
rustdx-cmd/stocks.csv
rustdx-cmd/eastmoney.csv
Expand Down
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,6 @@

具体文档待补充。

## TODO

近期计划:
- [] 【rustdx】增加 day 前复权测试用例(有些特例 #000001# 上市日因子不是 1,需要 fix bug)
- [] 【rustdx-cmd】针对 ClickHouse 和复权情况下,数据录入与导出子命令

远期计划:
- [] 【rustdx】增加数据源
- [] 【rustdx-cmd】拆分非 tdx 数据源到新 crate

## rustdx-cmd

### 安装
Expand Down
2 changes: 1 addition & 1 deletion rustdx-cmd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ simplelog = {version ="0.10", optional = true}
miniz_oxide = {version ="0.4", optional = true}

[dependencies.rustdx]
version = "0.2.1"
version = "0.2.3"
optional = true
path = "../"

Expand Down
61 changes: 34 additions & 27 deletions src/file/day/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::Path;

#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod fq;

/// 解析 `*.day` 文件中的一条日线数据,即其 32 个字节所代表的所有信息。
Expand All @@ -12,14 +13,14 @@ pub mod fq;
/// [`serde_type::Day`]: crate::serde_type::Day
#[derive(Debug, Clone, Copy)]
pub struct Day {
pub date: u32,
pub code: u32,
pub open: f32,
pub high: f32,
pub low: f32,
pub close: f32,
pub date: u32,
pub code: u32,
pub open: f32,
pub high: f32,
pub low: f32,
pub close: f32,
pub amount: f32,
pub vol: u32,
pub vol: u32,
}

impl Day {
Expand All @@ -46,18 +47,20 @@ impl Day {
/// 日:20210810%10000%100 = 10
pub fn from_bytes(code: u32, arr: &[u8]) -> Self {
use crate::bytes_helper::{f32_from_le_bytes, u32_from_le_bytes};
Self { date: u32_from_le_bytes(arr, 0),
open: u32_from_le_bytes(arr, 4) as f32 / 100.,
high: u32_from_le_bytes(arr, 8) as f32 / 100.,
low: u32_from_le_bytes(arr, 12) as f32 / 100.,
close: u32_from_le_bytes(arr, 16) as f32 / 100.,
amount: f32_from_le_bytes(arr, 20),
vol: u32_from_le_bytes(arr, 24),
code }
Self {
date: u32_from_le_bytes(arr, 0),
open: u32_from_le_bytes(arr, 4) as f32 / 100.,
high: u32_from_le_bytes(arr, 8) as f32 / 100.,
low: u32_from_le_bytes(arr, 12) as f32 / 100.,
close: u32_from_le_bytes(arr, 16) as f32 / 100.,
amount: f32_from_le_bytes(arr, 20),
vol: u32_from_le_bytes(arr, 24),
code,
}
}

/// 一次性以**同步**方式读取单个 `*.day` 文件所有数据,然后转化成 Vec。
pub fn file_into_vec<P: AsRef<Path>>(code: u32, p: P) -> Result<Vec<Day>, crate::Error> {
pub fn file_into_vec<P: AsRef<Path>>(code: u32, p: P) -> crate::Result<Vec<Day>> {
Ok(std::fs::read(p)?.chunks_exact(32).map(|b| Self::from_bytes(code, b)).collect())
}

Expand All @@ -73,20 +76,24 @@ impl Day {
/// 6 位字符串的股票代码;%Y-%m-%d 字符串格式的日期;f64 类型的成交额;u64 类型的 vol 。
#[cfg(feature = "serde")]
pub fn into_serde_type(self) -> crate::serde_type::Day {
crate::serde_type::Day { code: format!("{:06}", self.code),
date: self.date_string(),
open: self.open,
high: self.high,
low: self.low,
close: self.close,
// 单位:元
amount: self.amount,
// 转换成手:方便与其他数据源汇合
vol: self.vol as f32 / 100., }
crate::serde_type::Day {
code: format!("{:06}", self.code),
date: self.date_string(),
open: self.open,
high: self.high,
low: self.low,
close: self.close,
// 单位:元
amount: self.amount,
// 转换成手:方便与其他数据源汇合
vol: self.vol as f32 / 100.,
}
}

/// `%Y-%m-%d` 格式的日期
pub fn date_string(&self) -> String { crate::bytes_helper::date_string(self.date) }
pub fn date_string(&self) -> String {
crate::bytes_helper::date_string(self.date)
}

/// `[年, 月, 日]` 格式的日期
pub fn ymd_arr(&self) -> [u32; 3] {
Expand Down
164 changes: 97 additions & 67 deletions src/file/gbbq/fq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,62 @@ use crate::file::day::Day;
#[cfg(feature = "serde")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Factor {
pub date: String,
pub code: String,
pub date: String,
pub code: String,
#[serde(rename(deserialize = "close"))]
pub preclose: f64,
pub factor: f64,
pub factor: f64,
}

#[cfg(feature = "serde")]
impl Factor {
/// 根据前收、收盘价(最新价)和前一日的因子计算当日的复权因子。
#[inline]
pub fn compute_factor(&self, close: f64) -> f64 { self.factor * (close / self.preclose) }
pub fn compute_factor(&self, close: f64) -> f64 {
self.factor * (close / self.preclose)
}
}

#[derive(Debug, Clone, Copy)]
pub struct Fq {
pub code: u32,
pub code: u32,
/// 年月日
pub date: u32,
pub factor: f64,
pub close: f64,
pub date: u32,
pub factor: f64,
pub close: f64,
pub preclose: f64,
pub trading: bool,
pub xdxr: bool,
pub trading: bool,
pub xdxr: bool,
}

impl Fq {
/// preclose: days.clone().next()?.close
/// factor: 1.
pub fn new(days: impl Iterator<Item = Day> + ExactSizeIterator + Clone, g1: &[Gbbq])
-> Option<Vec<Fq>> {
/// 从上市日开始计算复权。
///
/// ## 注意
/// 1. 上市日因子为 1。所以如果存在除权日先于上市日,直接舍弃先于上市日的除权日,
/// 比如 #000001#、#601975#。
/// 2. 解析后的交易日天数、除权日天数会在 debug build 下校验。
pub fn new(
days: impl Iterator<Item = Day> + ExactSizeIterator + Clone, g1: &[Gbbq],
) -> Option<Vec<Fq>> {
let count = days.len();
let mut fqs: Vec<Fq> = Vec::with_capacity(count + 128);
let mut preclose = days.clone().next()?.close as f64;
let mut factor = 1.;
let mut gbbq = g1.iter();
let mut xdxr = gbbq.next()?;
let mut last = false;
let mut first = 0usize;

for d in days {
for (i, d) in days.enumerate() {
while !last && d.date > xdxr.date {
// 因为停牌或某种原因导致下个交易日晚于除权日
fqs.push(Self::_0(d, xdxr, preclose, &mut factor, false, true));
if i == 0 {
// 为了让上市日因子为 1
first += 1;
} else {
fqs.push(Self::_0(d, xdxr, preclose, &mut factor, false, true));
}
if let Some(x) = gbbq.next() {
xdxr = x;
} else if !last {
Expand All @@ -69,27 +82,33 @@ impl Fq {
}

// 确保所有数据都被正确解析:必须满足两个条件
debug_assert_eq!(count,
fqs.iter().filter(|f| f.trading).count(),
"{} 交易日天数不正确",
g1[0].code);
debug_assert_eq!(if g1[g1.len() - 1].date <= fqs.last().unwrap().date {
g1.len()
} else {
g1.iter().take_while(|g| g.date <= fqs.last().unwrap().date).count()
},
fqs.iter().filter(|f| f.xdxr).count(),
"{} 除权除息日天数不正确:\nleft: {:?}\nright: {:?}",
g1[0].code,
g1,
fqs.iter().filter(|f| f.xdxr).collect::<Vec<_>>());
debug_assert_eq!(
count,
fqs.iter().filter(|f| f.trading).count(),
"{} 交易日天数不正确",
g1[0].code
);
debug_assert_eq!(
if g1.last().unwrap().date <= fqs.last().unwrap().date {
g1.len()
} else {
// gbbq 最新日期大于 day 文件最新日期
g1.iter().take_while(|g| g.date <= fqs.last().unwrap().date).count()
},
fqs.iter().filter(|f| f.xdxr).count() + first,
"{} 除权除息日天数不正确:\nleft: {:?}\nright: {:?}",
g1[0].code,
g1,
fqs.iter().filter(|f| f.xdxr).collect::<Vec<_>>()
);

Some(fqs)
}

pub fn concat(days: impl Iterator<Item = Day> + ExactSizeIterator + Clone, g1: &[Gbbq],
mut preclose: f64, mut factor: f64)
-> Option<Vec<Fq>> {
pub fn concat(
days: impl Iterator<Item = Day> + ExactSizeIterator + Clone, g1: &[Gbbq],
mut preclose: f64, mut factor: f64,
) -> Option<Vec<Fq>> {
let count = days.len();
let mut fqs: Vec<Fq> = Vec::with_capacity(count + 128);
let mut gbbq = g1.iter();
Expand Down Expand Up @@ -122,52 +141,63 @@ impl Fq {
}

// 确保所有数据都被正确解析:必须满足两个条件
debug_assert_eq!(count,
fqs.iter().filter(|f| f.trading).count(),
"{} 交易日天数不正确",
g1[0].code);
debug_assert_eq!(g1.iter()
.filter(|g| g.date >= fqs.first().unwrap().date
&& g.date <= fqs.last().unwrap().date)
.count(),
fqs.iter().filter(|f| f.xdxr).count(),
"{} 除权除息日天数不正确:\nleft: {:?}\nright: {:?}",
g1[0].code,
g1,
fqs.iter().filter(|f| f.xdxr).collect::<Vec<_>>());
debug_assert_eq!(
count,
fqs.iter().filter(|f| f.trading).count(),
"{} 交易日天数不正确",
g1[0].code
);
debug_assert_eq!(
g1.iter()
.filter(
|g| g.date >= fqs.first().unwrap().date && g.date <= fqs.last().unwrap().date
)
.count(),
fqs.iter().filter(|f| f.xdxr).count(),
"{} 除权除息日天数不正确:\nleft: {:?}\nright: {:?}",
g1[0].code,
g1,
fqs.iter().filter(|f| f.xdxr).collect::<Vec<_>>()
);

Some(fqs)
}

pub fn no_gbbq(days: impl Iterator<Item = Day> + ExactSizeIterator + Clone) -> Option<Vec<Fq>> {
let mut preclose = days.clone().next()?.close as f64;
let mut factor = 1.;
Some(days.map(|d| {
let close = d.close as f64;
factor *= close / preclose;
let fq = Self { close,
factor,
preclose,
trading: true,
xdxr: false,
code: d.code,
date: d.date };
preclose = close;
fq
})
.collect())
Some(
days.map(|d| {
let close = d.close as f64;
factor *= close / preclose;
let fq = Self {
close,
factor,
preclose,
trading: true,
xdxr: false,
code: d.code,
date: d.date,
};
preclose = close;
fq
})
.collect(),
)
}

#[inline]
fn _0(d: Day, g: &Gbbq, preclose: f64, factor: &mut f64, trading: bool, xdxr: bool) -> Self {
let [preclose, close, pct] = g.compute_pre_pct(d.close, preclose, xdxr);
*factor *= pct;
Self { close,
preclose,
trading,
xdxr,
code: d.code,
date: d.date,
factor: *factor }
Self {
close,
preclose,
trading,
xdxr,
code: d.code,
date: d.date,
factor: *factor,
}
}
}
18 changes: 18 additions & 0 deletions tests/fq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use insta::assert_debug_snapshot;

#[test]
fn day_sz000001() -> rustdx::Result<()> {
use rustdx::file::{
day::Day,
gbbq::{Fq, Gbbq},
};
let day_src = std::fs::read("tests/assets/sz000001.day")?;
let days = day_src.chunks_exact(32).map(|arr| Day::from_bytes(1, arr));

let mut gbbq_src = std::fs::read("tests/assets/gbbq")?;
let stock_gbbq = Gbbq::filter_hashmap(Gbbq::iter(&mut gbbq_src[4..]));

let fq = Fq::new(days, stock_gbbq.get(&1).unwrap()).unwrap();
assert_debug_snapshot!(&fq[..3]);
Ok(())
}
Loading

0 comments on commit 5f2bbc9

Please sign in to comment.