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:
tinkle-community
2025-12-26 00:58:12 +08:00
parent 54b24167a7
commit 1744e7f38e
38 changed files with 6498 additions and 964 deletions

216
web/src/utils/indicators.ts Normal file
View 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
}