持仓数据接口
This commit is contained in:
parent
6a81305bad
commit
249be8d882
@ -5,3 +5,9 @@ export const baiduHttp = axios.create({
|
|||||||
baseURL: '/baidu',
|
baseURL: '/baidu',
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 开发环境走 Vite 代理 /jqka → https://fupage.10jqka.com.cn */
|
||||||
|
export const jqkaHttp = axios.create({
|
||||||
|
baseURL: '/jqka',
|
||||||
|
timeout: 15000,
|
||||||
|
})
|
||||||
|
|||||||
97
src/api/jqka/mapPosition.ts
Normal file
97
src/api/jqka/mapPosition.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import type { PositionRow, PositionsData, TopTwentySum } from '../../types'
|
||||||
|
import type { JqkaDealPositionData, JqkaPositionItem } from './types'
|
||||||
|
|
||||||
|
const TOP_N = 20
|
||||||
|
|
||||||
|
function withPercent(list: PositionRow[], totalHint?: number): PositionRow[] {
|
||||||
|
const total =
|
||||||
|
totalHint && totalHint > 0
|
||||||
|
? totalHint
|
||||||
|
: list.reduce((s, x) => s + Math.abs(x.qty), 0)
|
||||||
|
if (total <= 0) {
|
||||||
|
return list.map((x) => ({ ...x, percent: 0 }))
|
||||||
|
}
|
||||||
|
return list.map((x) => ({
|
||||||
|
...x,
|
||||||
|
percent: Number(((Math.abs(x.qty) / total) * 100).toFixed(2)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRows(
|
||||||
|
list: JqkaPositionItem[],
|
||||||
|
pickQty: (x: JqkaPositionItem) => number,
|
||||||
|
pickChange: (x: JqkaPositionItem) => number,
|
||||||
|
opts?: { absQty?: boolean; filter?: (x: JqkaPositionItem) => boolean },
|
||||||
|
): PositionRow[] {
|
||||||
|
const filtered = list.filter((item) => {
|
||||||
|
if (opts?.filter && !opts.filter(item)) return false
|
||||||
|
// 空持仓不展示、不参与统计
|
||||||
|
return pickQty(item) !== 0
|
||||||
|
})
|
||||||
|
const ranked = [...filtered]
|
||||||
|
.sort((a, b) => Math.abs(pickQty(b)) - Math.abs(pickQty(a)))
|
||||||
|
.slice(0, TOP_N)
|
||||||
|
|
||||||
|
return ranked.map((item, i) => {
|
||||||
|
const raw = pickQty(item)
|
||||||
|
return {
|
||||||
|
rank: i + 1,
|
||||||
|
name: item.company,
|
||||||
|
qty: opts?.absQty ? Math.abs(raw) : raw,
|
||||||
|
change: pickChange(item),
|
||||||
|
percent: 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将同花顺持仓接口映射为页面用 PositionsData(总持仓 / 成交量 / 净持仓)。
|
||||||
|
* 多空等列表均为非零持仓按数量排序后的前 20。
|
||||||
|
*/
|
||||||
|
export function mapJqkaDealPosition(data: JqkaDealPositionData): PositionsData {
|
||||||
|
const list = data.positionList ?? []
|
||||||
|
const sumLatest = data.topTwentyPositionSum?.[0]
|
||||||
|
|
||||||
|
const long = withPercent(
|
||||||
|
toRows(list, (x) => x.f009n, (x) => x.f013n),
|
||||||
|
sumLatest?.f009nSum,
|
||||||
|
)
|
||||||
|
const short = withPercent(
|
||||||
|
toRows(list, (x) => x.f015n, (x) => x.f019n),
|
||||||
|
sumLatest?.f015nSum,
|
||||||
|
)
|
||||||
|
const volume = withPercent(toRows(list, (x) => x.f003n, (x) => x.f007n))
|
||||||
|
|
||||||
|
const netLong = withPercent(
|
||||||
|
toRows(list, (x) => x.f024n, (x) => x.f025n, {
|
||||||
|
filter: (x) => x.f024n > 0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const netShort = withPercent(
|
||||||
|
toRows(list, (x) => x.f024n, (x) => x.f025n, {
|
||||||
|
absQty: true,
|
||||||
|
filter: (x) => x.f024n < 0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const topTwentySum: TopTwentySum[] = (data.topTwentyPositionSum ?? []).map(
|
||||||
|
(s) => ({
|
||||||
|
tradeDate: s.tradeDate,
|
||||||
|
longSum: s.f009nSum,
|
||||||
|
shortSum: s.f015nSum,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedAt =
|
||||||
|
list[0]?.tradeDate || sumLatest?.tradeDate || new Date().toISOString().slice(0, 10)
|
||||||
|
|
||||||
|
return {
|
||||||
|
long,
|
||||||
|
short,
|
||||||
|
volume,
|
||||||
|
netLong,
|
||||||
|
netShort,
|
||||||
|
topTwentySum,
|
||||||
|
updatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/api/jqka/position.ts
Normal file
44
src/api/jqka/position.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { jqkaHttp } from '../http'
|
||||||
|
import type { JqkaDealPositionData, JqkaDealPositionResponse } from './types'
|
||||||
|
import { extractVariety, getPreviousTradingDate } from '../../utils/tradingDate'
|
||||||
|
|
||||||
|
export interface GetDealPositionParams {
|
||||||
|
/** 合约代码,如 FG609 */
|
||||||
|
contract: string
|
||||||
|
/** 品种代码;缺省时从 contract 字母前缀推导 */
|
||||||
|
variety?: string
|
||||||
|
/**
|
||||||
|
* 查询日期 YYYY-MM-DD。
|
||||||
|
* 缺省为上一交易日(接口一般只能查到上一交易日)。
|
||||||
|
*/
|
||||||
|
date?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同花顺 — 期货会员成交持仓(龙虎榜)
|
||||||
|
* GET /futgwapi/api/market/v1/position/getDealPosition/?date=&variety=&contract=
|
||||||
|
*/
|
||||||
|
export async function getDealPosition(
|
||||||
|
params: GetDealPositionParams,
|
||||||
|
): Promise<JqkaDealPositionData> {
|
||||||
|
const contract = params.contract
|
||||||
|
const variety = params.variety ?? extractVariety(contract)
|
||||||
|
const date = params.date ?? getPreviousTradingDate()
|
||||||
|
|
||||||
|
const { data } = await jqkaHttp.get<JqkaDealPositionResponse>(
|
||||||
|
'/futgwapi/api/market/v1/position/getDealPosition/',
|
||||||
|
{
|
||||||
|
params: { date, variety, contract },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`持仓接口失败: code=${data.code}, msg=${data.msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.data?.positionList) {
|
||||||
|
throw new Error('持仓接口返回空 positionList')
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
43
src/api/jqka/types.ts
Normal file
43
src/api/jqka/types.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/** 同花顺期货网关 — 会员持仓明细行 */
|
||||||
|
export interface JqkaPositionItem {
|
||||||
|
tradeDate: string
|
||||||
|
contract: string
|
||||||
|
variety: string
|
||||||
|
company: string
|
||||||
|
/** 成交量 */
|
||||||
|
f003n: number
|
||||||
|
/** 成交量增减 */
|
||||||
|
f007n: number
|
||||||
|
/** 多单持仓数 */
|
||||||
|
f009n: number
|
||||||
|
/** 多单增减 */
|
||||||
|
f013n: number
|
||||||
|
/** 空单持仓数 */
|
||||||
|
f015n: number
|
||||||
|
/** 空单增减 */
|
||||||
|
f019n: number
|
||||||
|
/** 净持仓数 */
|
||||||
|
f024n: number
|
||||||
|
/** 净持仓增减 */
|
||||||
|
f025n: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 前 20 持仓量汇总(按交易日) */
|
||||||
|
export interface JqkaTopTwentySum {
|
||||||
|
tradeDate: string
|
||||||
|
/** 多单持仓合计 */
|
||||||
|
f009nSum: number
|
||||||
|
/** 空单持仓合计 */
|
||||||
|
f015nSum: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JqkaDealPositionData {
|
||||||
|
positionList: JqkaPositionItem[]
|
||||||
|
topTwentyPositionSum: JqkaTopTwentySum[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JqkaDealPositionResponse {
|
||||||
|
code: number
|
||||||
|
msg: string
|
||||||
|
data: JqkaDealPositionData
|
||||||
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="position-panel card">
|
<section class="position-panel card">
|
||||||
|
<div class="head">
|
||||||
<el-tabs v-model="tab">
|
<el-tabs v-model="tab">
|
||||||
<el-tab-pane label="总持仓" name="total" />
|
<el-tab-pane label="总持仓" name="total" />
|
||||||
<el-tab-pane label="成交量" name="volume" />
|
<el-tab-pane label="成交量" name="volume" />
|
||||||
<el-tab-pane label="净持仓" name="net" />
|
<el-tab-pane label="净持仓" name="net" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<div v-if="tab !== 'total'" class="placeholder-tip">当前为占位数据(结构与总持仓一致)</div>
|
<span v-if="data.updatedAt" class="updated">数据日期 {{ data.updatedAt }}</span>
|
||||||
<div class="cols">
|
</div>
|
||||||
|
|
||||||
|
<!-- 总持仓:多 / 空 -->
|
||||||
|
<div v-if="tab === 'total'" class="cols">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="long-title">多单持仓前20名</h3>
|
<h3 class="long-title">多单持仓前20名</h3>
|
||||||
<PositionDonut :list="data.long" />
|
<PositionDonut :list="data.long" />
|
||||||
@ -18,6 +22,29 @@
|
|||||||
<PositionTable :list="data.short" qty-label="空单" />
|
<PositionTable :list="data.short" qty-label="空单" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 成交量:单列 -->
|
||||||
|
<div v-else-if="tab === 'volume'" class="cols single">
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="volume-title">成交量前20名</h3>
|
||||||
|
<PositionDonut :list="data.volume" />
|
||||||
|
<PositionTable :list="data.volume" qty-label="成交量" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 净持仓:净多 / 净空 -->
|
||||||
|
<div v-else class="cols">
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="long-title">净多持仓前20名</h3>
|
||||||
|
<PositionDonut :list="data.netLong" />
|
||||||
|
<PositionTable :list="data.netLong" qty-label="净持仓" />
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="short-title">净空持仓前20名</h3>
|
||||||
|
<PositionDonut :list="data.netShort" />
|
||||||
|
<PositionTable :list="data.netShort" qty-label="净持仓" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -40,10 +67,23 @@ const tab = ref('total')
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-tip {
|
.head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head :deep(.el-tabs) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updated {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin: -4px 0 8px;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cols {
|
.cols {
|
||||||
@ -52,6 +92,11 @@ const tab = ref('total')
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cols.single {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
.long-title {
|
.long-title {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
color: var(--long);
|
color: var(--long);
|
||||||
@ -64,6 +109,12 @@ const tab = ref('total')
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.volume-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: var(--text-primary, #303133);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
.cols {
|
.cols {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
import { useQuotaStore } from '../stores/quota'
|
import { useQuotaStore } from '../stores/quota'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机构持仓 composable:状态统一落在 quota store(暂 mock)。
|
* 机构持仓 composable:状态统一落在 quota store。
|
||||||
|
* 打开页面时按上一交易日拉取同花顺会员持仓。
|
||||||
*/
|
*/
|
||||||
export function usePositions() {
|
export function usePositions() {
|
||||||
const store = useQuotaStore()
|
const store = useQuotaStore()
|
||||||
@ -12,6 +14,10 @@ export function usePositions() {
|
|||||||
positionsError: error,
|
positionsError: error,
|
||||||
} = storeToRefs(store)
|
} = storeToRefs(store)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void store.fetchPositions()
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
loading,
|
loading,
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
/** 当前关注的期货合约(后续可改为可切换) */
|
/** 当前关注的期货合约(后续可改为可切换) */
|
||||||
export const contractConfig = {
|
export const contractConfig = {
|
||||||
/** 合约代码,对应百度行情接口 code 参数 */
|
/** 合约代码,对应百度行情 / 同花顺持仓接口 code/contract */
|
||||||
code: 'FG609',
|
code: 'FG609',
|
||||||
|
/** 品种代码,持仓接口 variety;缺省可从 code 字母前缀推导 */
|
||||||
|
variety: 'FG',
|
||||||
/** 展示名称;接口未返回 name 时使用 */
|
/** 展示名称;接口未返回 name 时使用 */
|
||||||
name: '玻璃2609',
|
name: '玻璃2609',
|
||||||
/** 交易所展示名 */
|
/** 交易所展示名 */
|
||||||
|
|||||||
@ -68,8 +68,21 @@ const shortNames = [
|
|||||||
'其他',
|
'其他',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const long = withPercent(makeList(longNames, 650000))
|
||||||
|
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))
|
||||||
|
|
||||||
export const positionsMock: PositionsData = {
|
export const positionsMock: PositionsData = {
|
||||||
long: withPercent(makeList(longNames, 650000)),
|
long,
|
||||||
short: withPercent(makeList(shortNames, 640000)),
|
short,
|
||||||
|
volume,
|
||||||
|
netLong,
|
||||||
|
netShort,
|
||||||
|
topTwentySum: [
|
||||||
|
{ tradeDate: '2026-07-15', longSum: 1073446, shortSum: 1502020 },
|
||||||
|
{ tradeDate: '2026-07-14', longSum: 1094199, shortSum: 1519906 },
|
||||||
|
],
|
||||||
updatedAt: '2026-07-15',
|
updatedAt: '2026-07-15',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,12 @@ import { mapBaiduQuotationToQuote } from '../api/baidu/mapQuote'
|
|||||||
import { mapBaiduKlineToCandles } from '../api/baidu/mapKline'
|
import { mapBaiduKlineToCandles } from '../api/baidu/mapKline'
|
||||||
import { mapBaiduNewsToItems } from '../api/baidu/mapNews'
|
import { mapBaiduNewsToItems } from '../api/baidu/mapNews'
|
||||||
import type { BaiduKlineType } from '../api/baidu/types'
|
import type { BaiduKlineType } from '../api/baidu/types'
|
||||||
|
import { getDealPosition } from '../api/jqka/position'
|
||||||
|
import { mapJqkaDealPosition } from '../api/jqka/mapPosition'
|
||||||
|
import {
|
||||||
|
extractVariety,
|
||||||
|
getPreviousTradingDate,
|
||||||
|
} from '../utils/tradingDate'
|
||||||
|
|
||||||
/** K 线周期(日/周/月) */
|
/** K 线周期(日/周/月) */
|
||||||
export type KlinePeriod = 'day' | 'week' | 'month'
|
export type KlinePeriod = 'day' | 'week' | 'month'
|
||||||
@ -55,7 +61,7 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
const newsLoading = ref(false)
|
const newsLoading = ref(false)
|
||||||
const newsError = ref<unknown>(null)
|
const newsError = ref<unknown>(null)
|
||||||
|
|
||||||
// ─── 机构持仓(暂 mock,后续接 API)────────────────────
|
// ─── 机构持仓 ───────────────────────────────────────────
|
||||||
const positions = ref<PositionsData>(structuredClone(positionsMock))
|
const positions = ref<PositionsData>(structuredClone(positionsMock))
|
||||||
const positionsLoading = ref(false)
|
const positionsLoading = ref(false)
|
||||||
const positionsError = ref<unknown>(null)
|
const positionsError = ref<unknown>(null)
|
||||||
@ -121,13 +127,33 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取会员持仓。日期默认上一交易日;若遇节假日空数据则再往前最多试 5 个交易日。
|
||||||
|
*/
|
||||||
async function fetchPositions() {
|
async function fetchPositions() {
|
||||||
positionsLoading.value = true
|
positionsLoading.value = true
|
||||||
positionsError.value = null
|
positionsError.value = null
|
||||||
try {
|
try {
|
||||||
// TODO: 接入真实持仓 API 后替换
|
const contract = contractConfig.code
|
||||||
await new Promise((r) => setTimeout(r, 300))
|
const variety = contractConfig.variety || extractVariety(contract)
|
||||||
positions.value = structuredClone(positionsMock)
|
let date = getPreviousTradingDate()
|
||||||
|
let mapped: PositionsData | null = null
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const raw = await getDealPosition({ contract, variety, date })
|
||||||
|
if (raw.positionList?.length) {
|
||||||
|
mapped = mapJqkaDealPosition(raw)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 空列表:再往前一个交易日(覆盖长假)
|
||||||
|
const d = new Date(`${date}T12:00:00`)
|
||||||
|
date = getPreviousTradingDate(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapped) {
|
||||||
|
throw new Error(`持仓无数据(已回溯至 ${date})`)
|
||||||
|
}
|
||||||
|
positions.value = mapped
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
positionsError.value = e
|
positionsError.value = e
|
||||||
console.error('[quota] 拉取持仓失败', e)
|
console.error('[quota] 拉取持仓失败', e)
|
||||||
|
|||||||
@ -82,9 +82,26 @@ export interface PositionRow {
|
|||||||
percent: number
|
percent: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 前 20 多空持仓汇总(按交易日) */
|
||||||
|
export interface TopTwentySum {
|
||||||
|
tradeDate: string
|
||||||
|
longSum: number
|
||||||
|
shortSum: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface PositionsData {
|
export interface PositionsData {
|
||||||
|
/** 多单持仓前 20(f009n / f013n) */
|
||||||
long: PositionRow[]
|
long: PositionRow[]
|
||||||
|
/** 空单持仓前 20(f015n / f019n) */
|
||||||
short: PositionRow[]
|
short: PositionRow[]
|
||||||
|
/** 成交量前 20(f003n / f007n) */
|
||||||
|
volume: PositionRow[]
|
||||||
|
/** 净多持仓(f024n > 0) */
|
||||||
|
netLong: PositionRow[]
|
||||||
|
/** 净空持仓(f024n < 0,qty 取绝对值) */
|
||||||
|
netShort: PositionRow[]
|
||||||
|
/** 前 20 持仓量汇总 */
|
||||||
|
topTwentySum: TopTwentySum[]
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
src/utils/tradingDate.ts
Normal file
35
src/utils/tradingDate.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 格式化为 YYYY-MM-DD(本地时区)
|
||||||
|
*/
|
||||||
|
export function formatDateYmd(d: Date): string {
|
||||||
|
const y = d.getFullYear()
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWeekend(d: Date): boolean {
|
||||||
|
const day = d.getDay()
|
||||||
|
return day === 0 || day === 6
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上一个交易日(跳过周末;不含法定节假日日历)。
|
||||||
|
* 持仓接口一般只能查到上一交易日数据,故默认用此日期。
|
||||||
|
*/
|
||||||
|
export function getPreviousTradingDate(from: Date = new Date()): string {
|
||||||
|
const d = new Date(from.getFullYear(), from.getMonth(), from.getDate())
|
||||||
|
d.setDate(d.getDate() - 1)
|
||||||
|
while (isWeekend(d)) {
|
||||||
|
d.setDate(d.getDate() - 1)
|
||||||
|
}
|
||||||
|
return formatDateYmd(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从合约代码提取品种,如 FG609 → FG、au2512 → au
|
||||||
|
*/
|
||||||
|
export function extractVariety(contract: string): string {
|
||||||
|
const m = contract.match(/^([A-Za-z]+)/)
|
||||||
|
return m ? m[1] : contract
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user