Files
nofx/web/src/utils/indicators.ts
tinkle-community 1744e7f38e feat: migrate to CoinAnk API and improve chart UI
- Chart improvements: professional styling, popular symbols quick selection, simplified B/S legend
- Data source migration: use CoinAnk API exclusively for all kline data
- Code cleanup: remove Binance WebSocket cache and related code (websocket_client.go, combined_streams.go, monitor.go)
- Log optimization: reduce hook spam, suppress 404 errors, increase P&L diff threshold
- Lighter integration: add order sync functionality, fix market order precision
- Remove ticker merge logic for simplicity
2025-12-26 00:58:12 +08:00

217 lines
5.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.

// 技术指标计算工具
export interface Kline {
time: number
open: number
high: number
low: number
close: number
volume?: number
}
// 简单移动平均线 (SMA)
export function calculateSMA(data: Kline[], period: number): Array<{ time: number; value: number }> {
const result: Array<{ time: number; value: number }> = []
for (let i = period - 1; i < data.length; i++) {
let sum = 0
for (let j = 0; j < period; j++) {
sum += data[i - j].close
}
result.push({
time: data[i].time,
value: sum / period,
})
}
return result
}
// 指数移动平均线 (EMA)
export function calculateEMA(data: Kline[], period: number): Array<{ time: number; value: number }> {
const result: Array<{ time: number; value: number }> = []
const multiplier = 2 / (period + 1)
// 第一个EMA值使用SMA
let ema = 0
for (let i = 0; i < period; i++) {
ema += data[i].close
}
ema = ema / period
result.push({ time: data[period - 1].time, value: ema })
// 后续EMA值
for (let i = period; i < data.length; i++) {
ema = (data[i].close - ema) * multiplier + ema
result.push({ time: data[i].time, value: ema })
}
return result
}
// MACD 指标
export interface MACDData {
time: number
macd: number
signal: number
histogram: number
}
export function calculateMACD(
data: Kline[],
fastPeriod = 12,
slowPeriod = 26,
signalPeriod = 9
): MACDData[] {
const fastEMA = calculateEMA(data, fastPeriod)
const slowEMA = calculateEMA(data, slowPeriod)
// 计算MACD线
const macdLine: Array<{ time: number; value: number }> = []
for (let i = 0; i < slowEMA.length; i++) {
const fastValue = fastEMA.find(e => e.time === slowEMA[i].time)
if (fastValue) {
macdLine.push({
time: slowEMA[i].time,
value: fastValue.value - slowEMA[i].value,
})
}
}
// 计算信号线MACD的EMA
const signalLine = calculateEMAFromValues(macdLine, signalPeriod)
// 生成MACD数据
const result: MACDData[] = []
for (let i = 0; i < signalLine.length; i++) {
const macdValue = macdLine.find(m => m.time === signalLine[i].time)
if (macdValue) {
result.push({
time: signalLine[i].time,
macd: macdValue.value,
signal: signalLine[i].value,
histogram: macdValue.value - signalLine[i].value,
})
}
}
return result
}
// 从值数组计算EMA辅助函数
function calculateEMAFromValues(
data: Array<{ time: number; value: number }>,
period: number
): Array<{ time: number; value: number }> {
const result: Array<{ time: number; value: number }> = []
const multiplier = 2 / (period + 1)
if (data.length < period) return []
// 第一个EMA值使用SMA
let ema = 0
for (let i = 0; i < period; i++) {
ema += data[i].value
}
ema = ema / period
result.push({ time: data[period - 1].time, value: ema })
// 后续EMA值
for (let i = period; i < data.length; i++) {
ema = (data[i].value - ema) * multiplier + ema
result.push({ time: data[i].time, value: ema })
}
return result
}
// RSI 指标
export function calculateRSI(data: Kline[], period = 14): Array<{ time: number; value: number }> {
const result: Array<{ time: number; value: number }> = []
if (data.length < period + 1) return []
// 计算价格变化
const changes: number[] = []
for (let i = 1; i < data.length; i++) {
changes.push(data[i].close - data[i - 1].close)
}
// 计算初始平均涨跌幅
let avgGain = 0
let avgLoss = 0
for (let i = 0; i < period; i++) {
if (changes[i] > 0) {
avgGain += changes[i]
} else {
avgLoss += Math.abs(changes[i])
}
}
avgGain = avgGain / period
avgLoss = avgLoss / period
// 计算RSI
for (let i = period; i < changes.length; i++) {
const currentChange = changes[i]
if (currentChange > 0) {
avgGain = (avgGain * (period - 1) + currentChange) / period
avgLoss = (avgLoss * (period - 1)) / period
} else {
avgGain = (avgGain * (period - 1)) / period
avgLoss = (avgLoss * (period - 1) + Math.abs(currentChange)) / period
}
const rs = avgGain / avgLoss
const rsi = 100 - 100 / (1 + rs)
result.push({
time: data[i + 1].time,
value: rsi,
})
}
return result
}
// 布林带
export interface BollingerBands {
time: number
upper: number
middle: number
lower: number
}
export function calculateBollingerBands(
data: Kline[],
period = 20,
stdDev = 2
): BollingerBands[] {
const result: BollingerBands[] = []
for (let i = period - 1; i < data.length; i++) {
// 计算SMA
let sum = 0
for (let j = 0; j < period; j++) {
sum += data[i - j].close
}
const sma = sum / period
// 计算标准差
let variance = 0
for (let j = 0; j < period; j++) {
variance += Math.pow(data[i - j].close - sma, 2)
}
const std = Math.sqrt(variance / period)
result.push({
time: data[i].time,
upper: sma + stdDev * std,
middle: sma,
lower: sma - stdDev * std,
})
}
return result
}