Files
auv/src/lib/indicators.ts
T
Sakurasan 0743014f1a feat: 详情页K线增加技术指标 MA/MACD/RSI/KDJ
- 新增 indicators.ts 指标计算库:SMA/EMA/MACD(12,26,9)/RSI(14)/KDJ(9,3,3)
- KLineChart 支持指标开关:MA(5/10/20/30) 主图叠加、MACD/RSI/KDJ 独立副图 pane
- 图表高度随指标 pane 数量动态增长(主图 300 + 每个副图 95),主图不被压缩
- 指标全部前端本地计算,无需后端
2026-08-28 12:05:37 +08:00

140 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 技术指标计算(纯函数,前端本地计算)
* 输入 K 线数组(时间升序),输出与输入等长、前段为 null 的指标序列。
*/
export interface KBar {
time: string;
open: number;
close: number;
high: number;
low: number;
volume: number;
}
export type Nums = (number | null)[];
/** 简单移动平均 */
export function sma(closes: number[], period: number): Nums {
const out: Nums = new Array(closes.length).fill(null);
let sum = 0;
for (let i = 0; i < closes.length; i++) {
sum += closes[i];
if (i >= period) sum -= closes[i - period];
if (i >= period - 1) out[i] = sum / period;
}
return out;
}
/** EMA(标准 MACD 用的指数平滑) */
export function ema(values: number[], period: number): Nums {
const out: Nums = new Array(values.length).fill(null);
const k = 2 / (period + 1);
let prev: number | null = null;
for (let i = 0; i < values.length; i++) {
if (i === period - 1) {
// 首值取前 period 个的 SMA
let s = 0;
for (let j = 0; j < period; j++) s += values[j];
prev = s / period;
out[i] = prev;
} else if (prev !== null) {
prev = values[i] * k + prev * (1 - k);
out[i] = prev;
}
}
return out;
}
/** MACD(12,26,9):返回 DIF、DEA、MACD 柱(柱 = (DIF-DEA)*2,国内口径) */
export function macd(
closes: number[],
fast = 12,
slow = 26,
signal = 9,
): { dif: Nums; dea: Nums; hist: Nums } {
const ef = ema(closes, fast);
const es = ema(closes, slow);
const dif: Nums = closes.map((_, i) =>
ef[i] != null && es[i] != null ? (ef[i] as number) - (es[i] as number) : null,
);
// DEA = DIF 的 9 日 EMA(跳过 null)
const dea: Nums = new Array(closes.length).fill(null);
const difVals: number[] = [];
const difIdx: number[] = [];
for (let i = 0; i < dif.length; i++) {
if (dif[i] != null) {
difVals.push(dif[i] as number);
difIdx.push(i);
}
}
const deaVals = ema(difVals, signal);
for (let j = 0; j < difIdx.length; j++) {
if (deaVals[j] != null) dea[difIdx[j]] = deaVals[j];
}
const hist: Nums = closes.map((_, i) =>
dif[i] != null && dea[i] != null ? ((dif[i] as number) - (dea[i] as number)) * 2 : null,
);
return { dif, dea, hist };
}
/** RSI(Wilder 平滑,国内软件常用 SMA(X,N,1) 等价 Wilder) */
export function rsi(closes: number[], period = 14): Nums {
const out: Nums = new Array(closes.length).fill(null);
if (closes.length <= period) return out;
let avgGain = 0;
let avgLoss = 0;
// 首段:前 period 个变动的简单均值
for (let i = 1; i <= period; i++) {
const ch = closes[i] - closes[i - 1];
if (ch > 0) avgGain += ch;
else avgLoss -= ch;
}
avgGain /= period;
avgLoss /= period;
out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
// Wilder 平滑
for (let i = period + 1; i < closes.length; i++) {
const ch = closes[i] - closes[i - 1];
const gain = ch > 0 ? ch : 0;
const loss = ch < 0 ? -ch : 0;
avgGain = (avgGain * (period - 1) + gain) / period;
avgLoss = (avgLoss * (period - 1) + loss) / period;
out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
}
return out;
}
/** KDJ(9,3,3) */
export function kdj(
bars: KBar[],
n = 9,
m1 = 3,
m2 = 3,
): { k: Nums; d: Nums; j: Nums } {
const len = bars.length;
const k: Nums = new Array(len).fill(null);
const d: Nums = new Array(len).fill(null);
const j: Nums = new Array(len).fill(null);
let prevK = 50;
let prevD = 50;
for (let i = 0; i < len; i++) {
const from = Math.max(0, i - n + 1);
let hh = -Infinity;
let ll = Infinity;
for (let x = from; x <= i; x++) {
hh = Math.max(hh, bars[x].high);
ll = Math.min(ll, bars[x].low);
}
const rsv = hh === ll ? 50 : ((bars[i].close - ll) / (hh - ll)) * 100;
const kv = (2 * prevK + rsv) / m1; // 国内口径 K = 2/3 前值 + 1/3 RSV
const dv = (2 * prevD + kv) / m2;
prevK = kv;
prevD = dv;
k[i] = kv;
d[i] = dv;
j[i] = 3 * kv - 2 * dv;
}
return { k, d, j };
}