2026-05-26 11:12:44 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-05-25 02:16:35 +08:00
|
|
|
|
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 指标(无历史数据时使用)
|
2026-05-25 04:26:15 +08:00
|
|
|
|
pub fn 首次计算(
|
|
|
|
|
|
初始收盘价: f64,
|
|
|
|
|
|
初始时间: i64,
|
|
|
|
|
|
快线周期: i64,
|
|
|
|
|
|
慢线周期: i64,
|
|
|
|
|
|
信号周期: i64,
|
|
|
|
|
|
) -> Self {
|
2026-05-25 02:16:35 +08:00
|
|
|
|
let 快线EMA = 初始收盘价;
|
|
|
|
|
|
let 慢线EMA = 初始收盘价;
|
|
|
|
|
|
let DIF = 快线EMA - 慢线EMA;
|
|
|
|
|
|
let DEA_EMA = DIF;
|
2026-05-28 01:07:16 +08:00
|
|
|
|
let MACD柱 = 2.0 * (DIF - DEA_EMA);
|
2026-05-25 02:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|