优化AI解读提示词
This commit is contained in:
parent
cf9b9c5d74
commit
3e2e2ffc90
@ -1,4 +1,16 @@
|
||||
import type { AnalysisSnapshot } from '../../stores/quota'
|
||||
import type {
|
||||
ChangeRiskItem,
|
||||
MainForceTrendItem,
|
||||
NewsItem,
|
||||
PositionAnomalyItem,
|
||||
PositionRow,
|
||||
PositionsData,
|
||||
ProfitRow,
|
||||
QuoteData,
|
||||
TradeTick,
|
||||
} from '../../types'
|
||||
import { toAnomalySections } from '../jqka/mapAnomaly'
|
||||
import type { AiAnalysisPayload } from './types'
|
||||
|
||||
const OUTPUT_SCHEMA = `{
|
||||
@ -9,9 +21,18 @@ const OUTPUT_SCHEMA = `{
|
||||
"reasons": ["理由1", "理由2", "理由3", "理由4"]
|
||||
}`
|
||||
|
||||
const POSITION_TOP_N = 10
|
||||
const NEWS_TOP_N = 20
|
||||
const INTRADAY_TAIL = 120
|
||||
const CANDLE_DAY_TAIL = 60
|
||||
/** 主力全市场列表:当前品种优先后截断 */
|
||||
const TRACE_RISK_LIMIT = 12
|
||||
const TRACE_TREND_LIMIT = 16
|
||||
const ANOMALY_PER_SECTION = 5
|
||||
|
||||
/**
|
||||
* 将行情 / 新闻 / 持仓格式化规则数据拼成 DeepSeek messages。
|
||||
* keywords 作为 system 角色设定;snapshot 作为 user 侧事实数据。
|
||||
* keywords 作为 system 角色设定;其余按 UI 关键字文案提交,分时/K 线保留 JSON。
|
||||
*/
|
||||
export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{
|
||||
role: 'system' | 'user'
|
||||
@ -19,44 +40,372 @@ export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{
|
||||
}> {
|
||||
const system = [
|
||||
payload.keywords.trim() || '你是一名专业的国内期货交易员和分析员。',
|
||||
'请仅基于用户提供的行情、盘口、新闻、机构持仓、主力追踪(变盘预警、主力趋势机会)与主力异动数据给出交易建议。',
|
||||
'主力追踪与主力异动为全市场数据,请优先关注与当前合约品种相关的条目,并结合整体主力动向综合判断。',
|
||||
'不要编造未提供的数据;信息不足时偏向观望并说明原因。',
|
||||
'请仅基于用户提供的行情、盘口、大单分析、新闻、机构持仓、主力追踪(变盘预警、主力趋势机会)与主力异动数据给出交易建议。',
|
||||
'主力追踪与主力异动为全市场数据,请优先关注标注为「当前品种」的条目,并结合整体主力动向综合判断。',
|
||||
'文案中的单位与页面一致(手、万手、万、亿、百分比);不要编造未提供的数据;信息不足时偏向观望并说明原因。',
|
||||
'必须只输出一个 JSON 对象,不要 Markdown 代码块,不要其它说明文字。',
|
||||
`JSON 格式:${OUTPUT_SCHEMA}`,
|
||||
].join('\n')
|
||||
|
||||
const user = [
|
||||
'以下为当前合约的格式化分析数据(JSON),请据此给出建议:',
|
||||
JSON.stringify(slimSnapshot(payload.snapshot), null, 2),
|
||||
].join('\n\n')
|
||||
|
||||
return [
|
||||
{ role: 'system', content: system },
|
||||
{ role: 'user', content: user },
|
||||
{ role: 'user', content: buildAnalysisUserContent(payload) },
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩超长数组,保留分析关键字段,避免 prompt 过大。
|
||||
* 盘口、统计、持仓、新闻、主力追踪、主力异动完整保留;分时/成交/K 线做尾部截断。
|
||||
*/
|
||||
function slimSnapshot(snapshot: AnalysisSnapshot): AnalysisSnapshot {
|
||||
const quote = snapshot.quote
|
||||
if (!quote) return snapshot
|
||||
/** 供调试:与提交给模型的 user 内容一致 */
|
||||
export function buildAnalysisUserContent(payload: AiAnalysisPayload): string {
|
||||
const { snapshot, largeOrderLots, variety } = payload
|
||||
const sections = [
|
||||
formatContract(snapshot),
|
||||
formatQuoteStats(snapshot.quote),
|
||||
formatOrderBook(snapshot.quote),
|
||||
formatLargeOrder(snapshot.quote?.trades ?? [], largeOrderLots),
|
||||
formatIntradayJson(snapshot.quote),
|
||||
formatCandlesJson(snapshot.quote),
|
||||
formatPositions(snapshot.positions),
|
||||
formatMainForceTrace(snapshot, variety),
|
||||
formatPositionAnomaly(snapshot, variety),
|
||||
formatNews(snapshot.news),
|
||||
].filter(Boolean)
|
||||
|
||||
return {
|
||||
...snapshot,
|
||||
quote: {
|
||||
...quote,
|
||||
intraday: quote.intraday.slice(-120),
|
||||
trades: quote.trades.slice(-80),
|
||||
candles: {
|
||||
day: quote.candles.day.slice(-60),
|
||||
week: quote.candles.week.slice(-40),
|
||||
month: quote.candles.month.slice(-36),
|
||||
},
|
||||
},
|
||||
news: snapshot.news.slice(0, 30),
|
||||
}
|
||||
return [
|
||||
`抓取时间:${snapshot.fetchedAt || '—'}`,
|
||||
'以下为当前合约的分析数据(与页面展示一致的关键字文案;分时/K 线为 JSON),请据此给出建议:',
|
||||
...sections,
|
||||
].join('\n\n')
|
||||
}
|
||||
|
||||
function formatContract(snapshot: AnalysisSnapshot): string {
|
||||
const c = snapshot.contract
|
||||
const q = snapshot.quote
|
||||
const lines = [
|
||||
'【合约】',
|
||||
`名称:${q?.name || c.name || '—'}`,
|
||||
`代码:${q?.code || c.code || '—'}`,
|
||||
`交易所:${q?.exchange || c.exchange || '—'}`,
|
||||
]
|
||||
if (q) {
|
||||
lines.push(
|
||||
`最新价:${fmtPrice(q.last)}`,
|
||||
`涨跌:${fmtSigned(q.change)}(${fmtSigned(q.changePercent)}%)`,
|
||||
`状态:${q.status || '—'}`,
|
||||
`更新:${q.updatedAt || '—'}`,
|
||||
)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatQuoteStats(quote: QuoteData | null): string {
|
||||
if (!quote) return '【行情统计】\n暂无数据'
|
||||
return [
|
||||
'【行情统计】',
|
||||
`开盘:${fmtPrice(quote.open)}`,
|
||||
`最高:${fmtPrice(quote.high)}`,
|
||||
`最低:${fmtPrice(quote.low)}`,
|
||||
`昨收:${fmtPrice(quote.prevClose)}`,
|
||||
`结算:${fmtPrice(quote.settlement)}`,
|
||||
`昨结:${fmtPrice(quote.prevSettlement)}`,
|
||||
`均价:${fmtPrice(quote.avg)}`,
|
||||
`涨跌:${fmtSigned(quote.change)}`,
|
||||
`持仓量:${fmtWan(quote.openInterest)}万`,
|
||||
`成交量:${fmtWan(quote.volume)}万手`,
|
||||
`成交额:${quote.amount}亿`,
|
||||
`振幅:${quote.amplitude.toFixed(2)}%`,
|
||||
`外盘:${fmtWan(quote.outerVol)}万`,
|
||||
`内盘:${fmtWan(quote.innerVol)}万`,
|
||||
`日增:${formatWanSigned(quote.amountDelta)}`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function formatOrderBook(quote: QuoteData | null): string {
|
||||
if (!quote) return '【盘口】\n暂无数据'
|
||||
const lines = [
|
||||
'【盘口】',
|
||||
`买盘占比:${quote.buyRatio}%`,
|
||||
`卖盘占比:${quote.sellRatio}%`,
|
||||
]
|
||||
for (const ask of quote.orderBook.asks) {
|
||||
lines.push(`卖${ask.level}:价格 ${fmtPrice(ask.price)},量 ${ask.volume}`)
|
||||
}
|
||||
for (const bid of quote.orderBook.bids) {
|
||||
lines.push(`买${bid.level}:价格 ${fmtPrice(bid.price)},量 ${bid.volume}`)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatLargeOrder(trades: TradeTick[], lots: number): string {
|
||||
const threshold = Math.max(0, Number(lots) || 0)
|
||||
let largeBuy = 0
|
||||
let largeSell = 0
|
||||
let retailBuy = 0
|
||||
let retailSell = 0
|
||||
|
||||
for (const t of trades) {
|
||||
const isLarge = t.volume > threshold
|
||||
if (t.side === 'B') {
|
||||
if (isLarge) largeBuy += t.volume
|
||||
else retailBuy += t.volume
|
||||
} else if (isLarge) largeSell += t.volume
|
||||
else retailSell += t.volume
|
||||
}
|
||||
|
||||
const total = largeBuy + largeSell + retailBuy + retailSell
|
||||
const pct = (v: number) => (total > 0 ? ((v / total) * 100).toFixed(0) : '0')
|
||||
|
||||
return [
|
||||
'【大单分析】(分时成交汇总,非逐笔)',
|
||||
`大单手数阈值:现手大于 ${threshold} 手计为大单`,
|
||||
`大单主买:${largeBuy}手(${pct(largeBuy)}%)`,
|
||||
`散单主买:${retailBuy}手(${pct(retailBuy)}%)`,
|
||||
`大单主卖:${largeSell}手(${pct(largeSell)}%)`,
|
||||
`散单主卖:${retailSell}手(${pct(retailSell)}%)`,
|
||||
`合计:${total}手`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function formatIntradayJson(quote: QuoteData | null): string {
|
||||
if (!quote) return '【分时图 JSON】\n[]'
|
||||
const points = quote.intraday.slice(-INTRADAY_TAIL)
|
||||
return ['【分时图 JSON】', JSON.stringify(points)].join('\n')
|
||||
}
|
||||
|
||||
function formatCandlesJson(quote: QuoteData | null): string {
|
||||
const day = quote?.candles.day.slice(-CANDLE_DAY_TAIL) ?? []
|
||||
return ['【日K线 JSON】', JSON.stringify(day)].join('\n')
|
||||
}
|
||||
|
||||
function formatPositions(data: PositionsData | null): string {
|
||||
if (!data) return '【机构持仓】\n暂无数据'
|
||||
const lines = [
|
||||
'【机构持仓】',
|
||||
`数据日期:${data.updatedAt || '—'}`,
|
||||
'',
|
||||
`成交量前${POSITION_TOP_N}名:`,
|
||||
...formatPositionRows(data.volume, '成交量'),
|
||||
'',
|
||||
`净多持仓前${POSITION_TOP_N}名:`,
|
||||
...formatPositionRows(data.netLong, '净持仓'),
|
||||
'',
|
||||
`净空持仓前${POSITION_TOP_N}名:`,
|
||||
...formatPositionRows(data.netShort, '净持仓'),
|
||||
'',
|
||||
`盈利机构前${POSITION_TOP_N}名:`,
|
||||
...formatProfitRows(data.profitGain),
|
||||
'',
|
||||
`亏损机构前${POSITION_TOP_N}名:`,
|
||||
...formatProfitRows(data.profitLoss),
|
||||
]
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatPositionRows(list: PositionRow[], qtyLabel: string): string[] {
|
||||
const top = list.slice(0, POSITION_TOP_N)
|
||||
if (!top.length) return ['(无)']
|
||||
return top.map(
|
||||
(row) =>
|
||||
`${row.rank}. ${row.name}|${qtyLabel} ${row.qty}|增减 ${fmtSignedInt(row.change)}`,
|
||||
)
|
||||
}
|
||||
|
||||
function formatProfitRows(list: ProfitRow[]): string[] {
|
||||
const top = list.slice(0, POSITION_TOP_N)
|
||||
if (!top.length) return ['(无)']
|
||||
return top.map(
|
||||
(row) =>
|
||||
`${row.rank}. ${row.name}|盈利金额 ${formatProfit(row.profit)}|净持仓 ${row.netQty}|增减 ${fmtSignedInt(row.change)}`,
|
||||
)
|
||||
}
|
||||
|
||||
function formatMainForceTrace(snapshot: AnalysisSnapshot, variety: string): string {
|
||||
const data = snapshot.mainForceTrace
|
||||
if (!data) return '【主力追踪】\n暂无数据'
|
||||
|
||||
const riskList = takeVarietyFirst(data.changeRiskList, variety, TRACE_RISK_LIMIT)
|
||||
const trendList = takeVarietyFirst(data.mainForceTrendList, variety, TRACE_TREND_LIMIT)
|
||||
|
||||
const lines = [
|
||||
'【主力追踪】',
|
||||
`数据日期:${data.date || '—'}`,
|
||||
'',
|
||||
'变盘预警:',
|
||||
]
|
||||
|
||||
if (!riskList.length) lines.push('(无)')
|
||||
else {
|
||||
for (const row of riskList) {
|
||||
lines.push(formatChangeRiskLine(row, variety))
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('', '主力趋势机会:')
|
||||
if (!trendList.length) lines.push('(无)')
|
||||
else {
|
||||
for (const row of trendList) {
|
||||
lines.push(formatTrendLine(row, variety))
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatChangeRiskLine(row: ChangeRiskItem, variety: string): string {
|
||||
const tag = isCurrentVariety(row.variety, variety) ? '【当前品种】' : ''
|
||||
return [
|
||||
`${tag}${row.varietyName}(${row.variety})`,
|
||||
`${row.actionLabel}${row.actionLots}手`,
|
||||
`价格 ${formatTracePrice(row.linkClosePrice)}`,
|
||||
`涨跌幅 ${formatSignedPct(row.percent)}`,
|
||||
`变盘风险值 ${formatPct(row.changeRisk)}`,
|
||||
].join('|')
|
||||
}
|
||||
|
||||
function formatTrendLine(row: MainForceTrendItem, variety: string): string {
|
||||
const tag = isCurrentVariety(row.variety, variety) ? '【当前品种】' : ''
|
||||
const side = row.side === 'long' ? '多' : '空'
|
||||
return [
|
||||
`${tag}${row.varietyName}(${row.variety})`,
|
||||
`机构 ${row.company}`,
|
||||
`涨跌幅 ${formatSignedPct(row.percent)}`,
|
||||
`单日${formatPct(row.mood)}流${side}`,
|
||||
`单日加${side}${Math.abs(row.netChange)}手`,
|
||||
`流${side}${formatFlowAbs(row.pupilFlow)}`,
|
||||
`近一周胜率 ${formatPct(row.weekRate)}`,
|
||||
`近一年盈利 ${formatProfitAbs(row.yearProfit)}`,
|
||||
].join('|')
|
||||
}
|
||||
|
||||
function formatPositionAnomaly(snapshot: AnalysisSnapshot, variety: string): string {
|
||||
const data = snapshot.positionAnomaly
|
||||
if (!data) return '【主力异动】\n暂无数据'
|
||||
|
||||
const lines = ['【主力异动】', `数据日期:${data.date || '—'}`]
|
||||
for (const section of toAnomalySections(data)) {
|
||||
lines.push('', `${section.title}:`)
|
||||
const items = takeVarietyFirst(section.items, variety, ANOMALY_PER_SECTION)
|
||||
if (!items.length) {
|
||||
lines.push('(无)')
|
||||
continue
|
||||
}
|
||||
items.forEach((row, idx) => {
|
||||
lines.push(formatAnomalyLine(row, variety, idx + 1))
|
||||
})
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatAnomalyLine(
|
||||
row: PositionAnomalyItem,
|
||||
variety: string,
|
||||
rank: number,
|
||||
): string {
|
||||
const tag = isCurrentVariety(row.variety, variety) ? '【当前品种】' : ''
|
||||
return [
|
||||
`${rank}. ${tag}${row.varietyName}(${row.variety})`,
|
||||
`主导 ${row.maxCompanyName}`,
|
||||
`增减 ${fmtSignedInt(row.netPositionChange)}`,
|
||||
`增幅 ${fmtSigned(row.netPositionChangeRate)}%`,
|
||||
`净持仓 ${fmtSignedInt(row.netPosition)}`,
|
||||
].join('|')
|
||||
}
|
||||
|
||||
function formatNews(news: NewsItem[]): string {
|
||||
const top = news.slice(0, NEWS_TOP_N)
|
||||
if (!top.length) return '【新闻】\n暂无数据'
|
||||
const lines = ['【新闻】']
|
||||
for (const item of top) {
|
||||
const summary = item.summary?.trim()
|
||||
lines.push(
|
||||
summary
|
||||
? `[${item.source} ${item.time}] ${item.title}|${summary}`
|
||||
: `[${item.source} ${item.time}] ${item.title}`,
|
||||
)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function takeVarietyFirst<T extends { variety: string }>(
|
||||
list: T[],
|
||||
variety: string,
|
||||
limit: number,
|
||||
): T[] {
|
||||
const key = variety.trim().toUpperCase()
|
||||
if (!list.length) return []
|
||||
if (!key) return list.slice(0, limit)
|
||||
|
||||
const matched: T[] = []
|
||||
const rest: T[] = []
|
||||
for (const item of list) {
|
||||
if (item.variety.toUpperCase() === key) matched.push(item)
|
||||
else rest.push(item)
|
||||
}
|
||||
const merged = [...matched, ...rest]
|
||||
return merged.slice(0, Math.max(limit, matched.length))
|
||||
}
|
||||
|
||||
function isCurrentVariety(itemVariety: string, current: string): boolean {
|
||||
const key = current.trim().toUpperCase()
|
||||
return Boolean(key) && itemVariety.toUpperCase() === key
|
||||
}
|
||||
|
||||
function fmtPrice(n: number): string {
|
||||
return Number.isFinite(n) ? n.toFixed(2) : '—'
|
||||
}
|
||||
|
||||
function fmtSigned(n: number): string {
|
||||
if (!Number.isFinite(n)) return '—'
|
||||
return `${n > 0 ? '+' : ''}${n}`
|
||||
}
|
||||
|
||||
function fmtSignedInt(n: number): string {
|
||||
if (!Number.isFinite(n)) return '—'
|
||||
return `${n > 0 ? '+' : ''}${n}`
|
||||
}
|
||||
|
||||
function fmtWan(hands: number): string {
|
||||
return (hands / 10000).toFixed(2)
|
||||
}
|
||||
|
||||
function formatWanSigned(hands: number): string {
|
||||
const wan = hands / 10000
|
||||
const sign = wan > 0 ? '+' : ''
|
||||
return `${sign}${wan.toFixed(2)}万手`
|
||||
}
|
||||
|
||||
function formatProfit(n: number): string {
|
||||
const sign = n > 0 ? '+' : n < 0 ? '-' : ''
|
||||
const abs = Math.abs(n)
|
||||
if (abs >= 1e8) return `${sign}${(abs / 1e8).toFixed(2)}亿`
|
||||
if (abs >= 1e4) return `${sign}${(abs / 1e4).toFixed(2)}万`
|
||||
return `${sign}${abs.toFixed(0)}`
|
||||
}
|
||||
|
||||
function formatPct(n: number): string {
|
||||
return `${(n * 100).toFixed(1)}%`
|
||||
}
|
||||
|
||||
function formatSignedPct(n: number): string {
|
||||
const pct = n * 100
|
||||
if (Math.abs(pct) < 0.005) return '0.00%'
|
||||
const sign = pct > 0 ? '+' : ''
|
||||
return `${sign}${pct.toFixed(2)}%`
|
||||
}
|
||||
|
||||
function formatTracePrice(n: number): string {
|
||||
if (!Number.isFinite(n) || n === 0) return '—'
|
||||
return n >= 100 ? n.toFixed(0) : n.toFixed(2)
|
||||
}
|
||||
|
||||
function formatFlowAbs(n: number): string {
|
||||
const abs = Math.abs(n)
|
||||
if (abs >= 1e8) return `${(abs / 1e8).toFixed(1)}亿`
|
||||
if (abs >= 1e4) return `${(abs / 1e4).toFixed(0)}万`
|
||||
return abs.toFixed(0)
|
||||
}
|
||||
|
||||
function formatProfitAbs(n: number): string {
|
||||
const abs = Math.abs(n)
|
||||
if (abs >= 1e8) return `${(abs / 1e8).toFixed(2)}亿元`
|
||||
if (abs >= 1e4) return `${(abs / 1e4).toFixed(2)}万元`
|
||||
return `${abs.toFixed(0)}元`
|
||||
}
|
||||
|
||||
@ -6,6 +6,10 @@ export interface AiAnalysisPayload {
|
||||
keywords: string
|
||||
/** 行情 + 新闻 + 机构持仓 + 主力追踪 + 主力异动等接口快照 */
|
||||
snapshot: AnalysisSnapshot
|
||||
/** 大单手数阈值,与 UI 大单分析一致 */
|
||||
largeOrderLots: number
|
||||
/** 品种代码(如 FG),主力数据优先标注当前品种 */
|
||||
variety: string
|
||||
}
|
||||
|
||||
/** DeepSeek Chat Completions(OpenAI 兼容)请求体 */
|
||||
|
||||
@ -2,6 +2,7 @@ import { onUnmounted, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { AiAdvice } from '../types'
|
||||
import { fetchDeepSeekAdvice } from '../api/deepseek/analyze'
|
||||
import { buildAnalysisUserContent } from '../api/deepseek/prompt'
|
||||
import type { AiAnalysisPayload } from '../api/deepseek/types'
|
||||
import { getSharedData, putSharedData } from '../api/uuquant/sharedData'
|
||||
import { adviceSharedKey } from '../api/uuquant/keys'
|
||||
@ -9,7 +10,11 @@ import type { AdvicePayload } from '../api/uuquant/models'
|
||||
import { useQuotaStore, type KlinePeriod } from '../stores/quota'
|
||||
import { useContractStore } from '../stores/contract'
|
||||
import { useSettingsStore } from '../stores/settings'
|
||||
import { isTradingDay, isWsTradingSession } from '../utils/tradingDate'
|
||||
import {
|
||||
extractVariety,
|
||||
isTradingDay,
|
||||
isWsTradingSession,
|
||||
} from '../utils/tradingDate'
|
||||
import { useTitleBlink } from './useTitleBlink'
|
||||
|
||||
/** 全局额外关键字 + 当前合约关键字(合约段追加在后) */
|
||||
@ -138,26 +143,22 @@ export function useAiAdvice() {
|
||||
/**
|
||||
* 一键获取建议:收集行情 / 新闻 / 持仓后提交 DeepSeek,返回真实 AI 建议。
|
||||
* @param opts.silent 定时任务时静默:无 Key / 周末 / 非开盘 / 进行中则跳过,成功不弹 toast
|
||||
* 手动点击会先组装并 console.log 分析关键字(不受周末/开盘限制),再决定是否调用 API。
|
||||
*/
|
||||
async function refresh(opts?: { silent?: boolean }) {
|
||||
const silent = Boolean(opts?.silent)
|
||||
// 周末不分析(定时静默跳过;手动点击提示)
|
||||
if (!isTradingDay()) {
|
||||
if (!silent) ElMessage.warning('周末休市,暂不进行 AI 分析')
|
||||
return
|
||||
}
|
||||
// 定时分析仅在开盘时段执行(与行情 WS 时段一致)
|
||||
if (silent && !isWsTradingSession()) return
|
||||
|
||||
const apiKey = settings.apiKey.trim()
|
||||
if (!apiKey) {
|
||||
if (!silent) ElMessage.warning('请先在设置中配置 DeepSeek API Key')
|
||||
return
|
||||
// 定时:周末 / 非开盘 / 无 Key 直接跳过
|
||||
if (silent) {
|
||||
if (!isTradingDay()) return
|
||||
if (!isWsTradingSession()) return
|
||||
if (!settings.apiKey.trim()) return
|
||||
}
|
||||
if (loading.value) return
|
||||
|
||||
const epoch = analysisEpoch
|
||||
const contractCode = contractStore.current?.code || ''
|
||||
const contract = contractStore.current
|
||||
const contractCode = contract?.code || ''
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
@ -167,13 +168,30 @@ export function useAiAdvice() {
|
||||
if (epoch !== analysisEpoch) return
|
||||
|
||||
const payload: AiAnalysisPayload = {
|
||||
keywords: mergeKeywords(settings.keywords, contractStore.current.keywords),
|
||||
keywords: mergeKeywords(settings.keywords, contract?.keywords || ''),
|
||||
snapshot: quota.getAnalysisSnapshot(),
|
||||
largeOrderLots: contract?.largeOrderLots ?? 0,
|
||||
variety:
|
||||
(contract?.variety || extractVariety(contractCode)).toUpperCase(),
|
||||
}
|
||||
|
||||
// 勿 console.log 完整 snapshot:DevTools 会长期持有大对象导致内存暴涨
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('[AI] 分析', payload.snapshot.contract.code, payload.keywords.slice(0, 80))
|
||||
// 手动点击:始终打印关键字(周末/休市也可核对 prompt)
|
||||
if (!silent) {
|
||||
const userContent = buildAnalysisUserContent(payload)
|
||||
console.log('[AI] 角色关键字', payload.keywords)
|
||||
console.log('[AI] 分析数据关键字', userContent)
|
||||
}
|
||||
|
||||
// 周末:手动可看关键字,但不调 API
|
||||
if (!isTradingDay()) {
|
||||
if (!silent) ElMessage.warning('周末休市,暂不进行 AI 分析(已输出分析关键字到控制台)')
|
||||
return
|
||||
}
|
||||
|
||||
const apiKey = settings.apiKey.trim()
|
||||
if (!apiKey) {
|
||||
if (!silent) ElMessage.warning('请先在设置中配置 DeepSeek API Key')
|
||||
return
|
||||
}
|
||||
|
||||
const advice = await fetchDeepSeekAdvice(apiKey, payload)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user