history.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package quote
  2. import (
  3. "encoding/json"
  4. "mtp2_if/global/app"
  5. "mtp2_if/global/e"
  6. "mtp2_if/logger"
  7. "mtp2_if/models"
  8. "net/http"
  9. "sort"
  10. "time"
  11. "github.com/gin-gonic/gin"
  12. )
  13. // HistoryData 历史数据
  14. type HistoryData struct {
  15. Opened float32 `json:"Opened"` // 开盘价
  16. Highest float32 `json:"highest"` // 收盘价
  17. Lowest float32 `json:"lowest"` // 最低价
  18. Closed float32 `json:"closed"` // 收盘价
  19. TotleVolume float32 `json:"totleVolume"` // 总量
  20. TotleTurnover float32 `json:"totleTurnover"` // 总金额
  21. HoldVolume float32 `json:"holdVolume"` // 持仓量
  22. Settle float32 `json:"settle"` // 结算价
  23. TimeStamp time.Time `json:"timeStamp"` // 开盘时间
  24. }
  25. // QueryHistoryDatasReq 查询行情历史数据请求参数
  26. type QueryHistoryDatasReq struct {
  27. CycleType int `form:"cycleType" binding:"required"`
  28. GoodsCode string `form:"goodsCode" binding:"required"`
  29. StartTime string `form:"startTime"`
  30. EndTime string `form:"endTime"`
  31. Count int `form:"count"`
  32. IsAsc bool `form:"isAsc"`
  33. }
  34. // QueryHistoryDatas 查询行情历史数据
  35. // @Summary 查询行情历史数据
  36. // @Produce json
  37. // @Security ApiKeyAuth
  38. // @Param cycleType query int true "周期类型, 0-秒 1: 1分钟 2: 5分钟 3: 30分钟 4: 60分钟 120: 2小时 240: 4小时 11: 日线 10: Tik"
  39. // @Param goodsCode query string true "商品代码"
  40. // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
  41. // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
  42. // @Param count query int false "条数"
  43. // @Param IsAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
  44. // @Success 200 {object} models.CycleData
  45. // @Failure 500 {object} app.Response
  46. // @Router /Quote/QueryHistoryDatas [get]
  47. // @Tags 行情服务
  48. func QueryHistoryDatas(c *gin.Context) {
  49. appG := app.Gin{C: c}
  50. // 获取请求参数
  51. var req QueryHistoryDatasReq
  52. if err := appG.C.ShouldBindQuery(&req); err != nil {
  53. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  54. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  55. return
  56. }
  57. // 转换时间
  58. timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
  59. var startTime *time.Time
  60. if len(req.StartTime) > 0 {
  61. st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
  62. if err != nil {
  63. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  64. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  65. return
  66. }
  67. startTime = &st
  68. }
  69. var endTime *time.Time
  70. if len(req.EndTime) > 0 {
  71. et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
  72. if err != nil {
  73. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  74. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  75. return
  76. }
  77. endTime = &et
  78. }
  79. // 查询数据
  80. cycleData, err := models.GetHistoryCycleDatas(models.CycleType(req.CycleType), req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
  81. if err != nil {
  82. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  83. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  84. return
  85. }
  86. // 查询成功
  87. logger.GetLogger().Debugln("QueryHistoryDatas successed: %v", cycleData)
  88. appG.Response(http.StatusOK, e.SUCCESS, cycleData)
  89. }
  90. // QueryTSDataReq 分时图数据查询请求参数
  91. type QueryTSDataReq struct {
  92. GoodsCode string `form:"goodsCode" binding:"required"` // 商品代码
  93. }
  94. // QueryTSDataRsp 分时图数据查询返回模型
  95. type QueryTSDataRsp struct {
  96. GoodsCode string `json:"goodsCode"` // 商品代码
  97. DecimalPlace int `json:"decimalPlace"` // 小数位
  98. TradeDate string `json:"tradeDate"` // 交易日
  99. StartTime string `json:"startTime"` // 开始时间
  100. EndTime string `json:"endTime"` // 结束时间
  101. PreSettle float32 `json:"preSettle"` // 昨结
  102. HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
  103. }
  104. // QueryTSData 分时图数据查询
  105. // @Summary 分时图数据查询
  106. // @Produce json
  107. // @Param GoodsCode query string true "商品代码"
  108. // @Success 200 {object} QueryTSDataRsp
  109. // @Failure 500 {object} app.Response
  110. // @Router /Quote/QueryTSData [get]
  111. // @Tags 行情服务
  112. func QueryTSData(c *gin.Context) {
  113. appG := app.Gin{C: c}
  114. // 获取请求参数
  115. var req QueryTSDataReq
  116. if err := appG.C.ShouldBindQuery(&req); err != nil {
  117. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  118. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  119. return
  120. }
  121. var queryTSDataRsp QueryTSDataRsp
  122. // FIXME: - 一些不常变化的数据(如市场信息、商品信息等)应缓存到Redis中, 或缓存到服务内存
  123. market, err := models.GetMarketByGoodsCode(req.GoodsCode)
  124. if err != nil {
  125. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  126. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  127. return
  128. }
  129. if market == nil {
  130. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  131. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
  132. return
  133. }
  134. // 获取目标品种交易日
  135. // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
  136. // 故通道交易的品种目前只能在交易系统的外部市场中获
  137. // 取统一的交易日,后期应要求服务端同步外部数据
  138. marketRun, err := models.GetMarketRun(int(market.Marketid))
  139. if marketRun == nil {
  140. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  141. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKETRUN_FAILED, nil)
  142. return
  143. }
  144. // 获取目标品种的开休市计划
  145. var runSteps []map[string]interface{}
  146. // 通道交易外部市场开休市计划表 - QuoteSourceGroupRunStep; 其它市场的 - MarketRunStepDetail
  147. if market.Trademode == 15 {
  148. // 外部市场
  149. sourceRunSteps, err := models.FindQuoteSourceGroupRunStepsByMarket(*market)
  150. if err != nil {
  151. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  152. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  153. return
  154. }
  155. for v := range sourceRunSteps {
  156. // struct -> json
  157. if jsonBytes, err := json.Marshal(v); err == nil {
  158. // json -> struct
  159. var runStepMap map[string]interface{}
  160. json.Unmarshal(jsonBytes, &runStepMap)
  161. runSteps = append(runSteps, runStepMap)
  162. }
  163. }
  164. }
  165. // 非外部市场或外部市场没有配置QuoteSourceGroupRunStep表数据的情况下,都从MarketRunStepDetail中获取数据
  166. if len(runSteps) == 0 {
  167. sourceRunSteps, err := models.FindMarketRunStepDetails(int(market.Marketid))
  168. if err != nil {
  169. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  170. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  171. return
  172. }
  173. for _, v := range sourceRunSteps {
  174. // struct -> json
  175. if jsonBytes, err := json.Marshal(v); err == nil {
  176. // json -> struct
  177. var runStepMap map[string]interface{}
  178. json.Unmarshal(jsonBytes, &runStepMap)
  179. runSteps = append(runSteps, runStepMap)
  180. }
  181. }
  182. }
  183. // 构建分时图可直接使用的开休市数据
  184. // 这里有一个知识点:TRADEWEEKDAY 与 STARTWEEKDAY,以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天(管理端限制),
  185. // 所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
  186. // 当前交易日(周几)对应的开休市计划
  187. tradeDate, err := time.ParseInLocation("2006-01-02", marketRun.Tradedate, time.Local)
  188. if err != nil {
  189. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  190. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  191. return
  192. }
  193. curWeekRunSteps := make([]map[string]interface{}, 0)
  194. for _, v := range runSteps {
  195. tradeWeekDay := v["tradeweekday"].(int)
  196. if tradeWeekDay == int(tradeDate.Weekday()) {
  197. curWeekRunSteps = append(curWeekRunSteps, v)
  198. }
  199. }
  200. // 获取不到可用的开休市计划
  201. if len(curWeekRunSteps) == 0 {
  202. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  203. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  204. return
  205. }
  206. // 按 SECTIONID 顺序排序(管理端会按时间顺序添加开休市计划)
  207. sort.Slice(curWeekRunSteps, func(i int, j int) bool {
  208. return curWeekRunSteps[i]["sectionid"].(int) < curWeekRunSteps[j]["sectionid"].(int)
  209. })
  210. // 把各开休市时间段转化为真实的时间
  211. //
  212. // 查询成功
  213. logger.GetLogger().Debugln("QueryTSData successed: %v", queryTSDataRsp)
  214. appG.Response(http.StatusOK, e.SUCCESS, queryTSDataRsp)
  215. }