package quote import ( "encoding/json" "fmt" "mtp2_if/global/app" "mtp2_if/global/e" "mtp2_if/logger" "mtp2_if/models" "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: 日线" // @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 } // 获取目标商品信息 goods, err := models.GetGoodsByGoodsCode(req.GoodsCode) if err != nil { logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error()) appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil) return } // 计算最终价格 rst := make([]HistoryData, 0) for _, v := range cycleDatas { historyData := HistoryData{ Opened: utils.IntToFloat64(v.Open, int(goods.Decimalplace)), Highest: utils.IntToFloat64(v.High, int(goods.Decimalplace)), Lowest: utils.IntToFloat64(v.Low, int(goods.Decimalplace)), Closed: utils.IntToFloat64(v.Close, int(goods.Decimalplace)), TotleVolume: v.TV, TotleTurnover: float64(v.TT), HoldVolume: v.HV, Settle: utils.IntToFloat64(v.SP, int(goods.Decimalplace)), TimeStamp: time.Unix(int64(v.ST), 0), } rst = append(rst, historyData) } // 查询成功 logger.GetLogger().Debugln("QueryHistoryDatas successed: %v", 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 } // 获取目标商品信息 goods, err := models.GetGoodsByGoodsCode(req.GoodsCode) if err != nil { logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error()) appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil) return } // 计算最终价格 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, int(goods.Decimalplace)), Vol: v.Vol, TT: float64(v.TT), Bid: utils.IntToFloat64(v.Bid, int(goods.Decimalplace)), BV: v.BV, Ask: utils.IntToFloat64(v.Ask, int(goods.Decimalplace)), AV: v.AV, HV: v.HV, HI: v.HI, TDR: buyOrSell, TK: v.TK, }) } // 查询成功 logger.GetLogger().Debugln("QueryHistoryTikDatas successed: %v", 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().Errorf("QueryTSData failed: %s", err.Error()) 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: %v", queryTSDataRsp) 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 }