| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881 |
- package quote
- import (
- "encoding/json"
- "fmt"
- "mtp2_if/global/app"
- "mtp2_if/global/e"
- "mtp2_if/logger"
- "mtp2_if/models"
- "mtp2_if/mtpcache"
- "mtp2_if/utils"
- "net/http"
- "sort"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- )
- // HistoryData 历史数据
- type HistoryData struct {
- Opened float64 `json:"o"` // 开盘价
- Highest float64 `json:"h"` // 最高价
- Lowest float64 `json:"l"` // 最低价
- Closed float64 `json:"c"` // 收盘价
- TotleVolume int `json:"tv"` // 总量(成交量)
- TotleTurnover float64 `json:"tt"` // 总金额
- HoldVolume int `json:"hv"` // 持仓量
- Settle float64 `json:"s"` // 结算价,日线周期(包括)以上才有
- TimeStamp time.Time `json:"ts"` // 时间
- IsFill bool `json:"f"` // 是否补充数据
- }
- // QueryHistoryDatasReq 查询行情历史数据请求参数
- type QueryHistoryDatasReq struct {
- CycleType int `form:"cycleType" binding:"required"`
- GoodsCode string `form:"goodsCode" binding:"required"`
- StartTime string `form:"startTime"`
- EndTime string `form:"endTime"`
- Count int `form:"count"`
- IsAsc bool `form:"isAsc"`
- }
- // QueryHistoryDatas 查询行情历史数据
- // @Summary 查询行情历史数据
- // @Produce json
- // @Security ApiKeyAuth
- // @Param cycleType query int true "周期类型, 0-秒 1: 1分钟 2: 5分钟 3: 30分钟 4: 60分钟 120: 2小时 240: 4小时 11: 日线 12:周线 13:月线 14:年线"
- // @Param goodsCode query string true "商品代码"
- // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
- // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
- // @Param count query int false "条数"
- // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
- // @Success 200 {object} HistoryData
- // @Failure 500 {object} app.Response
- // @Router /Quote/QueryHistoryDatas [get]
- // @Tags 行情服务
- func QueryHistoryDatas(c *gin.Context) {
- appG := app.Gin{C: c}
- // 获取请求参数
- var req QueryHistoryDatasReq
- if err := appG.C.ShouldBindQuery(&req); err != nil {
- logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
- return
- }
- // 转换时间
- timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
- var startTime *time.Time
- if len(req.StartTime) > 0 {
- st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
- return
- }
- startTime = &st
- }
- var endTime *time.Time
- if len(req.EndTime) > 0 {
- et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
- return
- }
- endTime = &et
- }
- // 查询数据
- cycleDatas, err := models.GetHistoryCycleDatas(models.CycleType(req.CycleType), req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- // 获取目标商品报价小数位
- var dcplace int = 0
- if len(cycleDatas) > 0 {
- dcplace = mtpcache.GetGoodsDecimalplace(req.GoodsCode)
- }
- // 计算最终价格
- rst := make([]HistoryData, 0)
- for _, v := range cycleDatas {
- historyData := HistoryData{
- Opened: utils.IntToFloat64(v.Open, dcplace),
- Highest: utils.IntToFloat64(v.High, dcplace),
- Lowest: utils.IntToFloat64(v.Low, dcplace),
- Closed: utils.IntToFloat64(v.Close, dcplace),
- TotleVolume: v.TV,
- TotleTurnover: float64(v.TT),
- HoldVolume: v.HV,
- Settle: utils.IntToFloat64(v.SP, dcplace),
- TimeStamp: time.Unix(int64(v.ST), 0),
- }
- rst = append(rst, historyData)
- }
- // 包括日线以上的周期,如果没有带当前周期则通过盘面数据生成当前周期数据
- if req.CycleType >= 11 {
- logger.GetLogger().Debugf("[checkTiks]%v---------开始确定是否要补K线-----------", req.GoodsCode)
- // 获取商品信息
- goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
- if goods == nil {
- logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
- return
- }
- // 获取市场
- market, err := models.GetMarketByGoodsCode(req.GoodsCode)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- if market == nil {
- logger.GetLogger().Error("QueryHistoryDatas failed: not found goodscode")
- appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
- return
- }
- logger.GetLogger().Debugf("[checkTiks] goodscode[%v] trademode[%v]", req.GoodsCode, market.Trademode)
- // 获取目标品种交易日
- // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
- // 故通道交易的品种目前只能在交易系统的外部市场中获
- // 取统一的交易日,后期应要求服务端同步外部数据
- // 2021.06.30 业务更新:15、97和99市场的行情交易日要从商品盘面中获取
- quoteTradeDate := ""
- if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
- if quoteDays, err := models.GetQuoteDays("'" + req.GoodsCode + "'"); err == nil {
- if len(quoteDays) > 0 {
- quoteTradeDate = time.Unix(quoteDays[0].Exchangedate, 0).Format("20060102")
- logger.GetLogger().Debugf("[checkTiks] get quoteTradeDate[%v]", quoteTradeDate)
- }
- }
- } else {
- if marketRun, err := models.GetMarketRun(int(market.Marketid)); err == nil {
- quoteTradeDate = marketRun.Tradedate2
- logger.GetLogger().Debugf("[checkTiks] get quoteTradeDate[%v] = marketRun.Tradedate2", quoteTradeDate)
- }
- }
- if quoteTradeDate != "" {
- if tradeDate, err := time.ParseInLocation("20060102", quoteTradeDate, time.Local); err == nil {
- var fisrtDate time.Time
- switch req.CycleType {
- case 12: // 周
- fisrtDate = getFirstDateOfWeek(tradeDate)
- case 13: // 月
- fisrtDate = getFirstDateOfMonth(tradeDate)
- case 14: // 年
- fisrtDate = getFirstDateOfYear(tradeDate)
- default: // 日
- fisrtDate = tradeDate
- }
- logger.GetLogger().Debugf("[checkTiks] get firstDate[%v] CycleType[%v]", fisrtDate, req.CycleType)
- // 获取盘面数据
- if quoteDays, err := models.GetQuoteDays(req.GoodsCode); err == nil && len(quoteDays) > 0 {
- v := quoteDays[0]
- if len(rst) == 0 {
- logger.GetLogger().Debugf("[checkTiks] no tiks, add a tik:%v", v)
- // 历史数据当前没数据则直接加当前盘面数据
- historyData := HistoryData{
- Opened: utils.IntToFloat64(int(v.Opened), dcplace),
- Highest: utils.IntToFloat64(int(v.Highest), dcplace),
- Lowest: utils.IntToFloat64(int(v.Lowest), dcplace),
- Closed: utils.IntToFloat64(int(v.Last), dcplace),
- TotleVolume: int(v.Totalvolume),
- TotleTurnover: float64(v.Totalturnover),
- HoldVolume: int(v.Holdvolume),
- Settle: utils.IntToFloat64(int(v.Settle), dcplace),
- TimeStamp: fisrtDate,
- }
- if req.IsAsc {
- rst = append(rst, historyData)
- } else {
- // 插入第一条
- rear := append([]HistoryData{}, rst[0:]...)
- rst = append(append(rst[:0], historyData), rear...)
- }
- } else {
- // 判断最后周期是否已经存在盘面周期
- hd := rst[len(rst)-1]
- logger.GetLogger().Debugf("[checkTiks] firstTik TimeStamp[%v] lastTik TimeStamp[%v] IsAsc[%v]",
- rst[0].TimeStamp, hd.TimeStamp, req.IsAsc)
- logger.GetLogger().Debugf("[checkTiks] compare hd.TimeStamp[%v] > firstDate[%v]? ",
- hd.TimeStamp, fisrtDate)
- if hd.TimeStamp.Before(fisrtDate) {
- // #3424 当日未开市显示了K线
- // 修改:这里增加判断盘面是否有开盘价
- if v.Opened > 0 {
- historyData := HistoryData{
- Opened: utils.IntToFloat64(int(v.Opened), dcplace),
- Highest: utils.IntToFloat64(int(v.Highest), dcplace),
- Lowest: utils.IntToFloat64(int(v.Lowest), dcplace),
- Closed: utils.IntToFloat64(int(v.Last), dcplace),
- TotleVolume: int(v.Totalvolume),
- TotleTurnover: float64(v.Totalturnover),
- HoldVolume: int(v.Holdvolume),
- Settle: utils.IntToFloat64(int(v.Settle), dcplace),
- TimeStamp: fisrtDate,
- }
- logger.GetLogger().Debugf("[checkTiks] 补K线:\n%v", historyData)
- if req.IsAsc {
- rst = append(rst, historyData)
- } else {
- // 插入第一条
- rear := append([]HistoryData{}, rst[0:]...)
- rst = append(append(rst[:0], historyData), rear...)
- }
- }
- }
- }
- }
- }
- }
- }
- // 查询成功
- logger.GetLogger().Debugln("QueryHistoryDatas successed, rows:", len(rst))
- appG.Response(http.StatusOK, e.SUCCESS, rst)
- }
- // HistoryTikData Tik数据
- type HistoryTikData struct {
- TimeStamp time.Time `json:"TS"` // 行情时间文本
- PE float64 `json:"PE"` // 现价
- Vol int `json:"Vol"` // 现量
- TT float64 `json:"TT"` // 现金额
- Bid float64 `json:"Bid"` // 买价
- BV int `json:"BV"` // 买量
- Ask float64 `json:"Ask"` // 卖价
- AV int `json:"AV"` // 卖量
- HV int `json:"HV"` // 持仓量
- HI int `json:"HI"` // 单笔持仓
- TDR int `json:"TDR"` // 交易方向,0:买 1:卖
- TK int `json:"TK"` // 交易类型
- }
- // QueryHistoryTikDatasReq 查询行情Tik数据请求参数
- type QueryHistoryTikDatasReq struct {
- GoodsCode string `form:"goodsCode" binding:"required"`
- StartTime string `form:"startTime"`
- EndTime string `form:"endTime"`
- Count int `form:"count"`
- IsAsc bool `form:"isAsc"`
- }
- // QueryHistoryTikDatas 查询行情Tik数据
- // @Summary 查询行情Tik数据
- // @Produce json
- // @Security ApiKeyAuth
- // @Param goodsCode query string true "商品代码"
- // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
- // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
- // @Param count query int false "条数"
- // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
- // @Success 200 {object} HistoryTikData
- // @Failure 500 {object} app.Response
- // @Router /Quote/QueryHistoryTikDatas [get]
- // @Tags 行情服务
- func QueryHistoryTikDatas(c *gin.Context) {
- appG := app.Gin{C: c}
- // 获取请求参数
- var req QueryHistoryTikDatasReq
- if err := appG.C.ShouldBindQuery(&req); err != nil {
- logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
- return
- }
- // 转换时间
- timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
- var startTime *time.Time
- if len(req.StartTime) > 0 {
- st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
- return
- }
- startTime = &st
- }
- var endTime *time.Time
- if len(req.EndTime) > 0 {
- et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
- return
- }
- endTime = &et
- }
- // 查询数据
- tikDatas, err := models.GetHistoryTikDatas(req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
- if err != nil {
- logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- // 获取目标商品报价小数位
- var dcplace int = 0
- if len(tikDatas) > 0 {
- dcplace = mtpcache.GetGoodsDecimalplace(req.GoodsCode)
- }
- // 计算最终价格
- rst := make([]HistoryTikData, 0)
- for _, v := range tikDatas {
- // 获取方向
- buyOrSell := 0
- if v.TDR == 83 { // (Ascii) B-66 S-83
- buyOrSell = 1
- }
- rst = append(rst, HistoryTikData{
- TimeStamp: time.Unix(int64(v.AT), 0),
- PE: utils.IntToFloat64(v.PE, dcplace),
- Vol: v.Vol,
- TT: float64(v.TT),
- Bid: utils.IntToFloat64(v.Bid, dcplace),
- BV: v.BV,
- Ask: utils.IntToFloat64(v.Ask, dcplace),
- AV: v.AV,
- HV: v.HV,
- HI: v.HI,
- TDR: buyOrSell,
- TK: v.TK,
- })
- }
- // 查询成功
- logger.GetLogger().Debugln("QueryHistoryTikDatas successed, rows: %v", len(rst))
- appG.Response(http.StatusOK, e.SUCCESS, rst)
- }
- // QueryTSDataReq 分时图数据查询请求参数
- type QueryTSDataReq struct {
- GoodsCode string `form:"goodsCode" binding:"required"` // 商品代码
- }
- // QueryTSDataRsp 分时图数据查询返回模型
- type QueryTSDataRsp struct {
- GoodsCode string `json:"goodsCode"` // 商品代码
- OutGoodsCode string `json:"outGoodsCode"` // 外部商品代码
- DecimalPlace int `json:"decimalPlace"` // 小数位
- TradeDate string `json:"tradeDate"` // 交易日
- StartTime time.Time `json:"startTime"` // 开始时间
- EndTime time.Time `json:"endTime"` // 结束时间
- PreSettle float64 `json:"preSettle"` // 昨结价
- Count int `json:"Count"` // 交易日应有数据个数
- HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
- RunSteps []TSDataRunStep `json:"runSteps"` // 交易日开休市计划
- }
- // TSDataRunStep 分时图交易日开休市计划
- type TSDataRunStep struct {
- Groupid int32 `json:"groupid"` // 分组ID
- Tradeweekday int32 `json:"tradeweekday"` // 交易日归属 - 0:星期天、1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六
- Sectionid int32 `json:"sectionid"` // 从 1 开始 往下编 [0为系统清盘、结算时间] SectionId = 0时,开始时间=清盘时间 开始周几= 清盘周几, 结束时间=结算时间 结束周几=结算周几
- Runstep int32 `json:"runstep"` // 运行阶段 - 2:连续交易
- Startweekday int32 `json:"startweekday"` // 起始周几
- Starttime string `json:"starttime"` // 起始时间(HH:mm)
- Endweekday int32 `json:"endweekday"` // 结束周几
- Endtime string `json:"endtime"` // 结束时间(HH:mm)
- Startflag int32 `json:"startflag"` // 开始日标识 - (-1:上日 0:当日 1:次日 )
- Endflag int32 `json:"endflag"` // 结束日标识 - (-1:上日 0:当日 1:次日 )
- Start time.Time `json:"start"` // 真实开始时间
- End time.Time `json:"end"` // 真实结束时间
- }
- // QueryTSData 分时图数据查询
- // @Summary 分时图数据查询
- // @Produce json
- // @Security ApiKeyAuth
- // @Param goodsCode query string true "商品代码"
- // @Success 200 {object} QueryTSDataRsp
- // @Failure 500 {object} app.Response
- // @Router /Quote/QueryTSData [get]
- // @Tags 行情服务
- func QueryTSData(c *gin.Context) {
- appG := app.Gin{C: c}
- // 获取请求参数
- var req QueryTSDataReq
- if err := appG.C.ShouldBindQuery(&req); err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
- return
- }
- // FIXME: - 一些不常变化的数据(如市场信息、商品信息等)应缓存到Redis中, 或缓存到服务内存
- // 获取商品信息
- goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
- if goods == nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
- return
- }
- // 获取市场
- market, err := models.GetMarketByGoodsCode(req.GoodsCode)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- if market == nil {
- logger.GetLogger().Error("QueryTSData failed: not found goodscode")
- appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
- return
- }
- // 获取目标品种交易日
- // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
- // 故通道交易的品种目前只能在交易系统的外部市场中获
- // 取统一的交易日,后期应要求服务端同步外部数据
- // 2021.06.30 业务更新:15、97和99市场的行情交易日要从商品盘面中获取
- quoteTradeDate := ""
- if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
- if quoteDays, err := models.GetQuoteDays("'" + req.GoodsCode + "'"); err == nil {
- if len(quoteDays) > 0 {
- quoteTradeDate = time.Unix(quoteDays[0].Exchangedate, 0).Format("20060102")
- }
- }
- } else {
- if marketRun, err := models.GetMarketRun(int(market.Marketid)); err == nil {
- quoteTradeDate = marketRun.Tradedate2
- }
- }
- if len(quoteTradeDate) == 0 {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKETRUN_FAILED, nil)
- return
- }
- // 获取目标品种的开休市计划
- var runSteps []map[string]interface{}
- // 通道交易外部市场开休市计划表 - QuoteSourceGroupRunStep; 其它市场的 - MarketRunStepDetail
- if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
- // 外部市场
- sourceRunSteps, err := models.FindQuoteSourceGroupRunSteps(*goods)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
- return
- }
- for _, v := range sourceRunSteps {
- // struct -> json
- if jsonBytes, err := json.Marshal(v); err == nil {
- // json -> struct
- var runStepMap map[string]interface{}
- json.Unmarshal(jsonBytes, &runStepMap)
- runSteps = append(runSteps, runStepMap)
- }
- }
- }
- // 非外部市场或外部市场没有配置QuoteSourceGroupRunStep表数据的情况下,都从MarketRunStepDetail中获取数据
- if len(runSteps) == 0 {
- sourceRunSteps, err := models.FindMarketRunStepDetails(int(market.Marketid))
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
- return
- }
- for _, v := range sourceRunSteps {
- // struct -> json
- if jsonBytes, err := json.Marshal(v); err == nil {
- // json -> struct
- var runStepMap map[string]interface{}
- json.Unmarshal(jsonBytes, &runStepMap)
- runSteps = append(runSteps, runStepMap)
- }
- }
- }
- // 获取目标商品的盘面信息
- quoteDays, err := models.GetQuoteDays("'" + strings.ToUpper(goods.Goodscode) + "'")
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
- return
- }
- var preSettle float64
- var preSettleInt int
- if len(quoteDays) > 0 {
- if quoteDays[0].Presettle != 0 {
- preSettleInt = int(quoteDays[0].Presettle)
- preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
- }
- if preSettle == 0 && quoteDays[0].Preclose != 0 {
- preSettleInt = int(quoteDays[0].Preclose)
- preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
- }
- }
- // 构建返回数据
- // 注意:这里使用的是 marketRun.Tradedate2 行情交易日
- queryTSDataRsp := QueryTSDataRsp{
- GoodsCode: goods.Goodscode,
- OutGoodsCode: goods.Outgoodscode,
- TradeDate: quoteTradeDate,
- DecimalPlace: int(goods.Decimalplace),
- PreSettle: preSettle,
- }
- // 构建分时图可直接使用的开休市数据
- // 这里有一个知识点:TRADEWEEKDAY 与 STARTWEEKDAY,以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天(管理端限制),
- // 所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
- // 当前交易日(周几)对应的开休市计划
- tradeDate, err := time.ParseInLocation("20060102", quoteTradeDate, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- // !!!开休市计划明细
- curWeekRunSteps := make([]map[string]interface{}, 0)
- for _, v := range runSteps {
- tradeWeekDay := int(v["tradeweekday"].(float64))
- if tradeWeekDay == int(tradeDate.Weekday()) {
- curWeekRunSteps = append(curWeekRunSteps, v)
- }
- }
- // 获取不到可用的开休市计划
- if len(curWeekRunSteps) == 0 {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
- return
- }
- // 按 SECTIONID 顺序排序
- sort.Slice(curWeekRunSteps, func(i int, j int) bool {
- return curWeekRunSteps[i]["sectionid"].(float64) < curWeekRunSteps[j]["sectionid"].(float64)
- })
- // 把各开休市时间段转化为真实的时间
- // 关于开休市计划的时间顺序:管理端会按时间顺序添加开休市计划,所以交易日开始时间为第一条记录的开始时间,结束时间为最后一条记录的结束时间
- // 关于目标商品的交易日问题:目前只能从商品所属市场获取当前交易日,这样有两个问题,一是不能按常规显示最后一个有历史数据的交易日;二是目前所有外部商品的开休市计划都是一样的。
- timeFormat := "20060102 15:04"
- // 开始时间
- startInterval := getTradeDay(int(curWeekRunSteps[0]["tradeweekday"].(float64)), int(curWeekRunSteps[0]["startweekday"].(float64)))
- queryTSDataRsp.StartTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[0]["starttime"].(string)), time.Local)
- if startInterval != 0 {
- duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
- queryTSDataRsp.StartTime = queryTSDataRsp.StartTime.Add(duration)
- }
- // 结束时间
- index := len(curWeekRunSteps) - 1
- endInterval := getTradeDay(int(curWeekRunSteps[index]["tradeweekday"].(float64)), int(curWeekRunSteps[index]["endweekday"].(float64)))
- queryTSDataRsp.EndTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[index]["endtime"].(string)), time.Local)
- if endInterval != 0 {
- duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
- queryTSDataRsp.EndTime = queryTSDataRsp.EndTime.Add(duration)
- }
- // fmt.Printf("开始时间:%s 结束时间:%s\n", queryTSDataRsp.StartTime.Format(timeFormat), queryTSDataRsp.EndTime.Format(timeFormat))
- // 获取目标时间段的历史数据(1分钟周期)
- // 这里要注意:由于交易库和行情库由于GoodsCode大小写不一定对得上,所以在使用交易库的商品查询行情数据时间,都要使用OutGoodsCode字段
- // 20210702: 按行情服务要求,改用大写的GoodsCode
- cycleDatas, err := models.GetHistoryCycleDatas(models.CycleTypeMinutes1, strings.ToUpper(queryTSDataRsp.GoodsCode), &queryTSDataRsp.StartTime, &queryTSDataRsp.EndTime, 0, true)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- // ==================== 补数据(补休市数据和缺少的数据)====================
- if len(cycleDatas) > 0 {
- // 补数据第一步:如果第一数据不是开始时间的,需要补数据
- sources := time.Unix(int64(cycleDatas[0].ST), 0)
- diff := sources.Sub(queryTSDataRsp.StartTime)
- if diff.Minutes() > 0 {
- minute := int(diff.Minutes())
- for i := 1; i <= minute; i++ {
- st := cycleDatas[0].ST - i*60
- stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
- // 前面的数据使用昨结来补
- cycleDatas = append(cycleDatas, models.CycleData{
- GC: cycleDatas[0].GC,
- Open: preSettleInt,
- High: preSettleInt,
- Low: preSettleInt,
- Close: preSettleInt,
- TV: 0,
- TT: 0,
- HV: 0,
- SP: 0,
- ST: st,
- SST: stt,
- FI: true,
- })
- }
- }
- // 接时间戳排序
- sort.Slice(cycleDatas, func(i int, j int) bool {
- return cycleDatas[i].ST < cycleDatas[j].ST
- })
- // 补数据第二步:按尾部的时间(当前服务器时间或最后休市时间)进行全补
- // 获取服务器时间
- s, _ := models.GetServerTime()
- endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- if endTime.After(queryTSDataRsp.EndTime) {
- endTime = queryTSDataRsp.EndTime
- }
- index := len(cycleDatas) - 1
- sources = time.Unix(int64(cycleDatas[index].ST), 0)
- diff = endTime.Sub(sources)
- if diff.Minutes() > 0 {
- minute := int(diff.Minutes())
- for i := 1; i <= minute; i++ {
- st := cycleDatas[index].ST + i*60
- stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
- cycleDatas = append(cycleDatas, models.CycleData{
- GC: cycleDatas[index].GC,
- Open: cycleDatas[index].Close,
- High: cycleDatas[index].Close,
- Low: cycleDatas[index].Close,
- Close: cycleDatas[index].Close,
- TV: 0,
- TT: 0,
- HV: 0,
- SP: 0,
- ST: st,
- SST: stt,
- FI: true,
- })
- }
- }
- // 接时间戳排序
- sort.Slice(cycleDatas, func(i int, j int) bool {
- return cycleDatas[i].ST < cycleDatas[j].ST
- })
- // 补数据第三步:补中间数据
- fillDatas := make([]models.CycleData, 0)
- for i := range cycleDatas {
- // 第一条记录跳过
- if i == 0 {
- continue
- }
- current := time.Unix(int64(cycleDatas[i].ST), 0)
- prev := time.Unix(int64(cycleDatas[i-1].ST), 0)
- diff := current.Sub(prev)
- if diff.Minutes() > 1 {
- minute := int(diff.Minutes())
- // 判断是否需要补数据,与上一条数据的间距不是一分钟
- if minute > 1 {
- // 注意补中间数据时,是不用补最后一条数据的,所以是 < minute
- for j := 1; j < minute; j++ {
- st := cycleDatas[i-1].ST + j*60
- stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
- fillDatas = append(fillDatas, models.CycleData{
- GC: cycleDatas[i-1].GC,
- Open: cycleDatas[i-1].Close,
- High: cycleDatas[i-1].Close,
- Low: cycleDatas[i-1].Close,
- Close: cycleDatas[i-1].Close,
- TV: 0,
- TT: 0,
- HV: 0,
- SP: 0,
- ST: st,
- SST: stt,
- FI: true,
- })
- }
- }
- }
- }
- // 加入到源数据
- cycleDatas = append(cycleDatas, fillDatas...)
- // 接时间戳排序
- sort.Slice(cycleDatas, func(i int, j int) bool {
- return cycleDatas[i].ST < cycleDatas[j].ST
- })
- } else {
- // TODO: - 下面这块操作需求确认
- // 如果查询结果是空数据,则使用昨结价补到服务器时间(或最后休市时间)
- // 获取服务器时间
- s, _ := models.GetServerTime()
- endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
- if err != nil {
- logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
- appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
- return
- }
- if endTime.After(queryTSDataRsp.EndTime) {
- endTime = queryTSDataRsp.EndTime
- }
- diff := endTime.Sub(queryTSDataRsp.StartTime)
- minute := int(diff.Minutes())
- for i := 1; i <= minute; i++ {
- st := int(queryTSDataRsp.StartTime.Unix()) + i*60
- stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
- cycleDatas = append(cycleDatas, models.CycleData{
- GC: queryTSDataRsp.GoodsCode,
- Open: preSettleInt,
- High: preSettleInt,
- Low: preSettleInt,
- Close: preSettleInt,
- TV: 0,
- TT: 0,
- HV: 0,
- SP: 0,
- ST: st,
- SST: stt,
- FI: true,
- })
- }
- // 接时间戳排序
- sort.Slice(cycleDatas, func(i int, j int) bool {
- return cycleDatas[i].ST < cycleDatas[j].ST
- })
- }
- // 补数据第四步:清除掉开市计划外的数据
- // 先计算出每条计划明细的真正开始与结束时间
- queryTSDataRsp.RunSteps = make([]TSDataRunStep, 0)
- for _, v := range curWeekRunSteps {
- // 开始时间
- startInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["startweekday"].(float64)))
- v["start"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["starttime"].(string)), time.Local)
- if startInterval != 0 {
- duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
- v["start"] = v["start"].(time.Time).Add(duration)
- }
- // 结束时间
- endInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["endweekday"].(float64)))
- v["end"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["endtime"].(string)), time.Local)
- if endInterval != 0 {
- duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
- v["end"] = v["end"].(time.Time).Add(duration)
- }
- // map -> struct
- var tsDataRunStep TSDataRunStep
- jsonbody, err := json.Marshal(v)
- if err != nil {
- continue
- }
- if err := json.Unmarshal(jsonbody, &tsDataRunStep); err != nil {
- continue
- }
- queryTSDataRsp.RunSteps = append(queryTSDataRsp.RunSteps, tsDataRunStep)
- }
- // 最终返回的历史数据
- historyDatas := make([]HistoryData, 0)
- for _, cycleData := range cycleDatas {
- needAdd := false
- for _, runStep := range curWeekRunSteps {
- // 判断是否在开市计划内
- if cycleData.ST >= int(runStep["start"].(time.Time).Unix()) && cycleData.ST <= int(runStep["end"].(time.Time).Unix()) {
- needAdd = true
- break
- }
- }
- if needAdd {
- historyDatas = append(historyDatas, HistoryData{
- Opened: utils.IntToFloat64(cycleData.Open, int(goods.Decimalplace)),
- Highest: utils.IntToFloat64(cycleData.High, int(goods.Decimalplace)),
- Lowest: utils.IntToFloat64(cycleData.Low, int(goods.Decimalplace)),
- Closed: utils.IntToFloat64(cycleData.Close, int(goods.Decimalplace)),
- TotleVolume: cycleData.TV,
- TotleTurnover: float64(cycleData.TT),
- HoldVolume: cycleData.HV,
- Settle: utils.IntToFloat64(cycleData.SP, int(goods.Decimalplace)),
- TimeStamp: time.Unix(int64(cycleData.ST), 0),
- IsFill: cycleData.FI,
- })
- }
- }
- queryTSDataRsp.HistoryDatas = historyDatas
- // 计算当前交易日应该有多少个历史数据,主要用于iOS
- for _, v := range queryTSDataRsp.RunSteps {
- diff := v.End.Sub(v.Start)
- queryTSDataRsp.Count += int(diff.Minutes())
- }
- // 查询成功
- logger.GetLogger().Debugln("QueryTSData successed.")
- appG.Response(http.StatusOK, e.SUCCESS, queryTSDataRsp)
- }
- // getTradeDay 获取结算计划中天数间隔的方法
- // - tradeDay: 交易日周几
- // - weekDay: 开始或结束周几
- // - Returns: 天数间隔
- func getTradeDay(tradeDay, weekDay int) int {
- if tradeDay == weekDay {
- return 0
- }
- if weekDay == 0 {
- weekDay = 7
- }
- betWeekDay := weekDay - tradeDay
- if betWeekDay < 0 {
- betWeekDay = betWeekDay + 7
- }
- if betWeekDay >= 4 {
- betWeekDay = betWeekDay - 7
- }
- return betWeekDay
- }
- /**
- 获取某日所在周周一的日期
- */
- func getFirstDateOfWeek(d time.Time) time.Time {
- offset := int(time.Monday - d.Weekday())
- if offset > 0 {
- offset = -6
- }
- weekStartDate := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
- return weekStartDate
- }
- //获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
- func getFirstDateOfMonth(d time.Time) time.Time {
- d = d.AddDate(0, 0, -d.Day()+1)
- return getZeroTime(d)
- }
- //获取传入的时间所在年的第一天,即某年第一天的0点。如传入time.Now(), 返回当前年的第一天0点时间。
- func getFirstDateOfYear(d time.Time) time.Time {
- d = d.AddDate(0, -d.Year()+1, 0)
- return getZeroTime(d)
- }
- //获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
- func getLastDateOfMonth(d time.Time) time.Time {
- return getFirstDateOfMonth(d).AddDate(0, 1, -1)
- }
- //获取某一天的0点时间
- func getZeroTime(d time.Time) time.Time {
- return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
- }
|