|
|
@@ -2,6 +2,7 @@ package quote
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
+ "fmt"
|
|
|
"mtp2_if/global/app"
|
|
|
"mtp2_if/global/e"
|
|
|
"mtp2_if/logger"
|
|
|
@@ -25,6 +26,7 @@ type HistoryData struct {
|
|
|
HoldVolume int `json:"hv"` // 持仓量
|
|
|
Settle float64 `json:"s"` // 结算价,日线周期(包括)以上才有
|
|
|
TimeStamp time.Time `json:"ts"` // 时间
|
|
|
+ IsFill bool `json:"f"` // 是否补充数据
|
|
|
}
|
|
|
|
|
|
// QueryHistoryDatasReq 查询行情历史数据请求参数
|
|
|
@@ -132,10 +134,11 @@ type QueryTSDataReq struct {
|
|
|
// QueryTSDataRsp 分时图数据查询返回模型
|
|
|
type QueryTSDataRsp struct {
|
|
|
GoodsCode string `json:"goodsCode"` // 商品代码
|
|
|
+ OutGoodsCode string `json:"outGoodsCode"` // 外部商品代码
|
|
|
DecimalPlace int `json:"decimalPlace"` // 小数位
|
|
|
TradeDate string `json:"tradeDate"` // 交易日
|
|
|
- StartTime string `json:"startTime"` // 开始时间
|
|
|
- EndTime string `json:"endTime"` // 结束时间
|
|
|
+ StartTime time.Time `json:"startTime"` // 开始时间
|
|
|
+ EndTime time.Time `json:"endTime"` // 结束时间
|
|
|
PreSettle float32 `json:"preSettle"` // 昨结
|
|
|
HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
|
|
|
}
|
|
|
@@ -143,7 +146,7 @@ type QueryTSDataRsp struct {
|
|
|
// QueryTSData 分时图数据查询
|
|
|
// @Summary 分时图数据查询
|
|
|
// @Produce json
|
|
|
-// @Param GoodsCode query string true "商品代码"
|
|
|
+// @Param goodsCode query string true "商品代码"
|
|
|
// @Success 200 {object} QueryTSDataRsp
|
|
|
// @Failure 500 {object} app.Response
|
|
|
// @Router /Quote/QueryTSData [get]
|
|
|
@@ -159,8 +162,6 @@ func QueryTSData(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- var queryTSDataRsp QueryTSDataRsp
|
|
|
-
|
|
|
// FIXME: - 一些不常变化的数据(如市场信息、商品信息等)应缓存到Redis中, 或缓存到服务内存
|
|
|
market, err := models.GetMarketByGoodsCode(req.GoodsCode)
|
|
|
if err != nil {
|
|
|
@@ -225,11 +226,28 @@ func QueryTSData(c *gin.Context) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 获取商品信息
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建返回数据
|
|
|
+ queryTSDataRsp := QueryTSDataRsp{
|
|
|
+ GoodsCode: goods.Goodscode,
|
|
|
+ OutGoodsCode: goods.Outgoodscode,
|
|
|
+ TradeDate: marketRun.Tradedate,
|
|
|
+ DecimalPlace: int(goods.Decimalplace),
|
|
|
+ }
|
|
|
+
|
|
|
// 构建分时图可直接使用的开休市数据
|
|
|
// 这里有一个知识点:TRADEWEEKDAY 与 STARTWEEKDAY,以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天(管理端限制),
|
|
|
// 所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
|
|
|
// 当前交易日(周几)对应的开休市计划
|
|
|
- tradeDate, err := time.ParseInLocation("2006-01-02", marketRun.Tradedate, time.Local)
|
|
|
+ tradeDate, err := time.ParseInLocation("20060102", marketRun.Tradedate, time.Local)
|
|
|
if err != nil {
|
|
|
logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
|
|
|
appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
|
|
|
@@ -237,7 +255,7 @@ func QueryTSData(c *gin.Context) {
|
|
|
}
|
|
|
curWeekRunSteps := make([]map[string]interface{}, 0)
|
|
|
for _, v := range runSteps {
|
|
|
- tradeWeekDay := v["tradeweekday"].(int)
|
|
|
+ tradeWeekDay := int(v["tradeweekday"].(float64))
|
|
|
if tradeWeekDay == int(tradeDate.Weekday()) {
|
|
|
curWeekRunSteps = append(curWeekRunSteps, v)
|
|
|
}
|
|
|
@@ -248,14 +266,132 @@ func QueryTSData(c *gin.Context) {
|
|
|
appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
|
|
|
return
|
|
|
}
|
|
|
- // 按 SECTIONID 顺序排序(管理端会按时间顺序添加开休市计划)
|
|
|
+ // 按 SECTIONID 顺序排序
|
|
|
sort.Slice(curWeekRunSteps, func(i int, j int) bool {
|
|
|
return curWeekRunSteps[i]["sectionid"].(int) < curWeekRunSteps[j]["sectionid"].(int)
|
|
|
})
|
|
|
+
|
|
|
// 把各开休市时间段转化为真实的时间
|
|
|
- //
|
|
|
+ // 关于开休市计划的时间顺序:管理端会按时间顺序添加开休市计划,所以交易日开始时间为第一条记录的开始时间,结束时间为最后一条记录的结束时间
|
|
|
+ // 关于目标商品的交易日问题:目前只能从商品所属市场获取当前交易日,这样有两个问题,一是不能按常规显示最后一个有历史数据的交易日;二是目前所有外部商品的开休市计划都是一样的。
|
|
|
+ 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", marketRun.Tradedate, 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", marketRun.Tradedate, 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))
|
|
|
+
|
|
|
+ // 获取目标时间段的历史数据
|
|
|
+ // 这里要注意:由于交易库和行情库由于GoodsCode大小写不一定对得上,所以在使用交易库的商品查询行情数据时间,都要使用OutGoodsCode字段
|
|
|
+ cycleDatas, err := models.GetHistoryCycleDatas(models.CycleTypeMinutes1, queryTSDataRsp.OutGoodsCode, &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 := 0; 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: cycleDatas[0].Open,
|
|
|
+ High: cycleDatas[0].High,
|
|
|
+ Low: cycleDatas[0].Low,
|
|
|
+ Close: cycleDatas[0].Close,
|
|
|
+ TV: cycleDatas[0].TV,
|
|
|
+ TT: cycleDatas[0].TT,
|
|
|
+ HV: cycleDatas[0].HV,
|
|
|
+ SP: cycleDatas[0].SP,
|
|
|
+ ST: st,
|
|
|
+ SST: stt,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 接时间戳排序
|
|
|
+ sort.Slice(cycleDatas, func(i int, j int) bool {
|
|
|
+ return cycleDatas[i].ST < cycleDatas[j].ST
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 补数据第二步:按尾部的时间(当前服务器时间或最后休市时间)进行全补
|
|
|
+ if len(cycleDatas) > 0 {
|
|
|
+ s, _ := models.GetServerTime()
|
|
|
+ endTime, _ := time.ParseInLocation("2006/01/02 15:04:05", *s, time.Local)
|
|
|
+ if endTime.After(queryTSDataRsp.EndTime) {
|
|
|
+ endTime = queryTSDataRsp.EndTime
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否需要补数据,与上一条数据的间距不是一分钟
|
|
|
+ index := len(cycleDatas) - 1
|
|
|
+ sources := time.Unix(int64(cycleDatas[index].ST), 0)
|
|
|
+ diff := sources.Sub(endTime)
|
|
|
+ if diff.Minutes() > 0 {
|
|
|
+ minute := int(diff.Minutes())
|
|
|
+ for i := 0; 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].Open,
|
|
|
+ High: cycleDatas[index].High,
|
|
|
+ Low: cycleDatas[index].Low,
|
|
|
+ Close: cycleDatas[index].Close,
|
|
|
+ TV: cycleDatas[index].TV,
|
|
|
+ TT: cycleDatas[index].TT,
|
|
|
+ HV: cycleDatas[index].HV,
|
|
|
+ SP: cycleDatas[index].SP,
|
|
|
+ ST: st,
|
|
|
+ SST: stt,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 接时间戳排序
|
|
|
+ sort.Slice(cycleDatas, func(i int, j int) bool {
|
|
|
+ return cycleDatas[i].ST < cycleDatas[j].ST
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 补数据第三步:补中间数据
|
|
|
|
|
|
// 查询成功
|
|
|
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
|
|
|
+}
|