新增大单提醒

This commit is contained in:
dongzp 2026-07-24 10:10:13 +08:00
parent 4dae5e5d57
commit 1ae0639fc2
7 changed files with 72 additions and 4 deletions

View File

@ -76,8 +76,9 @@ function upsertIntraday(
/**
* Merge tick detailinfos into quote.trades (newest first, dedupe, cap 200).
* Mutates quote in place so Vue only invalidates trade-dependent effects.
* @returns newly inserted ticks (not already in quote.trades)
*/
export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): void {
export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): TradeTick[] {
const incoming: TradeTick[] = [...(data.detailinfos ?? [])]
.reverse()
.map((t) => ({
@ -87,6 +88,12 @@ export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): void {
side: t.bsFlag === 'B' ? 'B' : 'S',
}))
const existingKeys = new Set(quote.trades.map(tradeKey))
const added: TradeTick[] = []
for (const t of incoming) {
if (!existingKeys.has(tradeKey(t))) added.push(t)
}
const seen = new Set<string>()
const merged: TradeTick[] = []
for (const t of [...incoming, ...quote.trades]) {
@ -97,6 +104,7 @@ export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): void {
if (merged.length >= MAX_TRADES) break
}
quote.trades = merged
return added
}
/**

View File

@ -34,7 +34,7 @@
:value="c.id"
/>
</el-select>
<el-button title="合约设置" @click="contractSettingsVisible = true">合约</el-button>
<el-button title="自选设置" @click="contractSettingsVisible = true">自选</el-button>
</div>
<SettingsDialog v-model="settingsVisible" />
<ContractSettingsDialog v-model="contractSettingsVisible" />

View File

@ -2,7 +2,7 @@
<el-dialog
:model-value="modelValue"
title="合约设置"
width="760px"
width="860px"
destroy-on-close
@update:model-value="emit('update:modelValue', $event)"
>
@ -25,6 +25,11 @@
<el-table-column prop="variety" label="品种" width="70" />
<el-table-column prop="exchange" label="交易所" width="90" />
<el-table-column prop="largeOrderLots" label="大单阈值" width="88" />
<el-table-column label="大单提醒" width="88">
<template #default="{ row }">
{{ row.largeOrderAlertLots > 0 ? row.largeOrderAlertLots : '关' }}
</template>
</el-table-column>
<el-table-column prop="keywords" label="关键字" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
@ -74,6 +79,16 @@
controls-position="right"
/>
</el-form-item>
<el-form-item label="大单提醒手数">
<el-input-number
v-model="form.largeOrderAlertLots"
:min="0"
:max="999999"
:step="10"
controls-position="right"
/>
<div class="form-tip">现手大于该值时弹窗提醒;填 0 关闭</div>
</el-form-item>
<el-form-item label="合约关键字">
<el-input
v-model="form.keywords"
@ -119,6 +134,7 @@ const form = reactive({
variety: '',
exchange: '',
largeOrderLots: DEFAULT_CONTRACT.largeOrderLots,
largeOrderAlertLots: DEFAULT_CONTRACT.largeOrderAlertLots,
keywords: '',
})
@ -128,6 +144,8 @@ function resetForm(seed?: Partial<ContractItem>) {
form.variety = seed?.variety || ''
form.exchange = seed?.exchange || ''
form.largeOrderLots = seed?.largeOrderLots || DEFAULT_CONTRACT.largeOrderLots
form.largeOrderAlertLots =
seed?.largeOrderAlertLots ?? DEFAULT_CONTRACT.largeOrderAlertLots
form.keywords = seed?.keywords || ''
}
@ -136,6 +154,7 @@ function startAdd() {
resetForm({
exchange: DEFAULT_CONTRACT.exchange,
largeOrderLots: DEFAULT_CONTRACT.largeOrderLots,
largeOrderAlertLots: DEFAULT_CONTRACT.largeOrderAlertLots,
})
formVisible.value = true
}
@ -188,6 +207,7 @@ function saveForm() {
variety: form.variety.trim(),
exchange: form.exchange.trim(),
largeOrderLots: form.largeOrderLots,
largeOrderAlertLots: form.largeOrderAlertLots,
keywords: form.keywords.trim(),
}
if (!payload.code) {
@ -233,4 +253,10 @@ function saveForm() {
.toolbar {
margin-bottom: 12px;
}
.form-tip {
margin-top: 4px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>

View File

@ -12,6 +12,7 @@ export const DEFAULT_CONTRACT: Omit<ContractItem, 'id'> = {
name: '玻璃2609',
exchange: '郑商所',
largeOrderLots: 100,
largeOrderAlertLots: 0,
keywords: '',
}
@ -28,6 +29,9 @@ function normalizeItem(raw: Partial<ContractItem> & { code?: string }): Contract
const lots = Number(raw.largeOrderLots)
const largeOrderLots =
Number.isFinite(lots) && lots > 0 ? Math.floor(lots) : DEFAULT_CONTRACT.largeOrderLots
const alertLots = Number(raw.largeOrderAlertLots)
const largeOrderAlertLots =
Number.isFinite(alertLots) && alertLots > 0 ? Math.floor(alertLots) : 0
const keywords = String(raw.keywords ?? '').trim()
return {
id: String(raw.id || createId()),
@ -36,6 +40,7 @@ function normalizeItem(raw: Partial<ContractItem> & { code?: string }): Contract
name,
exchange,
largeOrderLots,
largeOrderAlertLots,
keywords,
}
}

View File

@ -23,6 +23,7 @@ import {
getPreviousTradingDate,
isWsTradingSession,
} from '../utils/tradingDate'
import { notifyLargeOrders } from '../utils/largeOrderAlert'
import type { WsConnectionStatus } from '../api/baidu/ws'
/** K 线周期(日/周/月) */
@ -139,7 +140,8 @@ export const useQuotaStore = defineStore('quota', () => {
const product = msg.data.product
// Mutate in place — replacing quote.value forces every chart/UI to rebuild.
if (product === 'tick') {
applyWsTick(quote.value, msg.data as BaiduWsTickData)
const added = applyWsTick(quote.value, msg.data as BaiduWsTickData)
notifyLargeOrders(added, activeContract())
} else if (product === 'snapshot') {
applyWsSnapshot(quote.value, msg.data as BaiduWsSnapshotData)
}

View File

@ -161,6 +161,10 @@ export interface ContractItem {
* 用于分时成交「大单分析」饼图。
*/
largeOrderLots: number
/**
* 大单提醒手数:WS 实时成交现手大于该值时弹出提醒;0 表示关闭。
*/
largeOrderAlertLots: number
/**
* 合约专属关键字(持仓/止损/看点等)。
* AI 分析时追加在全局「额外关键字」之后。

View File

@ -0,0 +1,23 @@
import { ElNotification } from 'element-plus'
import type { ContractItem, TradeTick } from '../types'
/**
* 对 WS 新成交中超过「大单提醒」阈值的笔弹出 Notification。
* largeOrderAlertLots 为 0 时关闭提醒。
*/
export function notifyLargeOrders(trades: TradeTick[], contract: ContractItem): void {
const threshold = contract.largeOrderAlertLots
if (!threshold || threshold <= 0 || trades.length === 0) return
for (const t of trades) {
if (t.volume <= threshold) continue
const sideLabel = t.side === 'B' ? '买' : '卖'
ElNotification({
title: `大单提醒 · ${contract.name || contract.code}`,
message: `${t.time} ${sideLabel} ${t.volume}手 @ ${t.price.toFixed(2)}`,
type: t.side === 'B' ? 'error' : 'success',
duration: 4500,
position: 'top-right',
})
}
}