From d88fc616f323a33f295d53a218b6bd5fd45f33a3 Mon Sep 17 00:00:00 2001 From: dongzp <975303544@qq.com> Date: Sat, 25 Jul 2026 12:56:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=BA=E6=9E=84=E4=B8=BB?= =?UTF-8?q?=E5=8A=9B=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/deepseek/prompt.ts | 39 +++++- src/api/deepseek/types.ts | 2 +- src/api/jqka/mapMainForceAnalysis.ts | 91 ++++++++++++++ src/api/jqka/mapPosition.ts | 8 ++ src/api/jqka/position.ts | 43 +++++++ src/api/jqka/types.ts | 64 ++++++++++ .../position/MainForceAnalysisTable.vue | 118 ++++++++++++++++++ src/components/position/PositionPanel.vue | 15 ++- src/stores/quota.ts | 44 +++++-- src/types/index.ts | 46 +++++++ 10 files changed, 455 insertions(+), 15 deletions(-) create mode 100644 src/api/jqka/mapMainForceAnalysis.ts create mode 100644 src/components/position/MainForceAnalysisTable.vue diff --git a/src/api/deepseek/prompt.ts b/src/api/deepseek/prompt.ts index d654f66..4f97037 100644 --- a/src/api/deepseek/prompt.ts +++ b/src/api/deepseek/prompt.ts @@ -1,6 +1,8 @@ import type { AnalysisSnapshot } from '../../stores/quota' import type { ChangeRiskItem, + MainForceAnalysisData, + MainForceAnalysisItem, MainForceTrendItem, NewsItem, PositionAnomalyItem, @@ -40,8 +42,8 @@ export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{ }> { const system = [ payload.keywords.trim() || '你是一名专业的国内期货交易员和分析员。', - '请仅基于用户提供的行情、盘口、大单分析、新闻、机构持仓、主力追踪(变盘预警、主力趋势机会)与主力异动数据给出交易建议。', - '主力追踪与主力异动为全市场数据,请优先关注标注为「当前品种」的条目,并结合整体主力动向综合判断。', + '请仅基于用户提供的行情、盘口、大单分析、新闻、机构持仓、主力分析(盈利席位榜)、主力追踪(变盘预警、主力趋势机会)与主力异动数据给出交易建议。', + '主力分析为当前品种盈利席位榜;主力追踪与主力异动为全市场数据,请优先关注标注为「当前品种」的条目,并结合整体主力动向综合判断。', '文案中的单位与页面一致(手、万手、万、亿、百分比);不要编造未提供的数据;信息不足时偏向观望并说明原因。', '必须只输出一个 JSON 对象,不要 Markdown 代码块,不要其它说明文字。', `JSON 格式:${OUTPUT_SCHEMA}`, @@ -195,10 +197,43 @@ function formatPositions(data: PositionsData | null): string { '', `亏损机构前${POSITION_TOP_N}名:`, ...formatProfitRows(data.profitLoss), + '', + '主力分析(盈利席位榜前20名):', + ...formatMainForceAnalysis(data.mainForceAnalysis), ] return lines.join('\n') } +function formatMainForceAnalysis(data: MainForceAnalysisData | null | undefined): string[] { + if (!data?.list?.length) return ['(无)'] + const lines = [ + `品种净持仓 ${fmtSignedInt(data.varietyNetQty)}|净增减 ${fmtSignedInt(data.varietyNetChange)}|日内情绪 ${formatPct(data.varietyDayMood)}|变盘风险 ${formatPct(data.varietyChangeRisk)}`, + ] + for (const row of data.list.slice(0, 20)) { + lines.push(formatMainForceAnalysisRow(row)) + } + return lines +} + +function formatMainForceAnalysisRow(row: MainForceAnalysisItem): string { + const action = + row.netChange === 0 + ? '持仓不变' + : row.netChange > 0 + ? `加多${Math.abs(row.netChange)}手` + : `加空${Math.abs(row.netChange)}手` + return [ + `${row.rank}. ${row.name}`, + `净持仓 ${fmtSignedInt(row.netQty)}`, + action, + `近一年盈亏 ${formatProfitAbs(row.yearProfit)}`, + `一周胜率 ${formatPct(row.weekRate)}`, + `多空流向 ${formatPct(row.dayMood)}(${row.flowLabel})`, + `3日情绪 ${formatPct(row.threeDayMood)}|5日情绪 ${formatPct(row.fiveDayMood)}`, + `价差持仓 ${row.priceDiffContract || '无'}`, + ].join('|') +} + function formatPositionRows(list: PositionRow[], qtyLabel: string): string[] { const top = list.slice(0, POSITION_TOP_N) if (!top.length) return ['(无)'] diff --git a/src/api/deepseek/types.ts b/src/api/deepseek/types.ts index b2f0696..c375637 100644 --- a/src/api/deepseek/types.ts +++ b/src/api/deepseek/types.ts @@ -4,7 +4,7 @@ import type { AnalysisSnapshot } from '../../stores/quota' export interface AiAnalysisPayload { /** 设置中的角色/分析关键词 */ keywords: string - /** 行情 + 新闻 + 机构持仓 + 主力追踪 + 主力异动等接口快照 */ + /** 行情 + 新闻 + 机构持仓(含主力分析)+ 主力追踪 + 主力异动等接口快照 */ snapshot: AnalysisSnapshot /** 大单手数阈值,与 UI 大单分析一致 */ largeOrderLots: number diff --git a/src/api/jqka/mapMainForceAnalysis.ts b/src/api/jqka/mapMainForceAnalysis.ts new file mode 100644 index 0000000..0d1b60b --- /dev/null +++ b/src/api/jqka/mapMainForceAnalysis.ts @@ -0,0 +1,91 @@ +import type { MainForceAnalysisData, MainForceAnalysisItem } from '../../types' +import type { + JqkaMainForceAnalysisData, + JqkaMainForceAnalysisItem, +} from './types' + +const TOP_N = 20 + +function toNum(v: string | number | null | undefined): number { + if (typeof v === 'number') return Number.isFinite(v) ? v : 0 + if (v == null || v === '') return 0 + const n = Number(v) + return Number.isFinite(n) ? n : 0 +} + +/** + * 多空流向文案(对齐同花顺 H5): + * - day_mood < 15% → 持仓平稳 + * - < 40% → 小幅流多/流空 + * - < 90% → 中度流多/流空 + * - ≥ 90% → 大幅流多/流空 + * 方向由净持仓增减 f025n 决定(>0 流多,<0 流空)。 + */ +export function buildFlowLabel( + dayMood: number, + netChange: number, +): { flowSide: MainForceAnalysisItem['flowSide']; flowLabel: string } { + if (dayMood < 0.15 || netChange === 0) { + return { flowSide: 'flat', flowLabel: '持仓平稳' } + } + const intens = + dayMood >= 0.9 ? '大幅' : dayMood >= 0.4 ? '中度' : '小幅' + if (netChange > 0) { + return { flowSide: 'long', flowLabel: `${intens}流多` } + } + return { flowSide: 'short', flowLabel: `${intens}流空` } +} + +function mapItem( + item: JqkaMainForceAnalysisItem, + rank: number, +): MainForceAnalysisItem { + const netChange = item.f025n + const dayMood = toNum(item.day_mood) + const flow = buildFlowLabel(dayMood, netChange) + const unitFunds = toNum(item.pre_pupil_flow) + + return { + rank, + name: item.company, + netQty: item.f024n, + netChange, + changeFunds: netChange * unitFunds, + dayProfit: toNum(item.day_profit), + yearProfit: toNum(item.year_profit), + weekRate: toNum(item.week_rate), + dayMood, + flowSide: flow.flowSide, + flowLabel: flow.flowLabel, + threeDayMood: toNum(item.three_day_mood), + fiveDayMood: toNum(item.five_day_mood), + threeDayNetChange: item.three_day_f025n, + fiveDayNetChange: item.five_day_f025n, + priceDiffContract: (item.price_diff_contract || '').trim(), + date: item.date, + } +} + +/** + * 主力分析接口 → 页面 / AI 用数据。 + * 仅取 position_list 前 20 条(接口顺序即盈利席位榜顺序)。 + */ +export function mapJqkaMainForceAnalysis( + data: JqkaMainForceAnalysisData, +): MainForceAnalysisData { + const list = (data.position_list ?? []) + .slice(0, TOP_N) + .map((item, i) => mapItem(item, i + 1)) + const vp = data.variety_position + const date = + list[0]?.date || vp?.date || new Date().toISOString().slice(0, 10) + + return { + list, + date, + varietyNetQty: vp?.f024n ?? 0, + varietyNetChange: vp?.f025n ?? 0, + varietyDayMood: toNum(vp?.day_mood), + varietyChangeRisk: toNum(vp?.change_risk), + } +} diff --git a/src/api/jqka/mapPosition.ts b/src/api/jqka/mapPosition.ts index 543c4d6..9c72f0c 100644 --- a/src/api/jqka/mapPosition.ts +++ b/src/api/jqka/mapPosition.ts @@ -103,6 +103,14 @@ export function mapJqkaDealPosition(data: JqkaDealPositionData): PositionsData { netShort, profitGain: [], profitLoss: [], + mainForceAnalysis: { + list: [], + date: '', + varietyNetQty: 0, + varietyNetChange: 0, + varietyDayMood: 0, + varietyChangeRisk: 0, + }, topTwentySum, updatedAt, } diff --git a/src/api/jqka/position.ts b/src/api/jqka/position.ts index 407a143..1ed548d 100644 --- a/src/api/jqka/position.ts +++ b/src/api/jqka/position.ts @@ -2,6 +2,8 @@ import { jqkaHttp } from '../http' import type { JqkaDealPositionData, JqkaDealPositionResponse, + JqkaMainForceAnalysisData, + JqkaMainForceAnalysisResponse, JqkaMainForceTraceData, JqkaMainForceTraceResponse, JqkaPositionProfitData, @@ -142,3 +144,44 @@ export async function getMainForceTrace( return data.data } + +export interface GetMainForceAnalysisParams { + /** 品种代码,如 FG */ + variety: string + /** + * 查询日期 YYYY-MM-DD。 + * 应与机构持仓 date 一致;缺省走持仓默认日期规则。 + */ + date?: string +} + +/** + * 同花顺 — 主力分析(盈利席位榜) + * GET /futgwapi/api/market/dragon_tiger/v1/main_force_analysis?variety=&date= + */ +export async function getMainForceAnalysis( + params: GetMainForceAnalysisParams, +): Promise { + const variety = params.variety?.trim() + if (!variety) { + throw new Error('主力分析接口需要 variety') + } + const date = params.date ?? getPositionQueryDate() + + const { data } = await jqkaHttp.get( + '/futgwapi/api/market/dragon_tiger/v1/main_force_analysis', + { + params: { variety, date }, + }, + ) + + if (data.code !== 0) { + throw new Error(`主力分析接口失败: code=${data.code}, msg=${data.msg}`) + } + + if (!data.data?.position_list) { + throw new Error('主力分析接口返回空 position_list') + } + + return data.data +} diff --git a/src/api/jqka/types.ts b/src/api/jqka/types.ts index 4fd5553..1d0a3d4 100644 --- a/src/api/jqka/types.ts +++ b/src/api/jqka/types.ts @@ -132,3 +132,67 @@ export interface JqkaMainForceTraceResponse { msg: string data: JqkaMainForceTraceData } + +/** 主力分析 — 品种汇总(variety_position) */ +export interface JqkaMainForceAnalysisVariety { + variety: string + market: string | null + f003n: number + f007n: number + f009n: number + f013n: number + f015n: number + f019n: number + f024n: number + f025n: number + percent: string + variety_name: string | null + date: string + day_mood: string + twenty_day_avg: string + change_risk: string + link_close_price: string + index_close_price: string + link_settle_price: string + max_funds: string + pupil_flow: string +} + +/** 主力分析 — 机构席位行(position_list) */ +export interface JqkaMainForceAnalysisItem { + variety: string + company: string + f003n: number + f007n: number + f009n: number + f013n: number + f015n: number + f019n: number + f024n: number + f025n: number + date: string + variety_name: string | null + day_profit: string + year_profit: string + week_rate: string + day_mood: string + three_day_mood: string + five_day_mood: string + three_day_f025n: number + five_day_f025n: number + max_funds: string + price_diff_contract: string + pre_pupil_flow: string +} + +export interface JqkaMainForceAnalysisData { + type: string + variety_position: JqkaMainForceAnalysisVariety | null + position_list: JqkaMainForceAnalysisItem[] +} + +export interface JqkaMainForceAnalysisResponse { + code: number + msg: string + data: JqkaMainForceAnalysisData +} diff --git a/src/components/position/MainForceAnalysisTable.vue b/src/components/position/MainForceAnalysisTable.vue new file mode 100644 index 0000000..8278b3c --- /dev/null +++ b/src/components/position/MainForceAnalysisTable.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/components/position/PositionPanel.vue b/src/components/position/PositionPanel.vue index 2e7a36d..ca5e7aa 100644 --- a/src/components/position/PositionPanel.vue +++ b/src/components/position/PositionPanel.vue @@ -6,6 +6,7 @@ + 数据日期 {{ data.updatedAt }} @@ -50,7 +51,7 @@ -
+

盈利机构前20名

@@ -62,6 +63,13 @@
+ +
+
+

盈利席位榜前20名

+ +
+
@@ -72,6 +80,7 @@ import PositionDonut from './PositionDonut.vue' import PositionTable from './PositionTable.vue' import PositionSumBar from './PositionSumBar.vue' import ProfitTable from './ProfitTable.vue' +import MainForceAnalysisTable from './MainForceAnalysisTable.vue' const props = defineProps<{ data: PositionsData @@ -131,6 +140,10 @@ const profitLossAsRows = computed(() => toDonutRows(props.data.profitLoss)) max-width: 560px; } +.cols.single.wide { + max-width: none; +} + .long-title { margin: 0 0 8px; color: var(--long); diff --git a/src/stores/quota.ts b/src/stores/quota.ts index feb44a8..98b51eb 100644 --- a/src/stores/quota.ts +++ b/src/stores/quota.ts @@ -24,11 +24,13 @@ import type { } from '../api/baidu/wsTypes' import { getDealPosition, + getMainForceAnalysis, getMainForceTrace, getPositionProfitRank, } from '../api/jqka/position' import { getPositionAnomaly } from '../api/jqka/anomaly' import { mapJqkaDealPosition, mapJqkaPositionProfit } from '../api/jqka/mapPosition' +import { mapJqkaMainForceAnalysis } from '../api/jqka/mapMainForceAnalysis' import { mapJqkaMainForceTrace } from '../api/jqka/mapMainForceTrace' import { mapJqkaPositionAnomaly } from '../api/jqka/mapAnomaly' import { @@ -113,6 +115,14 @@ function emptyPositions(): PositionsData { netShort: [], profitGain: [], profitLoss: [], + mainForceAnalysis: { + list: [], + date: '', + varietyNetQty: 0, + varietyNetChange: 0, + varietyDayMood: 0, + varietyChangeRisk: 0, + }, topTwentySum: [], updatedAt: '', } @@ -365,10 +375,10 @@ export const useQuotaStore = defineStore('quota', () => { } /** - * 拉取会员持仓 + 机构盈利 + 主力追踪 + 主力异动。 + * 拉取会员持仓 + 机构盈利 + 主力分析 + 主力追踪 + 主力异动。 * 持仓日期:交易日 16:00 后查当天,否则查上一交易日; * 若遇节假日空数据则再往前最多试 5 个交易日。 - * 主力追踪 / 主力异动 date 与最终持仓 date 保持一致。 + * 主力追踪 / 主力异动 / 主力分析 date 与最终持仓 date 保持一致。 * 盈利默认最近一个月、按当前品种查询。 */ async function fetchPositions() { @@ -400,14 +410,26 @@ export const useQuotaStore = defineStore('quota', () => { throw new Error(`持仓无数据(已回溯至 ${date})`) } - try { - const profitRaw = await getPositionProfitRank({ variety, contract }) - const profit = mapJqkaPositionProfit(profitRaw) - mapped.profitGain = profit.profitGain - mapped.profitLoss = profit.profitLoss - } catch (e) { - console.error('[quota] 拉取机构盈利失败', e) - } + await Promise.all([ + (async () => { + try { + const profitRaw = await getPositionProfitRank({ variety, contract }) + const profit = mapJqkaPositionProfit(profitRaw) + mapped!.profitGain = profit.profitGain + mapped!.profitLoss = profit.profitLoss + } catch (e) { + console.error('[quota] 拉取机构盈利失败', e) + } + })(), + (async () => { + try { + const analysisRaw = await getMainForceAnalysis({ variety, date }) + mapped!.mainForceAnalysis = mapJqkaMainForceAnalysis(analysisRaw) + } catch (e) { + console.error('[quota] 拉取主力分析失败', e) + } + })(), + ]) positions.value = mapped positionsLoaded.value = true @@ -426,7 +448,7 @@ export const useQuotaStore = defineStore('quota', () => { })(), (async () => { try { - const anomalyRaw = await getPositionAnomaly({ date, limit: 3 }) + const anomalyRaw = await getPositionAnomaly({ date, limit: 5 }) positionAnomaly.value = mapJqkaPositionAnomaly(anomalyRaw) positionAnomalyLoaded.value = true } catch (e) { diff --git a/src/types/index.ts b/src/types/index.ts index d2af91d..d33a5f2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -98,6 +98,50 @@ export interface ProfitRow { percent: number } +/** 主力分析(盈利席位榜)行 */ +export interface MainForceAnalysisItem { + rank: number + name: string + /** 净持仓 f024n */ + netQty: number + /** 净持仓增减 f025n:>0 加多,<0 加空 */ + netChange: number + /** 持仓变化资金估算(约等于 f025n × pre_pupil_flow) */ + changeFunds: number + dayProfit: number + /** 近一年盈亏(估) */ + yearProfit: number + /** 一周胜率 0~1 */ + weekRate: number + /** 多空流向强度 0~1 */ + dayMood: number + /** 流向方向:long=流多 / short=流空 / flat=平稳 */ + flowSide: 'long' | 'short' | 'flat' + /** 如「中度流空」「持仓平稳」 */ + flowLabel: string + threeDayMood: number + fiveDayMood: number + threeDayNetChange: number + fiveDayNetChange: number + /** 价差持仓原文;空则页面显示「无」 */ + priceDiffContract: string + date: string +} + +/** 主力分析(当前品种盈利席位榜前 20) */ +export interface MainForceAnalysisData { + list: MainForceAnalysisItem[] + date: string + /** 品种净持仓 */ + varietyNetQty: number + /** 品种净持仓增减 */ + varietyNetChange: number + /** 品种日内情绪 0~1 */ + varietyDayMood: number + /** 品种变盘风险 0~1 */ + varietyChangeRisk: number +} + /** 前 20 多空持仓汇总(按交易日) */ export interface TopTwentySum { tradeDate: string @@ -120,6 +164,8 @@ export interface PositionsData { profitGain: ProfitRow[] /** 亏损机构前 20(day_profit < 0) */ profitLoss: ProfitRow[] + /** 主力分析(盈利席位榜前 20 + 品种汇总) */ + mainForceAnalysis: MainForceAnalysisData /** 前 20 持仓量汇总 */ topTwentySum: TopTwentySum[] updatedAt: string