mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
fix(web-ui): ECharts 数字两位小数 + 取数自动翻页
图表格式化:新增 format.ts(fmt2/fmtPct),在 KlineChart/EquityChart/ CompareChart/PortfolioCompareChart/OptimizeHeatmap 统一应用——tooltip 用 valueFormatter、axis 用 axisLabel.formatter,所有数值显示两位小数。 取数翻页:fetchBars 当日期跨度超过 800 根时,循环调用 start=0,800,1600... 拼接结果,直到覆盖 startDate 或达上限(10 页 ≈ 32 年日线)。翻页后再按 日期范围闭区间过滤。实测 SZ:000001 翻 2 页拿到 1600 根(2019~2026)。
This commit is contained in:
+32
-15
@@ -47,10 +47,12 @@ export async function fetchStrategies(): Promise<StrategiesResponse> {
|
||||
/**
|
||||
* 按标的取 K 线行情(OHLCV)。
|
||||
*
|
||||
* 后端 /bars 仅支持按 count 取数(上限 800 根,约 3.2 年日线),不支持日期范围。
|
||||
* 这里固定拉满 800 根,由调用方按日期范围在前端过滤。
|
||||
* 后端 /bars 单次最多 800 根。当 startDate 到 endDate 跨度超过 800 根时,
|
||||
* 自动分页拉取(start=0, 800, 1600...)拼接,直到覆盖 startDate 或达上限。
|
||||
* 可选 startDate/endDate 对结果做闭区间过滤(ISO 日期字符串,如 "2024-01-01")。
|
||||
*/
|
||||
const MAX_PAGES = 10 // 翻页上限:10 × 800 = 8000 根(约 32 年日线)
|
||||
|
||||
export async function fetchBars(
|
||||
market: string,
|
||||
code: string,
|
||||
@@ -58,19 +60,34 @@ export async function fetchBars(
|
||||
startDate?: string,
|
||||
endDate?: string,
|
||||
): Promise<Bar[]> {
|
||||
const params = new URLSearchParams({
|
||||
market,
|
||||
code,
|
||||
category,
|
||||
count: '800', // 后端硬上限,前端按日期过滤
|
||||
})
|
||||
const resp = await fetch(`${BASE}/bars?${params}`)
|
||||
if (!resp.ok) await throwError(resp)
|
||||
const body = (await resp.json()) as { data: Record<string, unknown>[] }
|
||||
// 后端 bars 列名不统一:日线及以上是 `date`,分钟线是 `datetime`。
|
||||
// 归一化为统一 `datetime` 字段(取 ISO 前 19 位)。
|
||||
let bars = body.data.map((row) => normalizeBar(row))
|
||||
// 按日期范围过滤(闭区间,比较日期部分 YYYY-MM-DD)
|
||||
let allBars: Bar[] = []
|
||||
for (let page = 0; page < MAX_PAGES; page++) {
|
||||
const params = new URLSearchParams({
|
||||
market,
|
||||
code,
|
||||
category,
|
||||
count: '800',
|
||||
start: String(page * 800),
|
||||
})
|
||||
const resp = await fetch(`${BASE}/bars?${params}`)
|
||||
if (!resp.ok) await throwError(resp)
|
||||
const body = (await resp.json()) as { data: Record<string, unknown>[] }
|
||||
const pageBars = body.data.map((row) => normalizeBar(row))
|
||||
if (pageBars.length === 0) break // 无更多数据
|
||||
|
||||
allBars = allBars.concat(pageBars)
|
||||
|
||||
// 若已覆盖到 startDate(本页最早一根 ≤ startDate),停止翻页
|
||||
if (startDate && pageBars.length > 0) {
|
||||
const oldest = pageBars[pageBars.length - 1].datetime.slice(0, 10)
|
||||
if (oldest <= startDate) break
|
||||
}
|
||||
// 不足 800 根说明已到数据起点
|
||||
if (pageBars.length < 800) break
|
||||
}
|
||||
|
||||
// 按日期范围过滤(闭区间)
|
||||
let bars = allBars
|
||||
if (startDate) bars = bars.filter((b) => b.datetime.slice(0, 10) >= startDate)
|
||||
if (endDate) bars = bars.filter((b) => b.datetime.slice(0, 10) <= endDate)
|
||||
return bars
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import echarts from '../echarts-setup'
|
||||
import { fmt2 } from '../format'
|
||||
import type { BacktestResult } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -34,11 +35,19 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis' },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
valueFormatter: (v: number | string) => fmt2(Number(v)),
|
||||
},
|
||||
legend: { top: 0, data: seriesData.map((s) => s.name) },
|
||||
grid: { left: '8%', right: '5%', top: 30, bottom: 50 },
|
||||
xAxis: { type: 'category', data: allDates, boundaryGap: false },
|
||||
yAxis: { type: 'value', scale: true, name: '归一化净值' },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '归一化净值',
|
||||
axisLabel: { formatter: (v: number) => fmt2(v) },
|
||||
},
|
||||
dataZoom: [{ type: 'inside', start: 0, end: 100 }],
|
||||
series: seriesData.map((s) => {
|
||||
const valMap = new Map(s.dates.map((d, i) => [d, s.values[i]]))
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import echarts from '../echarts-setup'
|
||||
import { fmt2 } from '../format'
|
||||
import type { EquityPoint } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -28,7 +29,11 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' },
|
||||
valueFormatter: (v: number | string) => fmt2(Number(v)),
|
||||
},
|
||||
legend: { data: ['净值', '回撤%'], top: 0 },
|
||||
grid: { left: '8%', right: '8%', top: 30, bottom: 50 },
|
||||
xAxis: {
|
||||
@@ -44,12 +49,14 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
scale: true,
|
||||
position: 'left',
|
||||
splitLine: { lineStyle: { color: '#2a2e3a' } },
|
||||
axisLabel: { formatter: (v: number) => fmt2(v) },
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '回撤%',
|
||||
position: 'right',
|
||||
splitLine: { show: false },
|
||||
axisLabel: { formatter: (v: number) => fmt2(v) },
|
||||
},
|
||||
],
|
||||
dataZoom: [{ type: 'inside', start: 0, end: 100 }],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import echarts, { DOWN_COLOR, UP_COLOR } from '../echarts-setup'
|
||||
import { fmt2 } from '../format'
|
||||
import type { Bar, Trade } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -68,7 +69,11 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' },
|
||||
valueFormatter: (v: number | string) => fmt2(Number(v)),
|
||||
},
|
||||
legend: { data: ['K线'], top: 0 },
|
||||
grid: { left: '8%', right: '3%', top: 30, bottom: 60 },
|
||||
xAxis: {
|
||||
@@ -81,6 +86,7 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
yAxis: {
|
||||
scale: true,
|
||||
splitLine: { lineStyle: { color: '#2a2e3a' } },
|
||||
axisLabel: { formatter: (v: number) => fmt2(v) },
|
||||
},
|
||||
dataZoom: [
|
||||
{ type: 'inside', start: 60, end: 100 },
|
||||
|
||||
@@ -47,7 +47,7 @@ function buildOption(): echarts.EChartsCoreOption {
|
||||
orient: 'horizontal',
|
||||
left: 'center',
|
||||
bottom: 0,
|
||||
formatter: (v: number) => `${(v * 100).toFixed(0)}%`,
|
||||
formatter: (v: number) => `${(v * 100).toFixed(2)}%`,
|
||||
inRange: { color: ['#18a058', '#2a2e3a', '#ef4146'] }, // 绿(低)→暗→红(高)
|
||||
},
|
||||
series: [
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import echarts from '../echarts-setup'
|
||||
import { fmt2 } from '../format'
|
||||
import type { BacktestResult } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -43,11 +44,19 @@ function buildOption(
|
||||
const allDates = Array.from(new Set(series.flatMap((s) => s.dates))).sort()
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis' },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
valueFormatter: (v: number | string) => fmt2(Number(v)),
|
||||
},
|
||||
legend: { top: 0, data: series.map((s) => s.name) },
|
||||
grid: { left: '8%', right: '5%', top: 30, bottom: 50 },
|
||||
xAxis: { type: 'category', data: allDates, boundaryGap: false },
|
||||
yAxis: { type: 'value', scale: true, name: '归一化净值' },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '归一化净值',
|
||||
axisLabel: { formatter: (v: number) => fmt2(v) },
|
||||
},
|
||||
dataZoom: [{ type: 'inside', start: 0, end: 100 }],
|
||||
series: series.map((s) => {
|
||||
// 按 allDates 对齐(缺失日期 forward-fill)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 共享数字格式化工具。ECharts tooltip/axis 统一用两位小数。
|
||||
|
||||
/** 数字保留两位小数(NaN/Inf 返回 '-')。 */
|
||||
export function fmt2(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined || !Number.isFinite(v)) return '-'
|
||||
return v.toFixed(2)
|
||||
}
|
||||
|
||||
/** 百分比(接受小数如 0.1234 → "12.34%")。 */
|
||||
export function fmtPct(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined || !Number.isFinite(v)) return '-'
|
||||
return `${(v * 100).toFixed(2)}%`
|
||||
}
|
||||
Reference in New Issue
Block a user