From 24a72bfd3f8aea542603eeb373b4f91ed14190da Mon Sep 17 00:00:00 2001 From: dongzp <975303544@qq.com> Date: Tue, 21 Jul 2026 18:56:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=9C=BA=E6=9E=84=E5=93=81?= =?UTF-8?q?=E7=A7=8D=E7=9B=88=E5=88=A9=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/jqka/mapPosition.ts | 77 ++++++++++++++++++++++- src/api/jqka/position.ts | 67 +++++++++++++++++++- src/api/jqka/types.ts | 36 +++++++++++ src/components/position/PositionPanel.vue | 41 +++++++++--- src/components/position/ProfitTable.vue | 57 +++++++++++++++++ src/mocks/positions.ts | 30 ++++++++- src/stores/quota.ts | 18 +++++- src/types/index.ts | 18 ++++++ src/utils/tradingDate.ts | 16 +++++ 9 files changed, 344 insertions(+), 16 deletions(-) create mode 100644 src/components/position/ProfitTable.vue diff --git a/src/api/jqka/mapPosition.ts b/src/api/jqka/mapPosition.ts index 90935d0..6925946 100644 --- a/src/api/jqka/mapPosition.ts +++ b/src/api/jqka/mapPosition.ts @@ -1,5 +1,9 @@ -import type { PositionRow, PositionsData, TopTwentySum } from '../../types' -import type { JqkaDealPositionData, JqkaPositionItem } from './types' +import type { PositionRow, PositionsData, ProfitRow, TopTwentySum } from '../../types' +import type { + JqkaDealPositionData, + JqkaPositionItem, + JqkaPositionProfitData, +} from './types' const TOP_N = 20 @@ -91,7 +95,74 @@ export function mapJqkaDealPosition(data: JqkaDealPositionData): PositionsData { volume, netLong, netShort, + profitGain: [], + profitLoss: [], topTwentySum, updatedAt, } -} \ No newline at end of file +} + +function withProfitPercent(list: ProfitRow[]): ProfitRow[] { + const total = list.reduce((s, x) => s + Math.abs(x.profit), 0) + if (total <= 0) { + return list.map((x) => ({ ...x, percent: 0 })) + } + return list.map((x) => ({ + ...x, + percent: Number(((Math.abs(x.profit) / total) * 100).toFixed(2)), + })) +} + +/** + * 将机构盈利接口映射为盈利 / 亏损前 20。 + * day_profit 为盈利金额;顺带保留净持仓与增减。 + */ +export function mapJqkaPositionProfit(data: JqkaPositionProfitData): { + profitGain: ProfitRow[] + profitLoss: ProfitRow[] + updatedAt: string +} { + const list = (data.profit_detail_list ?? []) + .map((item) => ({ + name: item.company, + profit: Number(item.day_profit) || 0, + netQty: item.f024n, + change: item.f025n, + date: item.date, + })) + .filter((x) => x.profit !== 0) + + const gainRaw = [...list] + .filter((x) => x.profit > 0) + .sort((a, b) => b.profit - a.profit) + .slice(0, TOP_N) + .map((x, i) => ({ + rank: i + 1, + name: x.name, + profit: x.profit, + netQty: x.netQty, + change: x.change, + date: x.date, + percent: 0, + })) + + const lossRaw = [...list] + .filter((x) => x.profit < 0) + .sort((a, b) => a.profit - b.profit) + .slice(0, TOP_N) + .map((x, i) => ({ + rank: i + 1, + name: x.name, + profit: x.profit, + netQty: x.netQty, + change: x.change, + date: x.date, + percent: 0, + })) + + return { + profitGain: withProfitPercent(gainRaw), + profitLoss: withProfitPercent(lossRaw), + updatedAt: data.date || new Date().toISOString().slice(0, 10), + } +} diff --git a/src/api/jqka/position.ts b/src/api/jqka/position.ts index 219d9ff..935abff 100644 --- a/src/api/jqka/position.ts +++ b/src/api/jqka/position.ts @@ -1,6 +1,16 @@ import { jqkaHttp } from '../http' -import type { JqkaDealPositionData, JqkaDealPositionResponse } from './types' -import { extractVariety, getPreviousTradingDate } from '../../utils/tradingDate' +import type { + JqkaDealPositionData, + JqkaDealPositionResponse, + JqkaPositionProfitData, + JqkaPositionProfitRequest, + JqkaPositionProfitResponse, +} from './types' +import { + extractVariety, + getPreviousTradingDate, + getRecentMonthRange, +} from '../../utils/tradingDate' export interface GetDealPositionParams { /** 合约代码,如 FG609 */ @@ -42,3 +52,56 @@ export async function getDealPosition( return data.data } + +export interface GetPositionProfitRankParams { + /** 品种代码;缺省时可从 contract 推导 */ + variety?: string + contract?: string + /** 默认最近一个月 */ + startDate?: string + endDate?: string + /** type=company 时使用;variety 查询可不传 */ + company?: string + type?: 'variety' | 'company' +} + +/** + * 同花顺 — 机构盈利排行(按品种查看最近盈利) + * POST /futgwapi/api/market/dragon_tiger/v1/position_profit_rank + */ +export async function getPositionProfitRank( + params: GetPositionProfitRankParams = {}, +): Promise { + const variety = + params.variety ?? + (params.contract ? extractVariety(params.contract) : undefined) + if (!variety) { + throw new Error('盈利接口需要 variety 或 contract') + } + + const range = getRecentMonthRange() + const body: JqkaPositionProfitRequest = { + variety, + type: params.type ?? 'variety', + start_date: params.startDate ?? range.startDate, + end_date: params.endDate ?? range.endDate, + } + if (params.company) { + body.company = params.company + } + + const { data } = await jqkaHttp.post( + '/futgwapi/api/market/dragon_tiger/v1/position_profit_rank', + body, + ) + + if (data.code !== 0) { + throw new Error(`盈利接口失败: code=${data.code}, msg=${data.msg}`) + } + + if (!data.data?.profit_detail_list) { + throw new Error('盈利接口返回空 profit_detail_list') + } + + return data.data +} diff --git a/src/api/jqka/types.ts b/src/api/jqka/types.ts index 33c2f76..a19e702 100644 --- a/src/api/jqka/types.ts +++ b/src/api/jqka/types.ts @@ -41,3 +41,39 @@ export interface JqkaDealPositionResponse { msg: string data: JqkaDealPositionData } + +/** 机构盈利排行明细行 */ +export interface JqkaProfitDetailItem { + variety: string + company: string + /** 净持仓 */ + f024n: number + /** 净持仓增减 */ + f025n: number + date: string + variety_name: string + /** 盈利金额(字符串数字) */ + day_profit: string + year_profit: string | null +} + +export interface JqkaPositionProfitData { + date: string + profit_detail_list: JqkaProfitDetailItem[] +} + +export interface JqkaPositionProfitResponse { + code: number + msg: string + data: JqkaPositionProfitData +} + +export interface JqkaPositionProfitRequest { + /** 品种代码,如 FG;type=variety 时必填 */ + variety: string + type: 'variety' | 'company' + start_date: string + end_date: string + /** type=company 时必填;variety 查询可省略 */ + company?: string +} diff --git a/src/components/position/PositionPanel.vue b/src/components/position/PositionPanel.vue index 74ce327..2e7a36d 100644 --- a/src/components/position/PositionPanel.vue +++ b/src/components/position/PositionPanel.vue @@ -5,11 +5,11 @@ + 数据日期 {{ data.updatedAt }} -
-

成交量前20名

@@ -38,8 +37,7 @@
- -
+

净多持仓前20名

@@ -51,21 +49,50 @@
+ +
+
+

盈利机构前20名

+ + +
+
+

亏损机构前20名

+ + +
+
diff --git a/src/mocks/positions.ts b/src/mocks/positions.ts index 311abec..e1e93c3 100644 --- a/src/mocks/positions.ts +++ b/src/mocks/positions.ts @@ -1,4 +1,4 @@ -import type { PositionRow, PositionsData } from '../types' +import type { PositionRow, PositionsData, ProfitRow } from '../types' function makeList(names: string[], base: number): PositionRow[] { return names.map((name, i) => { @@ -22,6 +22,30 @@ function withPercent(list: PositionRow[]): PositionRow[] { })) } +function makeProfit( + names: string[], + base: number, + sign: 1 | -1, +): ProfitRow[] { + const list = names.map((name, i) => { + const profit = sign * Math.floor(base * (0.2 - i * 0.01) + Math.random() * 5e6) + return { + rank: i + 1, + name, + profit, + netQty: Math.floor((Math.random() - 0.5) * 80000), + change: Math.floor((Math.random() - 0.5) * 2000), + date: '2026-07-21', + percent: 0, + } + }) + const total = list.reduce((s, x) => s + Math.abs(x.profit), 0) + return list.map((x) => ({ + ...x, + percent: total > 0 ? Number(((Math.abs(x.profit) / total) * 100).toFixed(2)) : 0, + })) +} + const longNames = [ '中财期货', '东证期货', @@ -73,6 +97,8 @@ const short = withPercent(makeList(shortNames, 640000)) const volume = withPercent(makeList(longNames, 480000)) const netLong = withPercent(makeList(longNames.slice(0, 12), 120000)) const netShort = withPercent(makeList(shortNames.slice(0, 12), 110000)) +const profitGain = makeProfit(longNames.slice(0, 12), 2e8, 1) +const profitLoss = makeProfit(shortNames.slice(0, 12), 1.5e8, -1) export const positionsMock: PositionsData = { long, @@ -80,6 +106,8 @@ export const positionsMock: PositionsData = { volume, netLong, netShort, + profitGain, + profitLoss, topTwentySum: [ { tradeDate: '2026-07-15', longSum: 1073446, shortSum: 1502020 }, { tradeDate: '2026-07-14', longSum: 1094199, shortSum: 1519906 }, diff --git a/src/stores/quota.ts b/src/stores/quota.ts index f660fa8..592499f 100644 --- a/src/stores/quota.ts +++ b/src/stores/quota.ts @@ -11,8 +11,8 @@ import { mapBaiduQuotationToQuote } from '../api/baidu/mapQuote' import { mapBaiduKlineToCandles } from '../api/baidu/mapKline' import { mapBaiduNewsToItems } from '../api/baidu/mapNews' import type { BaiduKlineType } from '../api/baidu/types' -import { getDealPosition } from '../api/jqka/position' -import { mapJqkaDealPosition } from '../api/jqka/mapPosition' +import { getDealPosition, getPositionProfitRank } from '../api/jqka/position' +import { mapJqkaDealPosition, mapJqkaPositionProfit } from '../api/jqka/mapPosition' import { extractVariety, getPreviousTradingDate, @@ -128,7 +128,9 @@ export const useQuotaStore = defineStore('quota', () => { } /** - * 拉取会员持仓。日期默认上一交易日;若遇节假日空数据则再往前最多试 5 个交易日。 + * 拉取会员持仓 + 机构盈利。 + * 持仓日期默认上一交易日;若遇节假日空数据则再往前最多试 5 个交易日。 + * 盈利默认最近一个月、按当前品种查询。 */ async function fetchPositions() { positionsLoading.value = true @@ -153,6 +155,16 @@ export const useQuotaStore = defineStore('quota', () => { if (!mapped) { 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) + } + positions.value = mapped } catch (e) { positionsError.value = e diff --git a/src/types/index.ts b/src/types/index.ts index 6790e7e..7093b6b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -82,6 +82,20 @@ export interface PositionRow { percent: number } +/** 机构盈利排行行(day_profit) */ +export interface ProfitRow { + rank: number + name: string + /** 盈利金额(可正可负) */ + profit: number + /** 净持仓 f024n */ + netQty: number + /** 净持仓增减 f025n */ + change: number + date: string + percent: number +} + /** 前 20 多空持仓汇总(按交易日) */ export interface TopTwentySum { tradeDate: string @@ -100,6 +114,10 @@ export interface PositionsData { netLong: PositionRow[] /** 净空持仓(f024n < 0,qty 取绝对值) */ netShort: PositionRow[] + /** 盈利机构前 20(day_profit > 0) */ + profitGain: ProfitRow[] + /** 亏损机构前 20(day_profit < 0) */ + profitLoss: ProfitRow[] /** 前 20 持仓量汇总 */ topTwentySum: TopTwentySum[] updatedAt: string diff --git a/src/utils/tradingDate.ts b/src/utils/tradingDate.ts index 27a874b..31d1d74 100644 --- a/src/utils/tradingDate.ts +++ b/src/utils/tradingDate.ts @@ -33,3 +33,19 @@ export function extractVariety(contract: string): string { const m = contract.match(/^([A-Za-z]+)/) return m ? m[1] : contract } + +/** + * 最近一个月日期区间(含起止日),用于机构盈利等按区间查询的接口。 + */ +export function getRecentMonthRange(from: Date = new Date()): { + startDate: string + endDate: string +} { + const end = new Date(from.getFullYear(), from.getMonth(), from.getDate()) + const start = new Date(end) + start.setMonth(start.getMonth() - 1) + return { + startDate: formatDateYmd(start), + endDate: formatDateYmd(end), + } +}