新增大单提醒
This commit is contained in:
parent
4dae5e5d57
commit
1ae0639fc2
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -161,6 +161,10 @@ export interface ContractItem {
|
||||
* 用于分时成交「大单分析」饼图。
|
||||
*/
|
||||
largeOrderLots: number
|
||||
/**
|
||||
* 大单提醒手数:WS 实时成交现手大于该值时弹出提醒;0 表示关闭。
|
||||
*/
|
||||
largeOrderAlertLots: number
|
||||
/**
|
||||
* 合约专属关键字(持仓/止损/看点等)。
|
||||
* AI 分析时追加在全局「额外关键字」之后。
|
||||
|
||||
23
src/utils/largeOrderAlert.ts
Normal file
23
src/utils/largeOrderAlert.ts
Normal 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',
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user