mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
Merge pull request #259 from 0112020179/codex/hide-missing-intraday-average
fix(intraday): hide average when amount is unavailable
This commit is contained in:
@@ -151,7 +151,7 @@ class MyProvider:
|
|||||||
|
|
||||||
def get_minute(self, symbols, start_time, end_time, asset_type="stock",
|
def get_minute(self, symbols, start_time, end_time, asset_type="stock",
|
||||||
on_chunk_done=None, freq="1m") -> pl.DataFrame:
|
on_chunk_done=None, freq="1m") -> pl.DataFrame:
|
||||||
"""分钟K: [symbol, datetime(北京墙钟), open, high, low, close, volume, amount]"""
|
"""分钟K: [symbol, datetime(北京墙钟), open, high, low, close, volume, amount(元, 可空)]"""
|
||||||
|
|
||||||
def get_intraday_batch(self, symbols, count=300, asset_type="stock") -> pl.DataFrame:
|
def get_intraday_batch(self, symbols, count=300, asset_type="stock") -> pl.DataFrame:
|
||||||
"""(声明 full_minute 数据集时实现) 全量分钟修复轮: 给定标的当日 1 分钟K,
|
"""(声明 full_minute 数据集时实现) 全量分钟修复轮: 给定标的当日 1 分钟K,
|
||||||
@@ -208,6 +208,9 @@ provider 不应自行切换或回退到其他数据源。
|
|||||||
(09:30–11:30 / 13:00–15:00)映射每根K线,UTC 口径的帧会导致全部点位落在时轴外、
|
(09:30–11:30 / 13:00–15:00)映射每根K线,UTC 口径的帧会导致全部点位落在时轴外、
|
||||||
分时图空白。
|
分时图空白。
|
||||||
|
|
||||||
|
`amount` 单位为元;数据源无法提供可靠的分钟成交额时应返回 `null`,不得伪造。
|
||||||
|
成交额缺失后无法继续计算累计成交均价,前端会停止绘制后续均价线并显示 `—`。
|
||||||
|
|
||||||
入口守卫(`kline_sync._enforce_minute_beijing_wallclock`)对所有分钟源强制归一:
|
入口守卫(`kline_sync._enforce_minute_beijing_wallclock`)对所有分钟源强制归一:
|
||||||
带时区 → 自动换算成北京墙钟;naive 但整体呈 UTC 特征(如 01:30)→ 自动 +8 纠偏并
|
带时区 → 自动换算成北京墙钟;naive 但整体呈 UTC 特征(如 01:30)→ 自动 +8 纠偏并
|
||||||
记日志;完全无法识别的口径 → 拒收并回退 TickFlow。契约仍要求源头写对,守卫只是兜底。
|
记日志;完全无法识别的口径 → 拒收并回退 TickFlow。契约仍要求源头写对,守卫只是兜底。
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function getLimitPrices(prevClose: number, priceLimit?: PriceLimitInfo): {
|
|||||||
return { limitUp, limitDown, upPct, downPct }
|
return { limitUp, limitDown, upPct, downPct }
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOption(data: MinuteKlineRow[], prevClose: number | undefined, avgPrices: number[], lineColor: string, areaColor: string, yMode: YMode, ct: ChartTheme, priceLimit?: PriceLimitInfo, showLimitLines = true, showAvgLine = true, priceLines: Props['priceLines'] = []): EChartsOption {
|
function buildOption(data: MinuteKlineRow[], prevClose: number | undefined, avgPrices: (number | null)[], lineColor: string, areaColor: string, yMode: YMode, ct: ChartTheme, priceLimit?: PriceLimitInfo, showLimitLines = true, showAvgLine = true, priceLines: Props['priceLines'] = []): EChartsOption {
|
||||||
// 将数据映射到全天时间轴上的正确位置
|
// 将数据映射到全天时间轴上的正确位置
|
||||||
const timeIndexMap = new Map(FULL_DAY_TIMES.map((t, i) => [t, i]))
|
const timeIndexMap = new Map(FULL_DAY_TIMES.map((t, i) => [t, i]))
|
||||||
const closes = new Array(FULL_DAY_TIMES.length).fill(null) as (number | null)[]
|
const closes = new Array(FULL_DAY_TIMES.length).fill(null) as (number | null)[]
|
||||||
@@ -601,7 +601,7 @@ export function EChartsIntraday({
|
|||||||
</span>
|
</span>
|
||||||
{showAvgLine && <span className="flex items-center gap-x-1">
|
{showAvgLine && <span className="flex items-center gap-x-1">
|
||||||
<span style={{ display: 'inline-block', width: 14, height: 2, background: THEME.avgLine }} />
|
<span style={{ display: 'inline-block', width: 14, height: 2, background: THEME.avgLine }} />
|
||||||
<span style={{ color: THEME.avgLine }}>{avg?.toFixed(2)}</span>
|
<span style={{ color: THEME.avgLine }}>{avg != null ? avg.toFixed(2) : '—'}</span>
|
||||||
</span>}
|
</span>}
|
||||||
<span className="text-muted">量</span>
|
<span className="text-muted">量</span>
|
||||||
<span className="text-secondary">{d.volume.toFixed(0)}</span>
|
<span className="text-secondary">{d.volume.toFixed(0)}</span>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface Props {
|
|||||||
interface InfoPoint {
|
interface InfoPoint {
|
||||||
date: string
|
date: string
|
||||||
row: MinuteKlineRow
|
row: MinuteKlineRow
|
||||||
average: number
|
average: number | null
|
||||||
prevClose: number | null
|
prevClose: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ function buildModel(sessions: MinuteKlineSession[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const averagePrices = computeIntradayAverage(session.rows)
|
const averagePrices = computeIntradayAverage(session.rows)
|
||||||
const rowsByTime = new Map<string, { row: MinuteKlineRow; average: number }>()
|
const rowsByTime = new Map<string, { row: MinuteKlineRow; average: number | null }>()
|
||||||
session.rows.forEach((row, index) => {
|
session.rows.forEach((row, index) => {
|
||||||
rowsByTime.set(formatMinuteTime(row.datetime), {
|
rowsByTime.set(formatMinuteTime(row.datetime), {
|
||||||
row,
|
row,
|
||||||
@@ -105,7 +105,8 @@ function buildModel(sessions: MinuteKlineSession[]) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
prevRef = row.close
|
prevRef = row.close
|
||||||
priceValues.push(row.low, row.high, average)
|
priceValues.push(row.low, row.high)
|
||||||
|
if (average != null) priceValues.push(average)
|
||||||
pointByIndex.set(index, {
|
pointByIndex.set(index, {
|
||||||
date: session.date,
|
date: session.date,
|
||||||
row,
|
row,
|
||||||
@@ -422,7 +423,7 @@ export function EChartsMultiDayIntraday({
|
|||||||
{changePct != null && (
|
{changePct != null && (
|
||||||
<span style={{ color: infoColor }}>{changePct >= 0 ? '+' : ''}{changePct.toFixed(2)}%</span>
|
<span style={{ color: infoColor }}>{changePct >= 0 ? '+' : ''}{changePct.toFixed(2)}%</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-muted">均价</span><span style={{ color: COLORS.average }}>{info.average.toFixed(2)}</span>
|
<span className="text-muted">均价</span><span style={{ color: COLORS.average }}>{info.average != null ? info.average.toFixed(2) : '—'}</span>
|
||||||
<span className="text-muted">量</span><span className="text-secondary">{info.row.volume.toFixed(0)}</span>
|
<span className="text-muted">量</span><span className="text-secondary">{info.row.volume.toFixed(0)}</span>
|
||||||
<span className="text-muted">额</span><span className="text-secondary">{formatAmount(info.row.amount)}</span>
|
<span className="text-muted">额</span><span className="text-secondary">{formatAmount(info.row.amount)}</span>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export function formatMinuteTime(datetime: string): string {
|
|||||||
return `${match[1]}:${match[2]}`
|
return `${match[1]}:${match[2]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeIntradayAverage(data: MinuteKlineRow[]): number[] {
|
export function computeIntradayAverage(data: MinuteKlineRow[]): (number | null)[] {
|
||||||
const result: number[] = []
|
const result: (number | null)[] = []
|
||||||
let amount = 0
|
let amount = 0
|
||||||
let volume = 0
|
let volume = 0
|
||||||
let hasAmount = true
|
let hasAmount = true
|
||||||
@@ -19,7 +19,7 @@ export function computeIntradayAverage(data: MinuteKlineRow[]): number[] {
|
|||||||
hasAmount = false
|
hasAmount = false
|
||||||
}
|
}
|
||||||
volume += row.volume * 100
|
volume += row.volume * 100
|
||||||
result.push(hasAmount && volume > 0 ? amount / volume : row.close)
|
result.push(hasAmount && volume > 0 ? amount / volume : null)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user