mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-03 19:11:02 +08:00
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
This commit is contained in:
216
web/src/utils/indicators.ts
Normal file
216
web/src/utils/indicators.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
// 技术指标计算工具
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user