use super::errors::AnalyzeErorr;
use crate::objects::bar::RawBarBuilder;
use crate::objects::{
bar::{NewBar, NewBarBuilder, RawBar},
bi::{BI, BIBuilder},
direction::Direction,
freq::Freq,
fx::{FX, FXBuilder},
mark::Mark,
zs::ZS,
};
use anyhow::Context;
use chrono::DateTime;
use chrono::Utc;
use polars::frame::DataFrame;
use polars::prelude::TimeUnit;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GapInfo {
pub kind: String,
pub cover: String,
pub sdt: DateTime<Utc>,
pub edt: DateTime<Utc>,
pub high: f64,
pub low: f64,
pub delta: f64,
}
pub fn get_zs_seq(bis: &[BI]) -> Vec<ZS> {
let mut zs_list = Vec::new();
for bi in bis.iter().cloned() {
let Some(last_zs) = zs_list.pop() else {
zs_list.push(ZS::new(vec![bi]));
continue;
};
if (bi.direction == Direction::Up && bi.get_high() < last_zs.zd)
|| (bi.direction == Direction::Down && bi.get_low() > last_zs.zg)
{
zs_list.push(last_zs);
zs_list.push(ZS::new(vec![bi]));
} else {
let mut new_bis = last_zs.bis;
new_bis.push(bi);
zs_list.push(ZS::new(new_bis));
}
}
zs_list
}
pub fn is_symmetry_zs(bis: &[BI], threshold: f64) -> bool {
if bis.len() < 3 || bis.len().is_multiple_of(2) || !threshold.is_finite() || threshold < 0.0 {
return false;
}
let zs = ZS::new(bis.to_vec());
let max_low = bis
.iter()
.map(BI::get_low)
.fold(f64::NEG_INFINITY, f64::max);
let min_high = bis.iter().map(BI::get_high).fold(f64::INFINITY, f64::min);
if zs.zd > zs.zg || max_low > min_high {
return false;
}
let powers: Vec<f64> = bis.iter().map(BI::get_power_price).collect();
let mean = powers.iter().sum::<f64>() / powers.len() as f64;
if !mean.is_finite() || mean == 0.0 {
return false;
}
let variance = powers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / powers.len() as f64;
let std = variance.sqrt();
std.is_finite() && std / mean.abs() <= threshold
}
fn bis_are_chronological(bis: &[BI]) -> bool {
bis.windows(2).all(|w| w[0].end_dt() < w[1].end_dt())
}
pub fn is_bis_up(bis: &[BI]) -> bool {
if bis.len() < 3 || bis.len().is_multiple_of(2) || !bis_are_chronological(bis) {
return false;
}
let first = &bis[0];
let last = &bis[bis.len() - 1];
last.direction == Direction::Up
&& last.get_high()
== bis
.iter()
.map(BI::get_high)
.fold(f64::NEG_INFINITY, f64::max)
&& first.get_low() == bis.iter().map(BI::get_low).fold(f64::INFINITY, f64::min)
}
pub fn is_bis_down(bis: &[BI]) -> bool {
if bis.len() < 3 || bis.len().is_multiple_of(2) || !bis_are_chronological(bis) {
return false;
}
let first = &bis[0];
let last = &bis[bis.len() - 1];
last.direction == Direction::Down
&& first.get_high()
== bis
.iter()
.map(BI::get_high)
.fold(f64::NEG_INFINITY, f64::max)
&& last.get_low() == bis.iter().map(BI::get_low).fold(f64::INFINITY, f64::min)
}
pub fn check_gap_info(bars: &[RawBar]) -> Vec<GapInfo> {
let mut gaps = Vec::new();
for i in 1..bars.len() {
let bar1 = &bars[i - 1];
let bar2 = &bars[i];
let right = &bars[i..];
let gap = if bar1.high < bar2.low {
Some(GapInfo {
kind: "向上缺口".to_string(),
cover: if right.iter().any(|x| x.low < bar1.high) {
"已补".to_string()
} else {
"未补".to_string()
},
sdt: bar1.dt,
edt: bar2.dt,
high: bar2.low,
low: bar1.high,
delta: ((bar2.low / bar1.high - 1.0) * 10_000.0).round() / 10_000.0,
})
} else if bar1.low > bar2.high {
Some(GapInfo {
kind: "向下缺口".to_string(),
cover: if right.iter().any(|x| x.high > bar1.low) {
"已补".to_string()
} else {
"未补".to_string()
},
sdt: bar1.dt,
edt: bar2.dt,
high: bar1.low,
low: bar2.high,
delta: ((bar1.low / bar2.high - 1.0) * 10_000.0).round() / 10_000.0,
})
} else {
None
};
if let Some(gap) = gap {
gaps.push(gap);
}
}
gaps
}
pub fn remove_include(
k1: &NewBar,
k2: &NewBar,
k3: RawBar,
) -> Result<(bool, NewBar), AnalyzeErorr> {
let direction = if k1.high < k2.high {
Direction::Up
} else if k1.high > k2.high {
Direction::Down
} else {
return Ok((false, NewBar::new_from_raw(&k3)));
};
let has_inclusion =
(k2.high <= k3.high && k2.low >= k3.low) || (k2.high >= k3.high && k2.low <= k3.low);
if !has_inclusion {
return Ok((false, NewBar::new_from_raw(&k3)));
}
let (high, low, dt) = match direction {
Direction::Up => {
let high = k2.high.max(k3.high);
let low = k2.low.max(k3.low);
let dt = if k2.high > k3.high { k2.dt } else { k3.dt };
(high, low, dt)
}
Direction::Down => {
let high = k2.high.min(k3.high);
let low = k2.low.min(k3.low);
let dt = if k2.low < k3.low { k2.dt } else { k3.dt };
(high, low, dt)
}
};
let (open_, close) = if k3.open > k3.close {
(high, low)
} else {
(low, high)
};
let k4 = {
let k3_dt = k3.dt;
NewBarBuilder::default()
.symbol(k2.symbol.clone())
.id(k2.id)
.freq(k2.freq)
.dt(dt)
.open(open_)
.close(close)
.high(high)
.low(low)
.vol(k2.vol + k3.vol)
.amount(k2.amount + k3.amount)
.elements(
k2.elements
.iter()
.take(100)
.filter(|x| x.dt != k3_dt)
.cloned()
.chain(std::iter::once(k3))
.collect::<Vec<RawBar>>(),
)
.build()
.context("Failed to new NewBar")?
};
Ok((true, k4))
}
pub fn check_fxs<B: AsRef<NewBar>>(bars: &[B]) -> Vec<FX> {
let mut fxs: Vec<FX> = Vec::new();
for window in bars[0..bars.len()].windows(3) {
if let [k1, k2, k3] = window
&& let Some(fx1) = check_fx(k1.as_ref(), k2.as_ref(), k3.as_ref())
{
if fxs.len() >= 2 && fx1.mark == fxs.last().unwrap().mark {
eprintln!(
"check_fxs错误: {},{:?},{:?}",
k2.as_ref().dt,
fx1.mark,
fxs.last().unwrap().mark
);
} else {
fxs.push(fx1);
}
}
}
fxs
}
pub fn check_fx(k1: &NewBar, k2: &NewBar, k3: &NewBar) -> Option<FX> {
if k1.high < k2.high && k2.high > k3.high && k1.low < k2.low && k2.low > k3.low {
return Some(
FXBuilder::default()
.symbol(k1.symbol.clone())
.dt(k2.dt)
.mark(Mark::G)
.high(k2.high)
.low(k2.low)
.fx(k2.high)
.elements(vec![k1.clone(), k2.clone(), k3.clone()])
.build()
.unwrap(),
);
}
if k1.low > k2.low && k2.low < k3.low && k1.high > k2.high && k2.high < k3.high {
return Some(
FXBuilder::default()
.symbol(k1.symbol.clone())
.dt(k2.dt)
.mark(Mark::D)
.high(k2.high)
.low(k2.low)
.fx(k2.low)
.elements(vec![k1.clone(), k2.clone(), k3.clone()])
.build()
.unwrap(),
);
}
None
}
pub fn check_bi<B>(bars: &[B], min_bi_len: usize) -> (Option<BI>, &[B])
where
B: AsRef<NewBar>,
{
let fxs = check_fxs(bars);
if fxs.len() < 2 {
return (None, bars);
}
let fx_a = &fxs[0];
let (direction, fx_b) = match fx_a.mark {
Mark::D => {
let mut fx_b: Option<&FX> = None;
for x in fxs
.iter()
.filter(|x| x.mark == Mark::G && x.dt > fx_a.dt && x.fx > fx_a.fx)
{
match fx_b {
None => fx_b = Some(x),
Some(best) if x.high > best.high => fx_b = Some(x),
_ => {}
}
}
let fx_b = fx_b.cloned();
(Direction::Up, fx_b)
}
Mark::G => {
let mut fx_b: Option<&FX> = None;
for x in fxs
.iter()
.filter(|x| x.mark == Mark::D && x.dt > fx_a.dt && x.fx < fx_a.fx)
{
match fx_b {
None => fx_b = Some(x),
Some(best) if x.low < best.low => fx_b = Some(x),
_ => {}
}
}
let fx_b = fx_b.cloned();
(Direction::Down, fx_b)
}
};
let fx_b = match fx_b {
Some(fx) => fx,
None => return (None, bars),
};
let start_dt = fx_a.elements[0].dt;
let end_dt = fx_b.elements[2].dt;
let start_idx = bars.partition_point(|bar| bar.as_ref().dt < start_dt);
let end_idx = bars.partition_point(|bar| bar.as_ref().dt <= end_dt);
if start_idx >= end_idx {
return (None, bars);
}
let bars_a = &bars[start_idx..end_idx];
let new_start_dt = fx_b.elements[0].dt;
let new_start_idx = bars.partition_point(|bar| bar.as_ref().dt < new_start_dt);
let bars_b = &bars[new_start_idx..];
let ab_include = (fx_a.high > fx_b.high && fx_a.low < fx_b.low)
|| (fx_a.high < fx_b.high && fx_a.low > fx_b.low);
if !ab_include && bars_a.len() >= min_bi_len {
let fxs_filtered: Vec<_> = fxs
.iter()
.filter(|x| x.dt >= start_dt && x.dt <= end_dt)
.cloned()
.collect();
let bi = BIBuilder::default()
.symbol(fx_a.symbol.clone())
.fx_a(fx_a.clone())
.fx_b(fx_b.clone())
.fxs(fxs_filtered)
.direction(direction)
.bars(
bars_a
.iter()
.map(|b| b.as_ref().to_owned())
.collect::<Vec<NewBar>>(),
)
.build()
.unwrap();
(Some(bi), bars_b)
} else {
(None, bars)
}
}
pub fn format_standard_kline(df: DataFrame, freq: Freq) -> Result<Vec<RawBar>, AnalyzeErorr> {
let symbol_col = df.column("symbol")?.str()?;
let dt_col = df.column("dt")?.datetime()?;
let open_col = df.column("open")?.f64()?;
let close_col = df.column("close")?.f64()?;
let high_col = df.column("high")?.f64()?;
let low_col = df.column("low")?.f64()?;
let vol_col = df.column("vol")?.f64()?;
let amount_col = df.column("amount")?.f64()?;
let time_unit = dt_col.time_unit();
let len = df.height();
let mut bars = Vec::with_capacity(len);
for i in 0..len {
let ts = dt_col.phys.get(i).unwrap();
let ns = match time_unit {
TimeUnit::Milliseconds => ts * 1_000_000,
TimeUnit::Microseconds => ts * 1_000,
TimeUnit::Nanoseconds => ts,
};
let dt_utc = DateTime::<Utc>::from_timestamp_nanos(ns);
let bar = RawBarBuilder::default()
.symbol(symbol_col.get(i).unwrap_or(""))
.id(i as i32)
.dt(dt_utc)
.freq(freq)
.open(open_col.get(i).unwrap())
.close(close_col.get(i).unwrap())
.high(high_col.get(i).unwrap())
.low(low_col.get(i).unwrap())
.vol(vol_col.get(i).unwrap())
.amount(amount_col.get(i).unwrap())
.build()
.context("Failed to create raw bar")?;
bars.push(bar);
}
Ok(bars)
}