主力追踪
This commit is contained in:
parent
573d39696c
commit
b4f790e185
34
src/App.vue
34
src/App.vue
@ -12,12 +12,19 @@
|
|||||||
<section class="quote-section">
|
<section class="quote-section">
|
||||||
<QuoteStats v-if="quote" :quote="quote" />
|
<QuoteStats v-if="quote" :quote="quote" />
|
||||||
<div class="quote-grid">
|
<div class="quote-grid">
|
||||||
<ChartPanel
|
<div class="chart-col">
|
||||||
v-if="quote"
|
<ChartPanel
|
||||||
:quote="quote"
|
v-if="quote"
|
||||||
:kline-loading="klineLoading"
|
:quote="quote"
|
||||||
:load-kline="loadKline"
|
:kline-loading="klineLoading"
|
||||||
/>
|
:load-kline="loadKline"
|
||||||
|
/>
|
||||||
|
<MainForceTracePanel
|
||||||
|
:data="mainForceTrace"
|
||||||
|
:loading="mainForceTraceLoading"
|
||||||
|
:error="mainForceTraceError"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<aside class="side">
|
<aside class="side">
|
||||||
<OrderBook v-if="quote" :quote="quote" />
|
<OrderBook v-if="quote" :quote="quote" />
|
||||||
<TradeTape v-if="quote" :quote="quote" />
|
<TradeTape v-if="quote" :quote="quote" />
|
||||||
@ -74,6 +81,7 @@ import AppLayout from './layout/AppLayout.vue'
|
|||||||
import AppHeader from './components/header/AppHeader.vue'
|
import AppHeader from './components/header/AppHeader.vue'
|
||||||
import QuoteStats from './components/quote/QuoteStats.vue'
|
import QuoteStats from './components/quote/QuoteStats.vue'
|
||||||
import ChartPanel from './components/quote/ChartPanel.vue'
|
import ChartPanel from './components/quote/ChartPanel.vue'
|
||||||
|
import MainForceTracePanel from './components/quote/MainForceTracePanel.vue'
|
||||||
import OrderBook from './components/quote/OrderBook.vue'
|
import OrderBook from './components/quote/OrderBook.vue'
|
||||||
import TradeTape from './components/quote/TradeTape.vue'
|
import TradeTape from './components/quote/TradeTape.vue'
|
||||||
import LargeOrderAnalysis from './components/quote/LargeOrderAnalysis.vue'
|
import LargeOrderAnalysis from './components/quote/LargeOrderAnalysis.vue'
|
||||||
@ -88,7 +96,13 @@ import { useAiAdvice } from './composables/useAiAdvice'
|
|||||||
import { useQuotaStore } from './stores/quota'
|
import { useQuotaStore } from './stores/quota'
|
||||||
|
|
||||||
const { data: quote, klineLoading, loadKline } = useQuote()
|
const { data: quote, klineLoading, loadKline } = useQuote()
|
||||||
const { wsStatus, wsInSession } = storeToRefs(useQuotaStore())
|
const {
|
||||||
|
wsStatus,
|
||||||
|
wsInSession,
|
||||||
|
mainForceTrace,
|
||||||
|
mainForceTraceLoading,
|
||||||
|
mainForceTraceError,
|
||||||
|
} = storeToRefs(useQuotaStore())
|
||||||
const { data: news, loading: newsLoading, error: newsError, refresh: refreshNews } = useNews()
|
const { data: news, loading: newsLoading, error: newsError, refresh: refreshNews } = useNews()
|
||||||
const { data: positions } = usePositions()
|
const { data: positions } = usePositions()
|
||||||
const { data: advice, loading: adviceLoading, refresh: refreshAdvice } = useAiAdvice()
|
const { data: advice, loading: adviceLoading, refresh: refreshAdvice } = useAiAdvice()
|
||||||
@ -105,6 +119,12 @@ const positionOpen = ref(false)
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-col {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
.quote-grid {
|
.quote-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -19,7 +19,8 @@ export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{
|
|||||||
}> {
|
}> {
|
||||||
const system = [
|
const system = [
|
||||||
payload.keywords.trim() || '你是一名专业的国内期货交易员和分析员。',
|
payload.keywords.trim() || '你是一名专业的国内期货交易员和分析员。',
|
||||||
'请仅基于用户提供的行情、盘口、新闻与机构持仓数据给出交易建议。',
|
'请仅基于用户提供的行情、盘口、新闻、机构持仓与主力追踪(变盘预警、主力趋势机会)数据给出交易建议。',
|
||||||
|
'主力追踪为全市场数据,请优先关注与当前合约品种相关的条目,并结合整体主力动向综合判断。',
|
||||||
'不要编造未提供的数据;信息不足时偏向观望并说明原因。',
|
'不要编造未提供的数据;信息不足时偏向观望并说明原因。',
|
||||||
'必须只输出一个 JSON 对象,不要 Markdown 代码块,不要其它说明文字。',
|
'必须只输出一个 JSON 对象,不要 Markdown 代码块,不要其它说明文字。',
|
||||||
`JSON 格式:${OUTPUT_SCHEMA}`,
|
`JSON 格式:${OUTPUT_SCHEMA}`,
|
||||||
@ -38,7 +39,7 @@ export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 压缩超长数组,保留分析关键字段,避免 prompt 过大。
|
* 压缩超长数组,保留分析关键字段,避免 prompt 过大。
|
||||||
* 盘口、统计、持仓、新闻完整保留;分时/成交/K 线做尾部截断。
|
* 盘口、统计、持仓、新闻、主力追踪完整保留;分时/成交/K 线做尾部截断。
|
||||||
*/
|
*/
|
||||||
function slimSnapshot(snapshot: AnalysisSnapshot): AnalysisSnapshot {
|
function slimSnapshot(snapshot: AnalysisSnapshot): AnalysisSnapshot {
|
||||||
const quote = snapshot.quote
|
const quote = snapshot.quote
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { AnalysisSnapshot } from '../../stores/quota'
|
|||||||
export interface AiAnalysisPayload {
|
export interface AiAnalysisPayload {
|
||||||
/** 设置中的角色/分析关键词 */
|
/** 设置中的角色/分析关键词 */
|
||||||
keywords: string
|
keywords: string
|
||||||
/** 行情 + 新闻 + 机构持仓等接口快照 */
|
/** 行情 + 新闻 + 机构持仓 + 主力追踪等接口快照 */
|
||||||
snapshot: AnalysisSnapshot
|
snapshot: AnalysisSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
108
src/api/jqka/mapMainForceTrace.ts
Normal file
108
src/api/jqka/mapMainForceTrace.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import type { ChangeRiskItem, MainForceTraceData, MainForceTrendItem } from '../../types'
|
||||||
|
import type { JqkaMainForceTraceData, JqkaVarietyPositionItem } from './types'
|
||||||
|
|
||||||
|
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):
|
||||||
|
* - 净持仓减少且多单也在减 → 主力净减多头
|
||||||
|
* - 净持仓减少但多单未减(空单增更多)→ 主力净增空头
|
||||||
|
* - 净持仓增加且空单也在减 → 主力净减空头
|
||||||
|
* - 其余净增 → 主力净增多头
|
||||||
|
* 手数取 |f025n|。
|
||||||
|
*/
|
||||||
|
export function buildChangeRiskAction(item: {
|
||||||
|
f013n: number
|
||||||
|
f019n: number
|
||||||
|
f025n: number
|
||||||
|
}): { actionLabel: string; actionLots: number } {
|
||||||
|
const lots = Math.abs(item.f025n)
|
||||||
|
if (item.f025n < 0) {
|
||||||
|
return {
|
||||||
|
actionLabel: item.f013n < 0 ? '主力净减多头' : '主力净增空头',
|
||||||
|
actionLots: lots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.f025n > 0) {
|
||||||
|
return {
|
||||||
|
actionLabel: item.f019n < 0 ? '主力净减空头' : '主力净增多头',
|
||||||
|
actionLots: lots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { actionLabel: '主力持仓不变', actionLots: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主力趋势多空:看净增减 / 资金流向符号,与接口 type 无关 */
|
||||||
|
function resolveTrendSide(f025n: number, pupilFlow: number): 'long' | 'short' {
|
||||||
|
if (f025n !== 0) return f025n > 0 ? 'long' : 'short'
|
||||||
|
return pupilFlow >= 0 ? 'long' : 'short'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapChangeRisk(item: JqkaVarietyPositionItem): ChangeRiskItem {
|
||||||
|
const action = buildChangeRiskAction(item)
|
||||||
|
return {
|
||||||
|
variety: item.variety,
|
||||||
|
varietyName: item.variety_name,
|
||||||
|
date: item.date,
|
||||||
|
changeRisk: toNum(item.change_risk),
|
||||||
|
dayMood: toNum(item.day_mood),
|
||||||
|
percent: toNum(item.percent),
|
||||||
|
pupilFlow: toNum(item.pupil_flow),
|
||||||
|
linkClosePrice: toNum(item.link_close_price),
|
||||||
|
indexClosePrice: toNum(item.index_close_price),
|
||||||
|
linkSettlePrice: toNum(item.link_settle_price),
|
||||||
|
twentyDayAvg: toNum(item.twenty_day_avg),
|
||||||
|
maxFunds: toNum(item.max_funds),
|
||||||
|
longChange: item.f013n,
|
||||||
|
shortChange: item.f019n,
|
||||||
|
netQty: item.f024n,
|
||||||
|
netChange: item.f025n,
|
||||||
|
actionLabel: action.actionLabel,
|
||||||
|
actionLots: action.actionLots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将主力追踪接口映射为页面 / AI 用 MainForceTraceData。
|
||||||
|
* variety_position_list → 变盘预警;main_force_trend_list → 主力趋势机会。
|
||||||
|
*/
|
||||||
|
export function mapJqkaMainForceTrace(data: JqkaMainForceTraceData): MainForceTraceData {
|
||||||
|
const changeRiskList: ChangeRiskItem[] = (data.variety_position_list ?? [])
|
||||||
|
.map(mapChangeRisk)
|
||||||
|
.sort((a, b) => b.changeRisk - a.changeRisk)
|
||||||
|
|
||||||
|
const mainForceTrendList: MainForceTrendItem[] = (data.main_force_trend_list ?? []).map(
|
||||||
|
(item) => {
|
||||||
|
const netChange = item.f025n
|
||||||
|
const pupilFlow = toNum(item.pupil_flow)
|
||||||
|
return {
|
||||||
|
variety: item.variety,
|
||||||
|
varietyName: item.variety_name,
|
||||||
|
company: item.company,
|
||||||
|
logo: item.logo || '',
|
||||||
|
date: item.date,
|
||||||
|
rawType: item.type,
|
||||||
|
side: resolveTrendSide(netChange, pupilFlow),
|
||||||
|
mood: toNum(item.mood),
|
||||||
|
weekRate: toNum(item.week_rate),
|
||||||
|
yearProfit: toNum(item.year_profit),
|
||||||
|
pupilFlow,
|
||||||
|
percent: toNum(item.percent),
|
||||||
|
closePrice: toNum(item.close_price),
|
||||||
|
netChange,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const date =
|
||||||
|
changeRiskList[0]?.date ||
|
||||||
|
mainForceTrendList[0]?.date ||
|
||||||
|
new Date().toISOString().slice(0, 10)
|
||||||
|
|
||||||
|
return { changeRiskList, mainForceTrendList, date }
|
||||||
|
}
|
||||||
@ -2,6 +2,8 @@ import { jqkaHttp } from '../http'
|
|||||||
import type {
|
import type {
|
||||||
JqkaDealPositionData,
|
JqkaDealPositionData,
|
||||||
JqkaDealPositionResponse,
|
JqkaDealPositionResponse,
|
||||||
|
JqkaMainForceTraceData,
|
||||||
|
JqkaMainForceTraceResponse,
|
||||||
JqkaPositionProfitData,
|
JqkaPositionProfitData,
|
||||||
JqkaPositionProfitRequest,
|
JqkaPositionProfitRequest,
|
||||||
JqkaPositionProfitResponse,
|
JqkaPositionProfitResponse,
|
||||||
@ -105,3 +107,38 @@ export async function getPositionProfitRank(
|
|||||||
|
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetMainForceTraceParams {
|
||||||
|
/**
|
||||||
|
* 查询日期 YYYY-MM-DD。
|
||||||
|
* 应与机构持仓 date 一致;缺省走持仓默认日期规则。
|
||||||
|
*/
|
||||||
|
date?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同花顺 — 主力追踪(变盘预警 + 主力趋势机会)
|
||||||
|
* GET /futgwapi/api/market/dragon_tiger/v1/main_force_trace?date=
|
||||||
|
*/
|
||||||
|
export async function getMainForceTrace(
|
||||||
|
params: GetMainForceTraceParams = {},
|
||||||
|
): Promise<JqkaMainForceTraceData> {
|
||||||
|
const date = params.date ?? getPositionQueryDate()
|
||||||
|
|
||||||
|
const { data } = await jqkaHttp.get<JqkaMainForceTraceResponse>(
|
||||||
|
'/futgwapi/api/market/dragon_tiger/v1/main_force_trace',
|
||||||
|
{
|
||||||
|
params: { date },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`主力追踪接口失败: code=${data.code}, msg=${data.msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.data) {
|
||||||
|
throw new Error('主力追踪接口返回空 data')
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|||||||
@ -77,3 +77,58 @@ export interface JqkaPositionProfitRequest {
|
|||||||
/** type=company 时必填;variety 查询可省略 */
|
/** type=company 时必填;variety 查询可省略 */
|
||||||
company?: string
|
company?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 变盘预警(variety_position_list) */
|
||||||
|
export interface JqkaVarietyPositionItem {
|
||||||
|
variety: string
|
||||||
|
market: string
|
||||||
|
f003n: number
|
||||||
|
f007n: number
|
||||||
|
f009n: number
|
||||||
|
f013n: number
|
||||||
|
f015n: number
|
||||||
|
f019n: number
|
||||||
|
f024n: number
|
||||||
|
f025n: number
|
||||||
|
percent: string
|
||||||
|
variety_name: string
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主力趋势机会(main_force_trend_list) */
|
||||||
|
export interface JqkaMainForceTrendItem {
|
||||||
|
date: string
|
||||||
|
percent: string
|
||||||
|
variety: string
|
||||||
|
market: string
|
||||||
|
company: string
|
||||||
|
/** 1=多 / 其它=空(接口约定) */
|
||||||
|
type: string
|
||||||
|
mood: string
|
||||||
|
f025n: number
|
||||||
|
logo: string
|
||||||
|
close_price: string
|
||||||
|
variety_name: string
|
||||||
|
year_profit: string
|
||||||
|
week_rate: string
|
||||||
|
pupil_flow: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JqkaMainForceTraceData {
|
||||||
|
variety_position_list: JqkaVarietyPositionItem[]
|
||||||
|
main_force_trend_list: JqkaMainForceTrendItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JqkaMainForceTraceResponse {
|
||||||
|
code: number
|
||||||
|
msg: string
|
||||||
|
data: JqkaMainForceTraceData
|
||||||
|
}
|
||||||
|
|||||||
403
src/components/quote/MainForceTracePanel.vue
Normal file
403
src/components/quote/MainForceTracePanel.vue
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
<template>
|
||||||
|
<section class="main-force-trace card" v-loading="loading">
|
||||||
|
<div class="head">
|
||||||
|
<el-tabs v-model="tab">
|
||||||
|
<el-tab-pane label="变盘预警" name="risk" />
|
||||||
|
<el-tab-pane :label="trendTabLabel" name="trend" />
|
||||||
|
</el-tabs>
|
||||||
|
<span v-if="data?.date" class="updated">数据日期 {{ data.date }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="error" class="error">主力追踪加载失败</div>
|
||||||
|
|
||||||
|
<!-- 变盘预警:对齐 H5 卡片信息 -->
|
||||||
|
<div v-else-if="tab === 'risk'" class="risk-list">
|
||||||
|
<div
|
||||||
|
v-for="row in changeRiskList"
|
||||||
|
:key="row.variety"
|
||||||
|
class="risk-row"
|
||||||
|
:class="{ current: isCurrent(row.variety) }"
|
||||||
|
>
|
||||||
|
<div class="risk-main">
|
||||||
|
<div class="name">{{ row.varietyName }}</div>
|
||||||
|
<div class="action">{{ row.actionLabel }}{{ row.actionLots }}手</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-price">
|
||||||
|
<div class="px">{{ formatPrice(row.linkClosePrice) }}</div>
|
||||||
|
<div :class="pctClass(row.percent)">{{ formatSignedPct(row.percent) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-meter">
|
||||||
|
<div class="meter-label">变盘风险值</div>
|
||||||
|
<div class="meter-bar">
|
||||||
|
<div class="meter-fill" :style="{ width: `${row.changeRisk * 100}%` }" />
|
||||||
|
</div>
|
||||||
|
<div class="meter-pct" :class="riskClass(row.changeRisk)">
|
||||||
|
{{ formatPct(row.changeRisk) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!changeRiskList.length" class="empty">暂无变盘预警</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主力趋势机会:按机构分组,对齐 H5 -->
|
||||||
|
<div v-else class="trend-list">
|
||||||
|
<div v-for="group in trendGroups" :key="group.company" class="trend-group">
|
||||||
|
<div class="group-head">
|
||||||
|
<img v-if="group.logo" :src="group.logo" alt="" class="logo" />
|
||||||
|
<span class="company">{{ group.company }}</span>
|
||||||
|
<span class="group-count">{{ group.items.length }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="row in group.items"
|
||||||
|
:key="`${row.company}-${row.variety}`"
|
||||||
|
class="trend-card"
|
||||||
|
:class="{ current: isCurrent(row.variety) }"
|
||||||
|
>
|
||||||
|
<div class="trend-top">
|
||||||
|
<span class="name">{{ row.varietyName }}</span>
|
||||||
|
<span :class="pctClass(row.percent)">{{ formatSignedPct(row.percent) }}</span>
|
||||||
|
<span class="flow-tag" :class="row.side === 'long' ? 'price-up' : 'price-down'">
|
||||||
|
单日{{ formatPct(row.mood) }}{{ row.side === 'long' ? '流多' : '流空' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="trend-detail">
|
||||||
|
<div>
|
||||||
|
单日加{{ row.side === 'long' ? '多' : '空'
|
||||||
|
}}{{ Math.abs(row.netChange) }}手,流{{ row.side === 'long' ? '多' : '空'
|
||||||
|
}}{{ formatFlowAbs(row.pupilFlow) }}
|
||||||
|
</div>
|
||||||
|
<div>近一周胜率: {{ formatPct(row.weekRate) }}</div>
|
||||||
|
<div>近一年盈利: {{ formatProfitAbs(row.yearProfit) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!trendGroups.length" class="empty">暂无主力趋势</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import type { MainForceTraceData, MainForceTrendItem } from '../../types'
|
||||||
|
import { useContractStore } from '../../stores/contract'
|
||||||
|
import { extractVariety } from '../../utils/tradingDate'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: MainForceTraceData | null
|
||||||
|
loading?: boolean
|
||||||
|
error?: unknown
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tab = ref<'risk' | 'trend'>('risk')
|
||||||
|
const { current } = storeToRefs(useContractStore())
|
||||||
|
|
||||||
|
const currentVariety = computed(() => {
|
||||||
|
const v = current.value.variety || extractVariety(current.value.code)
|
||||||
|
return v.toUpperCase()
|
||||||
|
})
|
||||||
|
|
||||||
|
function isCurrent(variety: string) {
|
||||||
|
return variety.toUpperCase() === currentVariety.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前合约品种置顶 */
|
||||||
|
function prioritizeCurrent<T extends { variety: string }>(list: T[]): T[] {
|
||||||
|
const key = currentVariety.value
|
||||||
|
if (!key) return list
|
||||||
|
const matched: T[] = []
|
||||||
|
const rest: T[] = []
|
||||||
|
for (const item of list) {
|
||||||
|
if (item.variety.toUpperCase() === key) matched.push(item)
|
||||||
|
else rest.push(item)
|
||||||
|
}
|
||||||
|
return [...matched, ...rest]
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeRiskList = computed(() =>
|
||||||
|
prioritizeCurrent(props.data?.changeRiskList ?? []),
|
||||||
|
)
|
||||||
|
|
||||||
|
const trendTabLabel = computed(() => {
|
||||||
|
const n = props.data?.mainForceTrendList?.length ?? 0
|
||||||
|
return n > 0 ? `主力趋势机会 (${n}个)` : '主力趋势机会'
|
||||||
|
})
|
||||||
|
|
||||||
|
interface TrendGroup {
|
||||||
|
company: string
|
||||||
|
logo: string
|
||||||
|
items: MainForceTrendItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按机构分组,组内当前品种置顶;含当前品种的机构优先 */
|
||||||
|
const trendGroups = computed((): TrendGroup[] => {
|
||||||
|
const list = props.data?.mainForceTrendList ?? []
|
||||||
|
const map = new Map<string, TrendGroup>()
|
||||||
|
for (const item of list) {
|
||||||
|
let g = map.get(item.company)
|
||||||
|
if (!g) {
|
||||||
|
g = { company: item.company, logo: item.logo, items: [] }
|
||||||
|
map.set(item.company, g)
|
||||||
|
}
|
||||||
|
g.items.push(item)
|
||||||
|
}
|
||||||
|
const groups = [...map.values()].map((g) => ({
|
||||||
|
...g,
|
||||||
|
items: prioritizeCurrent(g.items),
|
||||||
|
}))
|
||||||
|
const key = currentVariety.value
|
||||||
|
if (!key) return groups
|
||||||
|
return groups.sort((a, b) => {
|
||||||
|
const aHit = a.items.some((x) => x.variety.toUpperCase() === key) ? 0 : 1
|
||||||
|
const bHit = b.items.some((x) => x.variety.toUpperCase() === key) ? 0 : 1
|
||||||
|
return aHit - bHit
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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 pctClass(n: number): string {
|
||||||
|
if (n > 0) return 'price-up'
|
||||||
|
if (n < 0) return 'price-down'
|
||||||
|
return 'pct-flat'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice(n: number): string {
|
||||||
|
if (!Number.isFinite(n) || n === 0) return '—'
|
||||||
|
return n >= 100 ? n.toFixed(0) : n.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 资金流向绝对值(H5:流多3.1亿) */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 年盈利绝对值(H5:5531.69万元 / 2.49亿元) */
|
||||||
|
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)}元`
|
||||||
|
}
|
||||||
|
|
||||||
|
function riskClass(risk: number): string {
|
||||||
|
if (risk >= 0.8) return 'risk-high'
|
||||||
|
if (risk >= 0.5) return 'risk-mid'
|
||||||
|
return 'risk-low'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main-force-trace {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px 14px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head :deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updated {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error,
|
||||||
|
.empty {
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--down, #e54545);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(120px, 1.2fr) minmax(72px, 0.6fr) minmax(140px, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--el-fill-color-lighter, #fafafa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-row.current,
|
||||||
|
.trend-card.current {
|
||||||
|
outline: 1px solid rgba(22, 119, 255, 0.35);
|
||||||
|
background: rgba(22, 119, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-price {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pct-flat {
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-meter {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-bar {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(90deg, #ff9c6e, #f5222d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-pct {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 48px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-high {
|
||||||
|
color: #d4380d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-mid {
|
||||||
|
color: #d48806;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-low {
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
max-height: 420px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary, #8c8c8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-card {
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-tag {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-detail {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--el-fill-color-light, #f5f7fa);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.risk-row {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-meter {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { computed, ref, toRaw } from 'vue'
|
import { computed, ref, toRaw } from 'vue'
|
||||||
import type { Candle, NewsItem, PositionsData, QuoteData } from '../types'
|
import type { Candle, MainForceTraceData, NewsItem, PositionsData, QuoteData } from '../types'
|
||||||
import { useContractStore } from './contract'
|
import { useContractStore } from './contract'
|
||||||
import { getStockKline, getStockQuotation } from '../api/baidu/quotation'
|
import { getStockKline, getStockQuotation } from '../api/baidu/quotation'
|
||||||
import { getFuturesNews } from '../api/baidu/news'
|
import { getFuturesNews } from '../api/baidu/news'
|
||||||
@ -15,8 +15,13 @@ import type {
|
|||||||
BaiduWsSnapshotData,
|
BaiduWsSnapshotData,
|
||||||
BaiduWsTickData,
|
BaiduWsTickData,
|
||||||
} from '../api/baidu/wsTypes'
|
} from '../api/baidu/wsTypes'
|
||||||
import { getDealPosition, getPositionProfitRank } from '../api/jqka/position'
|
import {
|
||||||
|
getDealPosition,
|
||||||
|
getMainForceTrace,
|
||||||
|
getPositionProfitRank,
|
||||||
|
} from '../api/jqka/position'
|
||||||
import { mapJqkaDealPosition, mapJqkaPositionProfit } from '../api/jqka/mapPosition'
|
import { mapJqkaDealPosition, mapJqkaPositionProfit } from '../api/jqka/mapPosition'
|
||||||
|
import { mapJqkaMainForceTrace } from '../api/jqka/mapMainForceTrace'
|
||||||
import {
|
import {
|
||||||
extractVariety,
|
extractVariety,
|
||||||
getPositionQueryDate,
|
getPositionQueryDate,
|
||||||
@ -45,6 +50,8 @@ export interface AnalysisSnapshot {
|
|||||||
quote: QuoteData | null
|
quote: QuoteData | null
|
||||||
news: NewsItem[]
|
news: NewsItem[]
|
||||||
positions: PositionsData | null
|
positions: PositionsData | null
|
||||||
|
/** 变盘预警 + 主力趋势机会(全市场,各合约共用) */
|
||||||
|
mainForceTrace: MainForceTraceData | null
|
||||||
fetchedAt: string
|
fetchedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +107,14 @@ function emptyPositions(): PositionsData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emptyMainForceTrace(): MainForceTraceData {
|
||||||
|
return {
|
||||||
|
changeRiskList: [],
|
||||||
|
mainForceTrendList: [],
|
||||||
|
date: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function activeContract() {
|
function activeContract() {
|
||||||
return useContractStore().current
|
return useContractStore().current
|
||||||
}
|
}
|
||||||
@ -220,18 +235,29 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
const positionsLoading = ref(false)
|
const positionsLoading = ref(false)
|
||||||
const positionsError = ref<unknown>(null)
|
const positionsError = ref<unknown>(null)
|
||||||
|
|
||||||
|
// ─── 主力追踪(变盘预警 + 主力趋势,全市场)─────────────
|
||||||
|
const mainForceTrace = ref<MainForceTraceData>(emptyMainForceTrace())
|
||||||
|
const mainForceTraceLoading = ref(false)
|
||||||
|
const mainForceTraceError = ref<unknown>(null)
|
||||||
|
|
||||||
/** 是否已有至少一次成功的行情拉取(可用于 AI 门禁) */
|
/** 是否已有至少一次成功的行情拉取(可用于 AI 门禁) */
|
||||||
const hasQuote = computed(() => quote.value.last > 0 && quote.value.intraday.length > 0)
|
const hasQuote = computed(() => quote.value.last > 0 && quote.value.intraday.length > 0)
|
||||||
const hasNews = computed(() => news.value.length > 0)
|
const hasNews = computed(() => news.value.length > 0)
|
||||||
/** 新闻 / 持仓是否已成功拉取过真实接口(有则 AI 刷新可跳过) */
|
/** 新闻 / 持仓是否已成功拉取过真实接口(有则 AI 刷新可跳过) */
|
||||||
const newsLoaded = ref(false)
|
const newsLoaded = ref(false)
|
||||||
const positionsLoaded = ref(false)
|
const positionsLoaded = ref(false)
|
||||||
|
const mainForceTraceLoaded = ref(false)
|
||||||
const hasPositions = computed(
|
const hasPositions = computed(
|
||||||
() =>
|
() =>
|
||||||
positions.value.long.length > 0 ||
|
positions.value.long.length > 0 ||
|
||||||
positions.value.short.length > 0 ||
|
positions.value.short.length > 0 ||
|
||||||
positions.value.volume.length > 0,
|
positions.value.volume.length > 0,
|
||||||
)
|
)
|
||||||
|
const hasMainForceTrace = computed(
|
||||||
|
() =>
|
||||||
|
mainForceTrace.value.changeRiskList.length > 0 ||
|
||||||
|
mainForceTrace.value.mainForceTrendList.length > 0,
|
||||||
|
)
|
||||||
|
|
||||||
async function fetchQuote() {
|
async function fetchQuote() {
|
||||||
const c = activeContract()
|
const c = activeContract()
|
||||||
@ -301,14 +327,17 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拉取会员持仓 + 机构盈利。
|
* 拉取会员持仓 + 机构盈利 + 主力追踪。
|
||||||
* 持仓日期:交易日 16:00 后查当天,否则查上一交易日;
|
* 持仓日期:交易日 16:00 后查当天,否则查上一交易日;
|
||||||
* 若遇节假日空数据则再往前最多试 5 个交易日。
|
* 若遇节假日空数据则再往前最多试 5 个交易日。
|
||||||
|
* 主力追踪 date 与最终持仓 date 保持一致。
|
||||||
* 盈利默认最近一个月、按当前品种查询。
|
* 盈利默认最近一个月、按当前品种查询。
|
||||||
*/
|
*/
|
||||||
async function fetchPositions() {
|
async function fetchPositions() {
|
||||||
positionsLoading.value = true
|
positionsLoading.value = true
|
||||||
positionsError.value = null
|
positionsError.value = null
|
||||||
|
mainForceTraceLoading.value = true
|
||||||
|
mainForceTraceError.value = null
|
||||||
try {
|
try {
|
||||||
const c = activeContract()
|
const c = activeContract()
|
||||||
const contract = c.code
|
const contract = c.code
|
||||||
@ -342,11 +371,22 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
|
|
||||||
positions.value = mapped
|
positions.value = mapped
|
||||||
positionsLoaded.value = true
|
positionsLoaded.value = true
|
||||||
|
|
||||||
|
// 主力追踪与机构持仓使用同一 date(全市场,失败不阻断持仓)
|
||||||
|
try {
|
||||||
|
const traceRaw = await getMainForceTrace({ date })
|
||||||
|
mainForceTrace.value = mapJqkaMainForceTrace(traceRaw)
|
||||||
|
mainForceTraceLoaded.value = true
|
||||||
|
} catch (e) {
|
||||||
|
mainForceTraceError.value = e
|
||||||
|
console.error('[quota] 拉取主力追踪失败', e)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
positionsError.value = e
|
positionsError.value = e
|
||||||
console.error('[quota] 拉取持仓失败', e)
|
console.error('[quota] 拉取持仓失败', e)
|
||||||
} finally {
|
} finally {
|
||||||
positionsLoading.value = false
|
positionsLoading.value = false
|
||||||
|
mainForceTraceLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,12 +406,15 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
quote.value = emptyQuote()
|
quote.value = emptyQuote()
|
||||||
news.value = []
|
news.value = []
|
||||||
positions.value = emptyPositions()
|
positions.value = emptyPositions()
|
||||||
|
mainForceTrace.value = emptyMainForceTrace()
|
||||||
loadedKlines.value = { day: false, week: false, month: false }
|
loadedKlines.value = { day: false, week: false, month: false }
|
||||||
newsLoaded.value = false
|
newsLoaded.value = false
|
||||||
positionsLoaded.value = false
|
positionsLoaded.value = false
|
||||||
|
mainForceTraceLoaded.value = false
|
||||||
quoteError.value = null
|
quoteError.value = null
|
||||||
newsError.value = null
|
newsError.value = null
|
||||||
positionsError.value = null
|
positionsError.value = null
|
||||||
|
mainForceTraceError.value = null
|
||||||
|
|
||||||
await fetchAll()
|
await fetchAll()
|
||||||
}
|
}
|
||||||
@ -379,11 +422,14 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
/**
|
/**
|
||||||
* AI 分析前取数:行情每次重新拉取;
|
* AI 分析前取数:行情每次重新拉取;
|
||||||
* 新闻 / 机构持仓若已有成功数据则跳过,避免重复请求。
|
* 新闻 / 机构持仓若已有成功数据则跳过,避免重复请求。
|
||||||
|
* (主力追踪随持仓一并拉取,持仓已加载则跳过)
|
||||||
*/
|
*/
|
||||||
async function fetchForAnalysis() {
|
async function fetchForAnalysis() {
|
||||||
const tasks: Promise<void>[] = [fetchQuote()]
|
const tasks: Promise<void>[] = [fetchQuote()]
|
||||||
if (!newsLoaded.value) tasks.push(fetchNews())
|
if (!newsLoaded.value) tasks.push(fetchNews())
|
||||||
if (!positionsLoaded.value) tasks.push(fetchPositions())
|
if (!positionsLoaded.value || !mainForceTraceLoaded.value) {
|
||||||
|
tasks.push(fetchPositions())
|
||||||
|
}
|
||||||
await Promise.all(tasks)
|
await Promise.all(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,6 +456,9 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
quote: quote.value ? clonePlain(quote.value) : null,
|
quote: quote.value ? clonePlain(quote.value) : null,
|
||||||
news: clonePlain(news.value),
|
news: clonePlain(news.value),
|
||||||
positions: positions.value ? clonePlain(positions.value) : null,
|
positions: positions.value ? clonePlain(positions.value) : null,
|
||||||
|
mainForceTrace: hasMainForceTrace.value
|
||||||
|
? clonePlain(mainForceTrace.value)
|
||||||
|
: null,
|
||||||
fetchedAt: new Date().toISOString(),
|
fetchedAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,14 +476,19 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
positions,
|
positions,
|
||||||
positionsLoading,
|
positionsLoading,
|
||||||
positionsError,
|
positionsError,
|
||||||
|
mainForceTrace,
|
||||||
|
mainForceTraceLoading,
|
||||||
|
mainForceTraceError,
|
||||||
wsStatus,
|
wsStatus,
|
||||||
wsInSession,
|
wsInSession,
|
||||||
// computed
|
// computed
|
||||||
hasQuote,
|
hasQuote,
|
||||||
hasNews,
|
hasNews,
|
||||||
hasPositions,
|
hasPositions,
|
||||||
|
hasMainForceTrace,
|
||||||
newsLoaded,
|
newsLoaded,
|
||||||
positionsLoaded,
|
positionsLoaded,
|
||||||
|
mainForceTraceLoaded,
|
||||||
// actions
|
// actions
|
||||||
fetchQuote,
|
fetchQuote,
|
||||||
loadKline,
|
loadKline,
|
||||||
|
|||||||
@ -125,6 +125,73 @@ export interface PositionsData {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 变盘预警行(主力追踪 variety_position_list) */
|
||||||
|
export interface ChangeRiskItem {
|
||||||
|
variety: string
|
||||||
|
varietyName: string
|
||||||
|
date: string
|
||||||
|
/** 变盘风险 0~1 */
|
||||||
|
changeRisk: number
|
||||||
|
/** 日内情绪 0~1 */
|
||||||
|
dayMood: number
|
||||||
|
/** 涨跌幅(小数,如 -0.00088) */
|
||||||
|
percent: number
|
||||||
|
/** 散户资金流向 */
|
||||||
|
pupilFlow: number
|
||||||
|
linkClosePrice: number
|
||||||
|
indexClosePrice: number
|
||||||
|
linkSettlePrice: number
|
||||||
|
twentyDayAvg: number
|
||||||
|
maxFunds: number
|
||||||
|
/** 多单增减 f013n */
|
||||||
|
longChange: number
|
||||||
|
/** 空单增减 f019n */
|
||||||
|
shortChange: number
|
||||||
|
/** 净持仓 f024n */
|
||||||
|
netQty: number
|
||||||
|
/** 净持仓增减 f025n */
|
||||||
|
netChange: number
|
||||||
|
/**
|
||||||
|
* H5 文案动作:净减多头 / 净增空头 / 净增多头 / 净减空头
|
||||||
|
* 由 f025n + f013n 推导,手数取 |f025n|
|
||||||
|
*/
|
||||||
|
actionLabel: string
|
||||||
|
actionLots: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主力趋势机会行(main_force_trend_list) */
|
||||||
|
export interface MainForceTrendItem {
|
||||||
|
variety: string
|
||||||
|
varietyName: string
|
||||||
|
company: string
|
||||||
|
logo: string
|
||||||
|
date: string
|
||||||
|
/**
|
||||||
|
* 接口原始 type(1/3/5 等),非多空方向。
|
||||||
|
* 多空看 side(由 f025n / pupil_flow 符号决定)。
|
||||||
|
*/
|
||||||
|
rawType: string
|
||||||
|
/** 资金流向方向:long=流多 / short=流空 */
|
||||||
|
side: 'long' | 'short'
|
||||||
|
/** 情绪/流向强度 0~1,H5 展示为「单日 xx%流多/流空」 */
|
||||||
|
mood: number
|
||||||
|
/** 周胜率 0~1 */
|
||||||
|
weekRate: number
|
||||||
|
yearProfit: number
|
||||||
|
pupilFlow: number
|
||||||
|
percent: number
|
||||||
|
closePrice: number
|
||||||
|
/** 净持仓增减 f025n:>0 加多,<0 加空 */
|
||||||
|
netChange: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主力追踪(变盘预警 + 主力趋势机会),全市场,各合约共用 */
|
||||||
|
export interface MainForceTraceData {
|
||||||
|
changeRiskList: ChangeRiskItem[]
|
||||||
|
mainForceTrendList: MainForceTrendItem[]
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
|
||||||
export type AdviceDirection = 'long' | 'short' | 'neutral'
|
export type AdviceDirection = 'long' | 'short' | 'neutral'
|
||||||
|
|
||||||
export interface AiAdvice {
|
export interface AiAdvice {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user