第一版
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 随机指标 (KDJ)
|
||||
///
|
||||
/// 使用滑动窗口 + 逐值平滑进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 随机指标 {
|
||||
pub 时间戳: i64,
|
||||
pub 最高价: f64,
|
||||
pub 最低价: f64,
|
||||
pub 收盘价: f64,
|
||||
pub N: i64,
|
||||
pub M1: i64,
|
||||
pub M2: i64,
|
||||
pub 超买阈值: f64,
|
||||
pub 超卖阈值: f64,
|
||||
pub RSV: Option<f64>,
|
||||
pub K: Option<f64>,
|
||||
pub D: Option<f64>,
|
||||
pub J: Option<f64>,
|
||||
pub 历史最高价队列: Vec<f64>,
|
||||
pub 历史最低价队列: Vec<f64>,
|
||||
pub 前一个RSV: Option<f64>,
|
||||
pub 前一个K: Option<f64>,
|
||||
pub 前一个D: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for 随机指标 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
最高价: 0.0,
|
||||
最低价: 0.0,
|
||||
收盘价: 0.0,
|
||||
N: 9,
|
||||
M1: 3,
|
||||
M2: 3,
|
||||
超买阈值: 80.0,
|
||||
超卖阈值: 20.0,
|
||||
RSV: None,
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: Vec::new(),
|
||||
历史最低价队列: Vec::new(),
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 随机指标 {
|
||||
/// 首次计算 KDJ(无历史数据时)
|
||||
pub fn 首次计算(
|
||||
初始最高价: f64,
|
||||
初始最低价: f64,
|
||||
初始收盘价: f64,
|
||||
初始时间: i64,
|
||||
N: i64,
|
||||
M1: i64,
|
||||
M2: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
) -> Self {
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
最高价: 初始最高价,
|
||||
最低价: 初始最低价,
|
||||
收盘价: 初始收盘价,
|
||||
N,
|
||||
M1,
|
||||
M2,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSV: None,
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: vec![初始最高价],
|
||||
历史最低价队列: vec![初始最低价],
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 KDJ 增量计算当前 KDJ
|
||||
pub fn 增量计算(前一个KDJ: &Self, 当前最高价: f64, 当前最低价: f64, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
let N = 前一个KDJ.N;
|
||||
let M1 = 前一个KDJ.M1;
|
||||
let M2 = 前一个KDJ.M2;
|
||||
let 超买阈值 = 前一个KDJ.超买阈值;
|
||||
let 超卖阈值 = 前一个KDJ.超卖阈值;
|
||||
|
||||
// 更新历史最高价队列
|
||||
let mut 历史最高价 = 前一个KDJ.历史最高价队列.clone();
|
||||
历史最高价.push(当前最高价);
|
||||
if 历史最高价.len() > N as usize {
|
||||
历史最高价.remove(0);
|
||||
}
|
||||
|
||||
// 更新历史最低价队列
|
||||
let mut 历史最低价 = 前一个KDJ.历史最低价队列.clone();
|
||||
历史最低价.push(当前最低价);
|
||||
if 历史最低价.len() > N as usize {
|
||||
历史最低价.remove(0);
|
||||
}
|
||||
|
||||
// RSV
|
||||
let RSV = if 历史最高价.len() == N as usize && 历史最低价.len() == N as usize {
|
||||
let highest = 历史最高价.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
||||
let lowest = 历史最低价.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||
if (highest - lowest).abs() > f64::EPSILON {
|
||||
Some((当前收盘价 - lowest) / (highest - lowest) * 100.0)
|
||||
} else {
|
||||
Some(50.0)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// K值
|
||||
let K = match RSV {
|
||||
Some(rsv) => match 前一个KDJ.K {
|
||||
None => Some(rsv),
|
||||
Some(prev_k) => Some((prev_k * (M1 - 1) as f64 + rsv) / M1 as f64),
|
||||
},
|
||||
None => 前一个KDJ.K,
|
||||
};
|
||||
|
||||
// D值
|
||||
let D = match K {
|
||||
Some(k) => match 前一个KDJ.D {
|
||||
None => Some(k),
|
||||
Some(prev_d) => Some((prev_d * (M2 - 1) as f64 + k) / M2 as f64),
|
||||
},
|
||||
None => 前一个KDJ.D,
|
||||
};
|
||||
|
||||
// J值
|
||||
let J = match (K, D) {
|
||||
(Some(k), Some(d)) => Some(3.0 * k - 2.0 * d),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
最高价: 当前最高价,
|
||||
最低价: 当前最低价,
|
||||
收盘价: 当前收盘价,
|
||||
N,
|
||||
M1,
|
||||
M2,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSV,
|
||||
K,
|
||||
D,
|
||||
J,
|
||||
历史最高价队列: 历史最高价,
|
||||
历史最低价队列: 历史最低价,
|
||||
前一个RSV: RSV,
|
||||
前一个K: K,
|
||||
前一个D: D,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0);
|
||||
assert_eq!(kdj.历史最高价队列, vec![110.0]);
|
||||
assert_eq!(kdj.历史最低价队列, vec![90.0]);
|
||||
assert_eq!(kdj.K, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_after_n_bars() {
|
||||
let mut kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 5, 3, 3, 80.0, 20.0);
|
||||
|
||||
// 喂入足够数据填充窗口
|
||||
let data = [
|
||||
(112.0, 91.0, 105.0),
|
||||
(115.0, 93.0, 110.0),
|
||||
(113.0, 95.0, 108.0),
|
||||
(116.0, 98.0, 112.0),
|
||||
(118.0, 100.0, 115.0),
|
||||
];
|
||||
for (i, (高, 低, 收)) in data.iter().enumerate() {
|
||||
kdj = 随机指标::增量计算(&kdj, *高, *低, *收, 1001 + i as i64);
|
||||
}
|
||||
|
||||
// 窗口填满后 KDJ 应有值
|
||||
assert!(kdj.K.is_some());
|
||||
assert!(kdj.D.is_some());
|
||||
assert!(kdj.J.is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 平滑异同移动平均线 (MACD)
|
||||
///
|
||||
/// 使用 EMA 递推算法进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 平滑异同移动平均线 {
|
||||
pub 时间戳: i64,
|
||||
pub 收盘价: f64,
|
||||
pub 快线周期: i64,
|
||||
pub 慢线周期: i64,
|
||||
pub 信号周期: i64,
|
||||
pub DIF: Option<f64>,
|
||||
pub DEA: Option<f64>,
|
||||
#[serde(rename = "MACD柱")]
|
||||
#[serde(default)]
|
||||
pub MACD柱: f64,
|
||||
pub 快线EMA: Option<f64>,
|
||||
pub 慢线EMA: Option<f64>,
|
||||
pub DEA_EMA: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for 平滑异同移动平均线 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
收盘价: 0.0,
|
||||
快线周期: 12,
|
||||
慢线周期: 26,
|
||||
信号周期: 9,
|
||||
DIF: None,
|
||||
DEA: None,
|
||||
MACD柱: 0.0,
|
||||
快线EMA: None,
|
||||
慢线EMA: None,
|
||||
DEA_EMA: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn 平滑系数(周期: i64) -> f64 {
|
||||
2.0 / (周期 as f64 + 1.0)
|
||||
}
|
||||
|
||||
impl 平滑异同移动平均线 {
|
||||
/// 首次计算 MACD 指标(无历史数据时使用)
|
||||
pub fn 首次计算(初始收盘价: f64, 初始时间: i64, 快线周期: i64, 慢线周期: i64, 信号周期: i64) -> Self {
|
||||
let 快线EMA = 初始收盘价;
|
||||
let 慢线EMA = 初始收盘价;
|
||||
let DIF = 快线EMA - 慢线EMA;
|
||||
let DEA_EMA = DIF;
|
||||
let MACD柱 = DIF - DEA_EMA;
|
||||
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
收盘价: 初始收盘价,
|
||||
快线周期,
|
||||
慢线周期,
|
||||
信号周期,
|
||||
DIF: Some(DIF),
|
||||
DEA: Some(DEA_EMA),
|
||||
MACD柱,
|
||||
快线EMA: Some(快线EMA),
|
||||
慢线EMA: Some(慢线EMA),
|
||||
DEA_EMA: Some(DEA_EMA),
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 MACD 指标增量计算当前 MACD
|
||||
pub fn 增量计算(前一个MACD: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
// 快线 EMA
|
||||
let 快线EMA = match 前一个MACD.快线EMA {
|
||||
Some(prev) => {
|
||||
当前收盘价 * 平滑系数(前一个MACD.快线周期)
|
||||
+ prev * ((前一个MACD.快线周期 - 1) as f64 / (前一个MACD.快线周期 + 1) as f64)
|
||||
}
|
||||
None => 当前收盘价,
|
||||
};
|
||||
|
||||
// 慢线 EMA
|
||||
let 慢线EMA = match 前一个MACD.慢线EMA {
|
||||
Some(prev) => {
|
||||
当前收盘价 * 平滑系数(前一个MACD.慢线周期)
|
||||
+ prev * ((前一个MACD.慢线周期 - 1) as f64 / (前一个MACD.慢线周期 + 1) as f64)
|
||||
}
|
||||
None => 当前收盘价,
|
||||
};
|
||||
|
||||
// DIF
|
||||
let DIF = 快线EMA - 慢线EMA;
|
||||
|
||||
// DEA_EMA
|
||||
let DEA_EMA = match 前一个MACD.DEA_EMA {
|
||||
Some(prev) => {
|
||||
DIF * 平滑系数(前一个MACD.信号周期)
|
||||
+ prev * ((前一个MACD.信号周期 - 1) as f64 / (前一个MACD.信号周期 + 1) as f64)
|
||||
}
|
||||
None => DIF,
|
||||
};
|
||||
|
||||
// MACD 柱 (注意: Python 版没有 ×2)
|
||||
let MACD柱 = DIF - DEA_EMA;
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
收盘价: 当前收盘价,
|
||||
快线周期: 前一个MACD.快线周期,
|
||||
慢线周期: 前一个MACD.慢线周期,
|
||||
信号周期: 前一个MACD.信号周期,
|
||||
DIF: Some(DIF),
|
||||
DEA: Some(DEA_EMA),
|
||||
MACD柱,
|
||||
快线EMA: Some(快线EMA),
|
||||
慢线EMA: Some(慢线EMA),
|
||||
DEA_EMA: Some(DEA_EMA),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let macd = 平滑异同移动平均线::首次计算(100.0, 1000, 12, 26, 9);
|
||||
assert_eq!(macd.DIF, Some(0.0));
|
||||
assert_eq!(macd.MACD柱, 0.0);
|
||||
assert_eq!(macd.快线EMA, Some(100.0));
|
||||
assert_eq!(macd.慢线EMA, Some(100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_calc() {
|
||||
let first = 平滑异同移动平均线::首次计算(100.0, 1000, 12, 26, 9);
|
||||
|
||||
// 价格上升
|
||||
let second = 平滑异同移动平均线::增量计算(&first, 102.0, 1001);
|
||||
assert!(second.DIF.unwrap() > 0.0);
|
||||
// 快线EMA 应该比慢线EMA 变化更快
|
||||
assert!(second.快线EMA.unwrap() > second.慢线EMA.unwrap());
|
||||
|
||||
// 价格下降
|
||||
let third = 平滑异同移动平均线::增量计算(&second, 98.0, 1002);
|
||||
// DIF 应该变小
|
||||
assert!(third.DIF.unwrap() < second.DIF.unwrap());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
pub mod macd;
|
||||
pub mod kdj;
|
||||
pub mod rsi;
|
||||
|
||||
pub use macd::平滑异同移动平均线;
|
||||
pub use kdj::随机指标;
|
||||
pub use rsi::相对强弱指数;
|
||||
|
||||
/// K线取值 —— 根据计算方式从K线提取对应的价格
|
||||
pub fn K线取值(开盘价: f64, 高: f64, 低: f64, 收盘价: f64, 计算方式: &str) -> f64 {
|
||||
match 计算方式 {
|
||||
"开" => 开盘价,
|
||||
"高" => 高,
|
||||
"低" => 低,
|
||||
"收" => 收盘价,
|
||||
"高低均值" => (高 + 低) / 2.0,
|
||||
"高低收均值" => (高 + 低 + 收盘价) / 3.0,
|
||||
"开高低收均值" => (高 + 低 + 开盘价 + 收盘价) / 4.0,
|
||||
_ => 收盘价,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 相对强弱指数 (RSI)
|
||||
///
|
||||
/// 使用 Wilder 平滑(RMA)进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 相对强弱指数 {
|
||||
pub 时间戳: i64,
|
||||
pub 收盘价: f64,
|
||||
pub 周期: i64,
|
||||
pub 超买阈值: f64,
|
||||
pub 超卖阈值: f64,
|
||||
pub RSI_SMA周期: Option<i64>,
|
||||
pub RSI: Option<f64>,
|
||||
pub 平均上涨: Option<f64>,
|
||||
pub 平均下跌: Option<f64>,
|
||||
pub 上涨幅度: f64,
|
||||
pub 下跌幅度: f64,
|
||||
pub 平滑系数: f64,
|
||||
pub RSI_SMA: Option<f64>,
|
||||
pub RSI历史队列: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Default for 相对强弱指数 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
收盘价: 0.0,
|
||||
周期: 14,
|
||||
超买阈值: 70.0,
|
||||
超卖阈值: 30.0,
|
||||
RSI_SMA周期: None,
|
||||
RSI: None,
|
||||
平均上涨: None,
|
||||
平均下跌: None,
|
||||
上涨幅度: 0.0,
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 0.0,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 相对强弱指数 {
|
||||
/// 首次计算 RSI(历史数据不足时)
|
||||
pub fn 首次计算(
|
||||
初始收盘价: f64,
|
||||
初始时间: i64,
|
||||
周期: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
RSI_SMA周期: Option<i64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
收盘价: 初始收盘价,
|
||||
周期,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSI_SMA周期,
|
||||
RSI: None,
|
||||
平均上涨: Some(0.0),
|
||||
平均下跌: Some(0.0),
|
||||
上涨幅度: 0.0,
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 1.0 / 周期 as f64,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 RSI 增量计算当前 RSI
|
||||
pub fn 增量计算(前一个RSI: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
let 周期 = 前一个RSI.周期;
|
||||
let 超买阈值 = 前一个RSI.超买阈值;
|
||||
let 超卖阈值 = 前一个RSI.超卖阈值;
|
||||
let RSI_SMA周期 = 前一个RSI.RSI_SMA周期;
|
||||
let 平滑系数 = 1.0 / 周期 as f64;
|
||||
|
||||
// 价格变化
|
||||
let 变化 = 当前收盘价 - 前一个RSI.收盘价;
|
||||
let 上涨 = 变化.max(0.0);
|
||||
let 下跌 = (-变化).max(0.0);
|
||||
|
||||
// Wilder 平滑
|
||||
let (平均上涨, 平均下跌) = match (前一个RSI.平均上涨, 前一个RSI.平均下跌) {
|
||||
(Some(prev_up), Some(prev_down)) => {
|
||||
let avg_up = prev_up * (1.0 - 平滑系数) + 上涨 * 平滑系数;
|
||||
let avg_down = prev_down * (1.0 - 平滑系数) + 下跌 * 平滑系数;
|
||||
(avg_up, avg_down)
|
||||
}
|
||||
_ => (上涨, 下跌),
|
||||
};
|
||||
|
||||
// RSI
|
||||
let RSI = if 平均下跌 == 0.0 {
|
||||
if 平均上涨 > 0.0 {
|
||||
100.0
|
||||
} else {
|
||||
50.0
|
||||
}
|
||||
} else {
|
||||
let RS = 平均上涨 / 平均下跌;
|
||||
100.0 - (100.0 / (1.0 + RS))
|
||||
};
|
||||
|
||||
// RSI_SMA
|
||||
let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 {
|
||||
Some(sma周期) if sma周期 > 0 => {
|
||||
let mut 队列 = 前一个RSI.RSI历史队列.clone();
|
||||
队列.push(RSI);
|
||||
if 队列.len() > sma周期 as usize {
|
||||
队列.remove(0);
|
||||
}
|
||||
let sma = if 队列.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(队列.iter().sum::<f64>() / 队列.len() as f64)
|
||||
};
|
||||
(sma, 队列)
|
||||
}
|
||||
_ => (None, Vec::new()),
|
||||
};
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
收盘价: 当前收盘价,
|
||||
周期,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSI_SMA周期,
|
||||
RSI: Some(RSI),
|
||||
平均上涨: Some(平均上涨),
|
||||
平均下跌: Some(平均下跌),
|
||||
上涨幅度: 上涨,
|
||||
下跌幅度: 下跌,
|
||||
平滑系数,
|
||||
RSI_SMA,
|
||||
RSI历史队列,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let rsi = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, None);
|
||||
assert_eq!(rsi.RSI, None);
|
||||
assert_eq!(rsi.平滑系数, 1.0 / 14.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_calc() {
|
||||
let first = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, None);
|
||||
let second = 相对强弱指数::增量计算(&first, 102.0, 1001);
|
||||
// 价格上涨 → RSI > 50
|
||||
assert!(second.RSI.unwrap() > 50.0);
|
||||
|
||||
let third = 相对强弱指数::增量计算(&second, 98.0, 1002);
|
||||
// 价格低于之前 → RSI 下降
|
||||
assert!(third.RSI.unwrap() < second.RSI.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsi_sma() {
|
||||
let mut rsi = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, Some(5));
|
||||
// 喂入多根K线来积累RSI历史队列
|
||||
let prices = [102.0, 103.0, 101.0, 104.0, 105.0, 103.0, 106.0];
|
||||
for (i, price) in prices.iter().enumerate() {
|
||||
rsi = 相对强弱指数::增量计算(&rsi, *price, 1001 + i as i64);
|
||||
}
|
||||
// SMA 应该已被计算(队列够长)
|
||||
assert!(rsi.RSI_SMA.is_some());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user