import { Injectable } from '@nestjs/common'; import { MoexHttpClient } from './moex-http.client'; import { MoexShareMarketData, MoexBondData, MoexBondMarketData, MoexBondPositionData, } from './moex-client.types'; @Injectable() export class MoexMarketDataClient { constructor(private readonly http: MoexHttpClient) {} async getShareMarketData(secid: string, boardId = 'TQBR'): Promise { const data = await this.http.request>( `/engines/stock/markets/shares/securities/${secid}`, { boards: boardId }, ); const rows = this.http.extractTable(data, 'securities'); const share = rows.find((r) => r.BOARDID === boardId); if (!share) return null; const mktRows = this.http.extractTable(data, 'marketdata'); const mkt = mktRows.find((r) => r.BOARDID === boardId); return { secid, boardid: boardId, shortName: (share?.SHORTNAME as string) || '', bid: mkt ? parseFloat((mkt.BID as string) || '') : null, offer: mkt ? parseFloat((mkt.OFFER as string) || '') : null, open: mkt ? parseFloat((mkt.OPEN as string) || '') : null, low: mkt ? parseFloat((mkt.LOW as string) || '') : null, high: mkt ? parseFloat((mkt.HIGH as string) || '') : null, last: mkt ? parseFloat((mkt.LAST as string) || '') : parseFloat((share.PREVPRICE as string) || ''), lastChange: mkt ? parseFloat((mkt.LASTCHANGE as string) || '') : null, lastChangePrcnt: mkt ? parseFloat((mkt.LASTCHANGEPRCNT as string) || '') : null, volume: mkt ? parseInt((mkt.VOLTODAY as string) || '0', 10) : 0, value: mkt ? parseFloat((mkt.VALTODAY as string) || '0') : 0, waprice: mkt ? parseFloat((mkt.WAPRICE as string) || '') : null, numtrades: mkt ? parseInt((mkt.NUMTRADES as string) || '0', 10) : 0, issueCapitalization: mkt ? parseFloat((mkt.ISSUECAPITALIZATION as string) || '') : null, tradingStatus: (mkt?.TRADINGSTATUS as string) || '', updateTime: (mkt?.UPDATETIME as string) || '', }; } async getShareMarketDataBatch( secids: string[], boardId = 'TQBR', ): Promise { const params: Record = { boards: boardId }; if (secids.length > 0) { params.securities = secids.join(','); } const data = await this.http.request>( `/engines/stock/markets/shares/securities`, params, ); const securities = this.http.extractTable(data, 'securities'); const marketdata = this.http.extractTable(data, 'marketdata'); const secidSet = secids.length > 0 ? new Set(secids) : null; const filteredSecurities = secidSet ? securities.filter((r) => secidSet.has(r.SECID as string)) : securities; return filteredSecurities.map((sec) => { const secid = sec.SECID as string; const mkt = marketdata.find((r) => r.SECID === secid && r.BOARDID === boardId) || marketdata.find((r) => r.SECID === secid); return { secid, boardid: boardId, shortName: (sec?.SHORTNAME as string) || '', bid: mkt ? parseFloat((mkt.BID as string) || '') : null, offer: mkt ? parseFloat((mkt.OFFER as string) || '') : null, open: mkt ? parseFloat((mkt.OPEN as string) || '') : null, low: mkt ? parseFloat((mkt.LOW as string) || '') : null, high: mkt ? parseFloat((mkt.HIGH as string) || '') : null, last: mkt ? parseFloat((mkt.LAST as string) || '') : parseFloat((sec?.PREVPRICE as string) || ''), lastChange: mkt ? parseFloat((mkt.LASTCHANGE as string) || '') : null, lastChangePrcnt: mkt ? parseFloat((mkt.LASTCHANGEPRCNT as string) || '') : null, volume: mkt ? parseInt((mkt.VOLTODAY as string) || '0', 10) : 0, value: mkt ? parseFloat((mkt.VALTODAY as string) || '0') : 0, waprice: mkt ? parseFloat((mkt.WAPRICE as string) || '') : null, numtrades: mkt ? parseInt((mkt.NUMTRADES as string) || '0', 10) : 0, issueCapitalization: mkt ? parseFloat((mkt.ISSUECAPITALIZATION as string) || '') : null, tradingStatus: (mkt?.TRADINGSTATUS as string) || '', updateTime: (mkt?.UPDATETIME as string) || '', }; }); } async getBondData(secid: string, boardId = 'TQCB'): Promise { const data = await this.http.request>( `/engines/stock/markets/bonds/securities/${secid}`, { boards: boardId }, ); const rows = this.http.extractTable(data, 'securities'); const bond = rows.find((r) => r.BOARDID === boardId && r.PREVWAPRICE != null) || rows.find((r) => r.PREVWAPRICE != null) || rows[0]; if (!bond) return null; return { secid, boardid: boardId, shortName: (bond.SHORTNAME as string) || '', prevWaprice: parseFloat((bond.PREVWAPRICE as string) || '') || null, yieldAtPrevWaprice: parseFloat((bond.YIELDATPREVWAPRICE as string) || '') || null, couponValue: bond.COUPONVALUE != null ? parseFloat(bond.COUPONVALUE as string) : null, nextCoupon: (bond.NEXTCOUPON as string) || null, accruedInt: bond.ACCRUEDINT != null ? parseFloat(bond.ACCRUEDINT as string) : null, prevPrice: parseFloat((bond.PREVPRICE as string) || '') || null, lotSize: parseInt((bond.LOTSIZE as string) || '1', 10), faceValue: parseFloat((bond.FACEVALUE as string) || '1000'), matDate: (bond.MATDATE as string) || '', couponPeriod: parseInt((bond.COUPONPERIOD as string) || '0', 10), issueSize: parseInt((bond.ISSUESIZE as string) || '0', 10), isin: (bond.ISIN as string) || '', couponPercent: bond.COUPONPERCENT != null ? parseFloat(bond.COUPONPERCENT as string) : null, offerDate: (bond.OFFERDATE as string) || null, buybackDate: (bond.BUYBACKDATE as string) || null, bondType: (bond.BONDTYPE as string) || '', bondSubType: (bond.BONDSUBTYPE as string) || '', listLevel: parseInt((bond.LISTLEVEL as string) || '0', 10), }; } async getBondMarketData(secid: string, boardId = 'TQCB'): Promise { const data = await this.http.request>( `/engines/stock/markets/bonds/securities/${secid}`, { boards: boardId }, ); const mktRows = this.http.extractTable(data, 'marketdata'); const mkt = mktRows.find((r) => r.BOARDID === boardId && r.LAST != null) || mktRows.find((r) => r.LAST != null) || mktRows.find((r) => r.SECID === secid); if (!mkt) return null; return { secid, bid: mkt.BID != null ? parseFloat(mkt.BID as string) : null, offer: mkt.OFFER != null ? parseFloat(mkt.OFFER as string) : null, open: mkt.OPEN != null ? parseFloat(mkt.OPEN as string) : null, low: mkt.LOW != null ? parseFloat(mkt.LOW as string) : null, high: mkt.HIGH != null ? parseFloat(mkt.HIGH as string) : null, last: mkt.LAST != null ? parseFloat(mkt.LAST as string) : null, yield: mkt.YIELD != null ? parseFloat(mkt.YIELD as string) : null, waprice: mkt.WAPRICE != null ? parseFloat(mkt.WAPRICE as string) : null, yieldAtWaprice: mkt.YIELDATWAPRICE != null ? parseFloat(mkt.YIELDATWAPRICE as string) : null, duration: mkt.DURATION != null ? parseFloat(mkt.DURATION as string) : null, volume: parseInt((mkt.VOLTODAY as string) || '0', 10), value: parseFloat((mkt.VALTODAY as string) || '0'), numtrades: parseInt((mkt.NUMTRADES as string) || '0', 10), tradingStatus: (mkt.TRADINGSTATUS as string) || '', updateTime: (mkt.UPDATETIME as string) || '', }; } async getBondPositionDataBatch( secids: string[], boardId = 'TQCB', ): Promise { const params: Record = { boards: boardId }; if (secids.length > 0) { params.securities = secids.join(','); } const data = await this.http.request>( `/engines/stock/markets/bonds/securities`, params, ); const securities = this.http.extractTable(data, 'securities'); const marketdata = this.http.extractTable(data, 'marketdata'); const secidSet = secids.length > 0 ? new Set(secids) : null; const filteredSecurities = secidSet ? securities.filter((r) => secidSet.has(r.SECID as string)) : securities; return filteredSecurities.map((bond) => { const secid = bond.SECID as string; const mkt = marketdata.find((r) => r.SECID === secid && r.BOARDID === boardId && r.LAST != null) || marketdata.find((r) => r.SECID === secid && r.LAST != null) || marketdata.find((r) => r.SECID === secid); return { secid, boardid: (bond.BOARDID as string) || boardId, shortName: (bond?.SHORTNAME as string) || '', price: mkt?.LAST != null ? parseFloat(mkt.LAST as string) : null, yieldToMaturity: mkt?.YIELD != null ? parseFloat(mkt.YIELD as string) : null, duration: mkt?.DURATION != null ? parseFloat(mkt.DURATION as string) : null, couponValue: bond?.COUPONVALUE != null ? parseFloat(bond.COUPONVALUE as string) : null, couponPercent: bond?.COUPONPERCENT != null ? parseFloat(bond.COUPONPERCENT as string) : null, nextCouponDate: (bond?.NEXTCOUPON as string) || null, matDate: (bond?.MATDATE as string) || null, accruedInt: bond?.ACCRUEDINT != null ? parseFloat(bond.ACCRUEDINT as string) : null, faceValue: parseFloat((bond?.FACEVALUE as string) || '1000'), bid: mkt?.BID != null ? parseFloat(mkt.BID as string) : null, offer: mkt?.OFFER != null ? parseFloat(mkt.OFFER as string) : null, couponPeriod: parseInt((bond?.COUPONPERIOD as string) || '0', 10), bondType: (bond?.BONDTYPE as string) || null, offerDate: (bond?.OFFERDATE as string) || null, }; }); } }