| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- import { shallowRef, reactive, computed, toRefs, onUnmounted } from 'vue'
- import { v4 } from 'uuid'
- import { timerTask } from '@/utils/timer'
- import { handlePriceColor } from '@/filters'
- import { queryMemberGoodsLimitConfig } from '@/services/api/common'
- import { getTodayAccountConfigInfo } from '@/services/api/account'
- import { queryErmcpGoods, queryQuoteDay } from '@/services/api/goods'
- import { wordArrayToUint8Array } from '@/services/websocket/package/crypto'
- import { decodeProto } from '@/services/websocket/package/package50/proto'
- import { defineStore } from '../store'
- import { useAccountStore } from './account'
- import CryptoJS from 'crypto-js'
- import eventBus from '@/services/bus'
- import moment from 'moment'
- /**
- * 期货存储对象
- * @returns
- */
- export const useFuturesStore = defineStore(() => {
- // 行情监听集合
- const quoteWatchMap = new Map<string, { keys: string[]; callback: (value: Partial<Model.QuoteDayRsp>) => void; }>()
- // 当前市场ID
- const marketIds = shallowRef<number[]>([])
- // 请求商品列表成功后的回调集合
- // 由于异步请求原因,页面发起行情订阅时商品数据可能还不存在
- // 将回调事件收集到合集中,待请求成功后再执行
- const dataCompletedCallback = new Set<() => void>()
- const state = reactive({
- loading: false,
- goodsList: <(Model.GoodsRsp & Partial<Model.MemberGoodsLimitConfigRsp>)[]>[], // 商品列表
- quotationList: <Model.GoodsQuote[]>[], // 行情列表
- selectedGoodsId: <number | undefined>undefined, // 当前选中的商品ID
- })
- // 指定市场ID的商品列表
- const marketGoodsList = computed(() => {
- const list = state.quotationList.filter((e) => marketIds.value.includes(e.marketid))
- return list.sort((a, b) => a.goodscode.localeCompare(b.goodscode)) // 根据 goodscode 字母升序排序
- })
- // 当前选中的商品
- const selectedGoods = computed(() => state.quotationList.find((e) => e.goodsid === state.selectedGoodsId))
- // 设置当前市场ID
- const setMarketId = (...values: number[]) => {
- marketIds.value = values
- const list = marketGoodsList.value
- if (list.length) {
- // 查找当前选中的商品ID
- if (!list.every((e) => e.goodsid === state.selectedGoodsId)) {
- state.selectedGoodsId = list[0].goodsid
- }
- } else {
- state.selectedGoodsId = undefined
- }
- }
- const getGoodsListByMarketId = (...marketId: number[]) => {
- const list = state.quotationList.filter((e) => marketId.includes(e.marketid))
- return list.sort((a, b) => a.goodscode.localeCompare(b.goodscode)) // 根据 goodscode 字母升序排序
- }
- const getGoodsListByTradeMode = (...tradeMode: number[]) => {
- const list = state.quotationList.filter((e) => tradeMode.includes(e.trademode))
- return list.sort((a, b) => a.goodscode.localeCompare(b.goodscode)) // 根据 goodscode 字母升序排序
- }
- // 获取商品列表
- const getGoodsList = async () => {
- state.loading = true
- state.goodsList = []
- state.quotationList = []
- timerTask.clearTimeout('quoteDay')
- const accountStore = useAccountStore()
- // 任务 #5511
- const { data: accountConfig } = await getTodayAccountConfigInfo()
- // 任务 #5197
- const { data: limitConfig } = await queryMemberGoodsLimitConfig({
- data: {
- roletype: 7
- }
- })
- const { data: goodsList } = await queryErmcpGoods()
- for (let i = 0; i < goodsList.length; i++) {
- const item = goodsList[i]
- const limit = limitConfig.find((e) => e.goodsid === item.goodsid)
- // 跳过不显示的商品
- if (limit?.isnodisplay) {
- continue
- }
- // 更新商品配置
- const findAccountConfig = accountConfig.todayAccountMargins.find((e) => e.accountid === accountStore.currentAccountId && e.goodsid === item.goodsid)
- const config = findAccountConfig ?? accountConfig.todayAccountMargins.find((e) => e.accountid === 0 && e.goodsid === item.goodsid)
- if (config) {
- const wordArray = CryptoJS.enc.Base64.parse(config.infocontent) // 解析base64
- const uint8Array = wordArrayToUint8Array(wordArray)
- const res = await decodeProto<Proto.MarginInfoStruct>('MarginInfoStruct', uint8Array) // proto数据解析
- item.marketmarginalgorithm = res.MarginAlgorithm
- item.marketmarginvalue = res.MarketMarginValue
- }
- // 组合商品属性
- state.goodsList.push({
- ...item,
- iscannotbuy: limit?.iscannotbuy ?? 0,
- iscannotsell: limit?.iscannotsell ?? 0
- })
- }
- // 待优化
- getQuoteDay().then(() => {
- dataCompletedCallback.forEach((callback) => callback())
- dataCompletedCallback.clear()
- }).finally(() => {
- state.loading = false
- if (!state.selectedGoodsId) {
- state.selectedGoodsId = marketGoodsList.value[0]?.goodsid
- }
- // 每5分钟获取一次盘面
- timerTask.setTimeout(() => getQuoteDay(), 5 * 60 * 1000, 'quoteDay')
- })
- }
- // 获取商品盘面信息(待优化)
- const getQuoteDay = async () => {
- state.loading = true
- const values = state.goodsList.map((e) => e.goodscode.toString())
- const res = await queryQuoteDay({
- data: {
- goodsCodes: values.join(',')
- }
- })
- // 只遍历有盘面的商品
- // res.data.forEach((item) => {
- // updateQuotation(item)
- // })
- // 遍历所有商品
- values.forEach((goodscode) => {
- const item = res.data.find((e_1) => e_1.goodscode === goodscode)
- updateQuotation(item ?? { goodscode })
- })
- state.loading = false
- }
- // 盘面数据加载完成后触发
- const onDataCompleted = (callback: () => void) => {
- if (state.quotationList.length) {
- setTimeout(() => callback(), 0)
- } else {
- dataCompletedCallback.add(callback)
- onUnmounted(() => dataCompletedCallback.delete(callback))
- }
- }
- // 通过 goodscode 获取实时盘面
- const getGoodsQuote = (code: string | number) => {
- return computed(() => state.quotationList.find((e) => e.goodscode === code || e.goodsid === code))
- }
- // 通过 goodscode 获取实时行情报价
- const getQuotePrice = (goodsCode: string) => {
- return computed(() => {
- const quote = getGoodsQuote(goodsCode)
- const { last = 0, presettle = 0, preclose = 0 } = quote.value ?? {}
- if (last != 0.0) {
- return last
- } else if (presettle != 0.0) {
- return presettle
- } else if (preclose != 0.0) {
- return preclose
- } else {
- return 0.0
- }
- })
- }
- // 获取商品名称
- const getGoodsName = (code: string | number) => {
- const goods = getGoods(code)
- return goods?.goodsname ?? ''
- }
- // 获取对应的商品
- const getGoods = (code: string | number) => {
- const goods = state.goodsList.find((e) => e.goodscode === code || e.goodsid === code)
- return goods
- }
- // 获取商品市场ID
- const getGoodsMarket = (code: string | number) => {
- const quote = state.goodsList.find((e) => e.goodscode === code || e.goodsid === code)
- return quote?.marketid ?? 0
- }
- // 监听行情推送
- const quoteWatch = (goodsCodes: string | string[], callback: (value: Partial<Model.QuoteDayRsp>) => void) => {
- const uuid = v4()
- quoteWatchMap.set(uuid, {
- keys: Array.isArray(goodsCodes) ? goodsCodes : [goodsCodes],
- callback
- })
- const append = (...goodsCodes: string[]) => {
- const value = quoteWatchMap.get(uuid)
- value?.keys.push(...goodsCodes)
- }
- const stop = () => {
- quoteWatchMap.delete(uuid)
- }
- // 页面离开时停止监听事件,防止事件重复触发
- onUnmounted(() => stop())
- return {
- uuid,
- append,
- stop
- }
- }
- // 更新行情数据
- const updateQuotation = (quote: Partial<Model.QuoteDayRsp>) => {
- // 查找对应的商品行情
- const item: Model.GoodsQuote = state.quotationList.find((e) => e.goodscode === quote.goodscode) ?? {
- goodsid: 0,
- goodscode: quote.goodscode ?? '',
- goodsname: '',
- goodsgroupid: 0,
- goodunitid: 0,
- marketid: 0,
- trademode: 0,
- agreeunit: 0,
- decimalplace: 0,
- decimalvalue: 0,
- quoteminunit: 0,
- quotegear: 0,
- last: quote.last ?? 0,
- lasttime: quote.lasttime ?? '',
- bid: quote.bid ?? 0,
- bid2: quote.bid2 ?? 0,
- bid3: quote.bid3 ?? 0,
- bid4: quote.bid4 ?? 0,
- bid5: quote.bid5 ?? 0,
- bidvolume: quote.bidvolume ?? 0,
- bidvolume2: quote.bidvolume2 ?? 0,
- bidvolume3: quote.bidvolume3 ?? 0,
- bidvolume4: quote.bidvolume4 ?? 0,
- bidvolume5: quote.bidvolume5 ?? 0,
- ask: quote.ask ?? 0,
- ask2: quote.ask2 ?? 0,
- ask3: quote.ask3 ?? 0,
- ask4: quote.ask4 ?? 0,
- ask5: quote.ask5 ?? 0,
- askvolume: quote.askvolume ?? 0,
- askvolume2: quote.askvolume2 ?? 0,
- askvolume3: quote.askvolume3 ?? 0,
- askvolume4: quote.askvolume4 ?? 0,
- askvolume5: quote.askvolume5 ?? 0,
- lastvolume: quote.lastvolume ?? 0,
- lastturnover: quote.Lastturnover ?? 0,
- presettle: quote.presettle ?? 0,
- preclose: quote.preclose ?? 0,
- opened: quote.opened ?? 0,
- highest: quote.highest ?? 0,
- lowest: quote.lowest ?? 0,
- limitup: quote.limitup ?? 0,
- limitdown: quote.limitdown ?? 0,
- averageprice: quote.averageprice ?? 0,
- totalvolume: quote.totalvolume ?? 0,
- totalturnover: quote.totalturnover ?? 0,
- holdvolume: quote.holdvolume ?? 0,
- provideraccountid: 0,
- currencyid: 1,
- provideruserid: 0,
- marketmarginalgorithm: 0,
- marketmarginvalue: 0,
- goodstradetype: 0,
- maxspread: 0,
- minspread: 0,
- goodsorder: '',
- tradeproperty: 0,
- rise: 0,
- change: 0,
- amplitude: 0,
- iscannotbuy: 0,
- iscannotsell: 0,
- bidColor: '',
- bid2Color: '',
- bid3Color: '',
- bid4Color: '',
- bid5Color: '',
- askColor: '',
- ask2Color: '',
- ask3Color: '',
- ask4Color: '',
- ask5Color: '',
- lastColor: '',
- averagepriceColor: '',
- openedColor: '',
- highestColor: '',
- lowestColor: '',
- }
- if (item.goodsid) {
- // 更新对象属性
- Object.entries(quote).forEach(([key, value]) => {
- if (value !== undefined) {
- type TKey = keyof Model.GoodsQuote
- (<K extends TKey>(prop: K, value: Model.GoodsQuote[K]) => {
- item[prop] = value
- })(key as TKey, value)
- }
- })
- } else {
- const goods = state.goodsList.find((e) => e.goodscode === quote.goodscode)
- if (goods) {
- ({
- goodsid: item.goodsid,
- goodsname: item.goodsname,
- goodsgroupid: item.goodsgroupid,
- goodunitid: item.goodunitid,
- marketid: item.marketid,
- trademode: item.trademode,
- agreeunit: item.agreeunit,
- decimalplace: item.decimalplace,
- quoteminunit: item.quoteminunit,
- quotegear: item.quotegear,
- marketmarginalgorithm: item.marketmarginalgorithm,
- marketmarginvalue: item.marketmarginvalue,
- maxspread: item.maxspread,
- minspread: item.minspread,
- goodsorder: item.goodsorder,
- tradeproperty: item.tradeproperty,
- provideraccountid: item.provideraccountid,
- provideruserid: item.provideruserid,
- goodstradetype: item.goodstradetype,
- currencyid: item.currencyid
- } = goods)
- item.iscannotbuy = goods.iscannotbuy ?? 0
- item.iscannotsell = goods.iscannotsell ?? 0
- // 向列表添加新数据
- state.quotationList.push(item)
- }
- }
- const price = item.trademode === 52 ? Math.max(item.last, item.ask) : item.last // 任务 #5677
- item.opened = item.opened || item.last // 没有开盘价默认取最新价
- item.highest = item.highest || price // 没有最高价默认取最新价
- item.lowest = item.lowest || item.last // 没有最低价价默认取最新价
- // 处理最高最低价
- if (item.last) {
- if (price > item.highest) {
- item.highest = price
- }
- if (item.last < item.lowest) {
- item.lowest = item.last
- }
- }
- item.averageprice = item.totalvolume ? item.totalturnover / (item.totalvolume * item.agreeunit) : 0 // 计算均价
- item.rise = item.last ? item.last - item.presettle : 0 // 涨跌额/涨跌: 最新价 - 昨结价
- item.change = item.presettle ? item.rise / item.presettle : 0 // 涨跌幅/幅度: (最新价 - 昨结价) / 昨结价
- item.amplitude = item.presettle ? (item.highest - item.lowest) / item.presettle : 0 // 振幅: (最高价 - 最低价 ) / 最新价
- // 计算小数单位值
- item.decimalvalue = item.quoteminunit * Math.pow(10, item.decimalplace * -1)
- // 处理行情价格颜色
- const handleColor = (value: number | string) => handlePriceColor(Number(value), item.presettle)
- item.bidColor = handleColor(item.bid)
- item.bid2Color = handleColor(item.bid2)
- item.bid3Color = handleColor(item.bid3)
- item.bid4Color = handleColor(item.bid4)
- item.bid5Color = handleColor(item.bid5)
- item.askColor = handleColor(item.ask)
- item.ask2Color = handleColor(item.ask2)
- item.ask3Color = handleColor(item.ask3)
- item.ask4Color = handleColor(item.ask4)
- item.ask5Color = handleColor(item.ask5)
- item.lastColor = handleColor(item.last)
- item.averagepriceColor = handleColor(item.averageprice.toFixed(item.decimalplace))
- item.openedColor = handleColor(item.opened)
- item.highestColor = handleColor(item.highest)
- item.lowestColor = handleColor(item.lowest)
- }
- // 处理行情数据
- const handleQuotation = (quote: Proto.Quote): Partial<Model.QuoteDayRsp> => {
- const goods = state.goodsList.find((e) => e.goodscode.toUpperCase() === quote.goodscode?.toUpperCase())
- // 处理报价小数位
- const handleDeimalplace = (value?: number) => {
- if (goods && value) {
- const decimal = Math.pow(10, goods.decimalplace)
- return value / decimal
- }
- return value
- }
- return {
- goodscode: quote.goodscode,
- last: handleDeimalplace(quote.last),
- lasttime: (quote.date && quote.time) ? moment(quote.date + quote.time, 'YYYYMMDDHHmmss').toISOString(true) : undefined,
- ask: handleDeimalplace(quote.ask),
- ask2: handleDeimalplace(quote.ask2),
- ask3: handleDeimalplace(quote.ask3),
- ask4: handleDeimalplace(quote.ask4),
- ask5: handleDeimalplace(quote.ask5),
- askvolume: quote.askvolume,
- askvolume2: quote.askvolume2,
- askvolume3: quote.askvolume3,
- askvolume4: quote.askvolume4,
- askvolume5: quote.askvolume5,
- bid: handleDeimalplace(quote.bid),
- bid2: handleDeimalplace(quote.bid2),
- bid3: handleDeimalplace(quote.bid3),
- bid4: handleDeimalplace(quote.bid4),
- bid5: handleDeimalplace(quote.bid5),
- bidvolume: quote.bidvolume,
- bidvolume2: quote.bidvolume2,
- bidvolume3: quote.bidvolume3,
- bidvolume4: quote.bidvolume4,
- bidvolume5: quote.bidvolume5,
- calloptionpremiums: quote.calloptionpremiums,
- calloptionpremiums2: quote.calloptionpremiums2,
- calloptionpremiums3: quote.calloptionpremiums3,
- calloptionpremiums4: quote.calloptionpremiums4,
- calloptionpremiums5: quote.calloptionpremiums5,
- exchangecode: quote.exchangecode,
- exchangedate: quote.exchangedate,
- highest: handleDeimalplace(quote.highest),
- holdvolume: quote.holdvolume,
- inventory: quote.inventory,
- lastvolume: quote.lastvolume,
- Lastturnover: handleDeimalplace(quote.lastturnover),
- limitdown: handleDeimalplace(quote.limitlow),
- limitup: handleDeimalplace(quote.limithigh),
- lowest: handleDeimalplace(quote.lowest),
- opened: handleDeimalplace(quote.opened),
- preclose: handleDeimalplace(quote.preclose),
- preholdvolume: quote.preholdvolume,
- presettle: handleDeimalplace(quote.presettle),
- putoptionpremiums: handleDeimalplace(quote.putoptionpremiums),
- putoptionpremiums2: handleDeimalplace(quote.putoptionpremiums2),
- putoptionpremiums3: handleDeimalplace(quote.putoptionpremiums3),
- putoptionpremiums4: handleDeimalplace(quote.putoptionpremiums4),
- putoptionpremiums5: handleDeimalplace(quote.putoptionpremiums5),
- settle: handleDeimalplace(quote.settle),
- totalturnover: handleDeimalplace(quote.totalturnover),
- totalvolume: quote.totalvolume,
- }
- }
- // 接收行情推送通知
- const quotePushNotify = eventBus.$on('QuotePushNotify', (res) => {
- const data = res as Proto.Quote[]
- data.forEach((item) => {
- const quote = handleQuotation(item)
- if (!state.loading) {
- updateQuotation(quote)
- }
- // 触发行情监听事件
- for (const e of quoteWatchMap.values()) {
- if (e.keys.includes(quote.goodscode ?? '')) {
- e.callback(quote)
- }
- }
- })
- })
- return {
- ...toRefs(state),
- marketGoodsList,
- selectedGoods,
- onDataCompleted,
- setMarketId,
- getQuoteDay,
- getQuotePrice,
- getGoodsList,
- getGoodsQuote,
- getGoodsName,
- getGoodsMarket,
- getGoodsListByMarketId,
- getGoodsListByTradeMode,
- updateQuotation,
- quoteWatch,
- quotePushNotify,
- getGoods
- }
- })
|