优化:折线图初始化数据
This commit is contained in:
parent
fef3d86fc2
commit
1a63d2d6e1
@ -191,9 +191,10 @@ export interface FetchCryptoChartResponse {
|
||||
*/
|
||||
async function fetchBinanceKlines(
|
||||
binanceSymbol: string,
|
||||
limit: number
|
||||
limit: number,
|
||||
interval: '1s' | '1m' = '1m'
|
||||
): Promise<CryptoChartPoint[]> {
|
||||
const url = `${BINANCE_REST}/klines?symbol=${binanceSymbol}&interval=1m&limit=${limit}`
|
||||
const url = `${BINANCE_REST}/klines?symbol=${binanceSymbol}&interval=${interval}&limit=${limit}`
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) throw new Error(`Binance API: ${res.status}`)
|
||||
const raw = (await res.json()) as [number, string, string, string, string, number, ...unknown[]][]
|
||||
@ -223,9 +224,41 @@ async function fetchCoinGeckoChart(
|
||||
|
||||
/**
|
||||
* 拉取加密货币价格历史
|
||||
* 优先 Binance(1 分钟粒度),不支持时回退 CoinGecko
|
||||
* 优先 Binance(1 分钟粒度;30S 场景为 1 秒粒度以铺满视窗),不支持时回退 CoinGecko
|
||||
*/
|
||||
const THIRTY_SEC_MS = 30 * 1000
|
||||
/**
|
||||
* 30S 细粒度模式下内存中保留的历史跨度(毫秒)。
|
||||
* 需 ≥ 图表 timeScale 实际展示的时间宽度;仅用「最近 30s」裁剪时会把已拉取的 1s K 线丢掉大半,屏内左侧会变空。
|
||||
*/
|
||||
export const CRYPTO_30S_RETENTION_MS = 120 * 1000
|
||||
|
||||
/** 30S 初始:拉取的 1s K 线条数,应略大于 {@link CRYPTO_30S_RETENTION_MS}(秒)以抵消网络延迟与 K 线闭合同步 */
|
||||
const CRYPTO_30S_INITIAL_1S_LIMIT = 150
|
||||
|
||||
/** 加密货币分时折线图 X 轴 / 补点统一时间步长(ms),与图表 timeScale 一致 */
|
||||
export const CRYPTO_CHART_TIME_STEP_MS = 100
|
||||
|
||||
/** 将毫秒时间戳对齐到 {@link CRYPTO_CHART_TIME_STEP_MS} 网格(向下取整,单调递增友好) */
|
||||
export function alignCryptoChartTimeMs(tsMs: number): number {
|
||||
const step = CRYPTO_CHART_TIME_STEP_MS
|
||||
return Math.floor(tsMs / step) * step
|
||||
}
|
||||
|
||||
/** 同一时间对齐戳保留最后一条,避免与 realtime 合并时重复键 */
|
||||
function dedupeLatestByTime(points: CryptoChartPoint[]): CryptoChartPoint[] {
|
||||
if (points.length <= 1) return points
|
||||
const sorted = [...points].sort((a, b) => a[0] - b[0])
|
||||
const out: CryptoChartPoint[] = []
|
||||
for (const pt of sorted) {
|
||||
const last = out[out.length - 1]
|
||||
if (last && last[0] === pt[0]) {
|
||||
out[out.length - 1] = pt
|
||||
} else {
|
||||
out.push(pt)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export async function fetchCryptoChart(
|
||||
params: FetchCryptoChartParams
|
||||
@ -243,10 +276,21 @@ export async function fetchCryptoChart(
|
||||
|
||||
try {
|
||||
if (useBinance) {
|
||||
let points = await fetchBinanceKlines(binanceSymbol, limit)
|
||||
let points: CryptoChartPoint[]
|
||||
if (is30S) {
|
||||
const cutoff = Date.now() - THIRTY_SEC_MS
|
||||
points = points.filter(([ts]) => ts >= cutoff)
|
||||
points = await fetchBinanceKlines(
|
||||
binanceSymbol,
|
||||
Math.min(CRYPTO_30S_INITIAL_1S_LIMIT, 1000),
|
||||
'1s'
|
||||
)
|
||||
const cutoff = Date.now() - CRYPTO_30S_RETENTION_MS
|
||||
points = dedupeLatestByTime(
|
||||
points
|
||||
.map(([ts, p]) => [alignCryptoChartTimeMs(ts), p] as CryptoChartPoint)
|
||||
.filter(([ts]) => ts >= cutoff)
|
||||
)
|
||||
} else {
|
||||
points = await fetchBinanceKlines(binanceSymbol, limit, '1m')
|
||||
}
|
||||
return { code: 0, data: points, msg: 'ok' }
|
||||
}
|
||||
@ -254,7 +298,7 @@ export async function fetchCryptoChart(
|
||||
const days = RANGE_TO_DAYS[fetchRange] ?? 7
|
||||
let points = await fetchCoinGeckoChart(coinId, days)
|
||||
if (is30S) {
|
||||
const cutoff = Date.now() - THIRTY_SEC_MS
|
||||
const cutoff = Date.now() - CRYPTO_30S_RETENTION_MS
|
||||
points = points.filter(([ts]) => ts >= cutoff)
|
||||
}
|
||||
return { code: 0, data: points, msg: 'ok' }
|
||||
@ -278,15 +322,6 @@ interface BinanceAggTradeMsg {
|
||||
|
||||
const CRYPTO_UPDATE_THROTTLE_MS = 80
|
||||
|
||||
/** 加密货币分时折线图 X 轴 / 补点统一时间步长(ms),与图表 timeScale 一致 */
|
||||
export const CRYPTO_CHART_TIME_STEP_MS = 100
|
||||
|
||||
/** 将毫秒时间戳对齐到 {@link CRYPTO_CHART_TIME_STEP_MS} 网格(向下取整,单调递增友好) */
|
||||
export function alignCryptoChartTimeMs(tsMs: number): number {
|
||||
const step = CRYPTO_CHART_TIME_STEP_MS
|
||||
return Math.floor(tsMs / step) * step
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅 Binance 归集交易 WebSocket(aggTrade),实现更丝滑的实时走势
|
||||
* 参考:https://developers.binance.com/docs/zh-CN/binance-spot-api-docs/web-socket-streams#归集交易
|
||||
|
||||
@ -414,6 +414,7 @@ import {
|
||||
subscribeCryptoRealtime,
|
||||
alignCryptoChartTimeMs,
|
||||
CRYPTO_CHART_TIME_STEP_MS,
|
||||
CRYPTO_30S_RETENTION_MS,
|
||||
} from '../api/cryptoChart'
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -1225,7 +1226,7 @@ const lineColor = '#2563eb'
|
||||
/** 叠加价格轴 ID:曲线绑定在 overlay scale,主图占满宽;Y 轴数字由 InPanePriceScalePrimitive 画在图内 */
|
||||
const CHART_OVERLAY_PRICE_SCALE_ID = 'overlay-price'
|
||||
/** 右侧留白(px):fitContent 时压缩可用宽度,线头略离开物理右缘,lastPrice 水波能露出一半且不依赖超大 rightOffset(bars) */
|
||||
const CHART_RIPPLE_RIGHT_PAD_PX = 28
|
||||
const CHART_RIPPLE_RIGHT_PAD_PX = 0
|
||||
|
||||
function ensureChartSeries() {
|
||||
if (!chartInstance || !chartContainerRef.value) return
|
||||
@ -1392,7 +1393,7 @@ function applyCryptoRealtimePoint(point: [number, number]) {
|
||||
let updatePoint: [number, number] | null = null
|
||||
|
||||
if (range === '30S') {
|
||||
const cutoff = Date.now() - 30 * 1000
|
||||
const cutoff = Date.now() - CRYPTO_30S_RETENTION_MS
|
||||
if (prevLastT != null && ts === prevLastT) {
|
||||
list[list.length - 1] = [ts, price]
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user