290 lines
8.5 KiB
Rust
290 lines
8.5 KiB
Rust
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2026 YuYuKunKun
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
use crate::kline::bar::K线;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// 随机指标 (KDJ)
|
|
///
|
|
/// 使用滑动窗口 + 逐值平滑进行增量计算
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(default)]
|
|
pub struct 随机指标 {
|
|
/// 数据时间戳
|
|
pub 时间戳: i64,
|
|
/// 最高价
|
|
pub 最高价: f64,
|
|
/// 最低价
|
|
pub 最低价: f64,
|
|
/// 收盘价
|
|
pub 收盘价: f64,
|
|
/// RSV 周期
|
|
pub N: i64,
|
|
/// K 值平滑周期
|
|
pub M1: i64,
|
|
/// D 值平滑周期
|
|
pub M2: i64,
|
|
/// 超买阈值
|
|
pub 超买阈值: f64,
|
|
/// 超卖阈值
|
|
pub 超卖阈值: f64,
|
|
/// RSV 值(未成熟随机值)
|
|
pub RSV: Option<f64>,
|
|
/// K 值
|
|
pub K: Option<f64>,
|
|
/// D 值
|
|
pub D: Option<f64>,
|
|
/// J 值 (3K - 2D)
|
|
pub J: Option<f64>,
|
|
/// 历史最高价队列(滑动窗口)
|
|
pub 历史最高价队列: Vec<f64>,
|
|
/// 历史最低价队列(滑动窗口)
|
|
pub 历史最低价队列: Vec<f64>,
|
|
/// 前一个 RSV(用于平滑递推)
|
|
pub 前一个RSV: Option<f64>,
|
|
/// 前一个 K(用于平滑递推)
|
|
pub 前一个K: Option<f64>,
|
|
/// 前一个 D(用于平滑递推)
|
|
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(无历史数据时)
|
|
#[allow(clippy::too_many_arguments)]
|
|
/// 首次计算 KDJ — 无历史数据时的初始计算
|
|
#[allow(clippy::too_many_arguments)]
|
|
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 指标 — 从 K线 取值后计算(KDJ 始终使用 高/低/收)
|
|
pub fn 首次计算_K线(
|
|
k线: &K线,
|
|
RSV周期: i64,
|
|
K值平滑周期: i64,
|
|
D值平滑周期: i64,
|
|
超买阈值: f64,
|
|
超卖阈值: f64,
|
|
) -> Self {
|
|
Self::首次计算(
|
|
k线.高,
|
|
k线.低,
|
|
k线.收盘价,
|
|
k线.时间戳,
|
|
RSV周期,
|
|
K值平滑周期,
|
|
D值平滑周期,
|
|
超买阈值,
|
|
超卖阈值,
|
|
)
|
|
}
|
|
|
|
/// 增量计算 KDJ 指标 — 从 K线 取值后递推
|
|
pub fn 增量计算_K线(前一个KDJ: &Self, 当前K线: &K线) -> Self {
|
|
Self::增量计算(
|
|
前一个KDJ,
|
|
当前K线.高,
|
|
当前K线.低,
|
|
当前K线.收盘价,
|
|
当前K线.时间戳,
|
|
)
|
|
}
|
|
|
|
/// 基于前一个 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());
|
|
}
|
|
}
|