history.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. package quote
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "mtp2_if/global/app"
  6. "mtp2_if/global/e"
  7. "mtp2_if/logger"
  8. "mtp2_if/models"
  9. "mtp2_if/utils"
  10. "net/http"
  11. "sort"
  12. "strings"
  13. "time"
  14. "github.com/gin-gonic/gin"
  15. )
  16. // HistoryData 历史数据
  17. type HistoryData struct {
  18. Opened float64 `json:"o"` // 开盘价
  19. Highest float64 `json:"h"` // 最高价
  20. Lowest float64 `json:"l"` // 最低价
  21. Closed float64 `json:"c"` // 收盘价
  22. TotleVolume int `json:"tv"` // 总量
  23. TotleTurnover float64 `json:"tt"` // 总金额
  24. HoldVolume int `json:"hv"` // 持仓量
  25. Settle float64 `json:"s"` // 结算价,日线周期(包括)以上才有
  26. TimeStamp time.Time `json:"ts"` // 时间
  27. IsFill bool `json:"f"` // 是否补充数据
  28. }
  29. // QueryHistoryDatasReq 查询行情历史数据请求参数
  30. type QueryHistoryDatasReq struct {
  31. CycleType int `form:"cycleType" binding:"required"`
  32. GoodsCode string `form:"goodsCode" binding:"required"`
  33. StartTime string `form:"startTime"`
  34. EndTime string `form:"endTime"`
  35. Count int `form:"count"`
  36. IsAsc bool `form:"isAsc"`
  37. }
  38. // QueryHistoryDatas 查询行情历史数据
  39. // @Summary 查询行情历史数据
  40. // @Produce json
  41. // @Security ApiKeyAuth
  42. // @Param cycleType query int true "周期类型, 0-秒 1: 1分钟 2: 5分钟 3: 30分钟 4: 60分钟 120: 2小时 240: 4小时 11: 日线"
  43. // @Param goodsCode query string true "商品代码"
  44. // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
  45. // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
  46. // @Param count query int false "条数"
  47. // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
  48. // @Success 200 {object} HistoryData
  49. // @Failure 500 {object} app.Response
  50. // @Router /Quote/QueryHistoryDatas [get]
  51. // @Tags 行情服务
  52. func QueryHistoryDatas(c *gin.Context) {
  53. appG := app.Gin{C: c}
  54. // 获取请求参数
  55. var req QueryHistoryDatasReq
  56. if err := appG.C.ShouldBindQuery(&req); err != nil {
  57. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  58. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  59. return
  60. }
  61. // 转换时间
  62. timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
  63. var startTime *time.Time
  64. if len(req.StartTime) > 0 {
  65. st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
  66. if err != nil {
  67. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  68. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  69. return
  70. }
  71. startTime = &st
  72. }
  73. var endTime *time.Time
  74. if len(req.EndTime) > 0 {
  75. et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
  76. if err != nil {
  77. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  78. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  79. return
  80. }
  81. endTime = &et
  82. }
  83. // 查询数据
  84. cycleDatas, err := models.GetHistoryCycleDatas(models.CycleType(req.CycleType), req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
  85. if err != nil {
  86. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  87. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  88. return
  89. }
  90. // 获取目标商品信息
  91. goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
  92. if err != nil {
  93. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  94. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  95. return
  96. }
  97. // 计算最终价格
  98. rst := make([]HistoryData, 0)
  99. for _, v := range cycleDatas {
  100. historyData := HistoryData{
  101. Opened: utils.IntToFloat64(v.Open, int(goods.Decimalplace)),
  102. Highest: utils.IntToFloat64(v.High, int(goods.Decimalplace)),
  103. Lowest: utils.IntToFloat64(v.Low, int(goods.Decimalplace)),
  104. Closed: utils.IntToFloat64(v.Close, int(goods.Decimalplace)),
  105. TotleVolume: v.TV,
  106. TotleTurnover: float64(v.TT),
  107. HoldVolume: v.HV,
  108. Settle: utils.IntToFloat64(v.SP, int(goods.Decimalplace)),
  109. TimeStamp: time.Unix(int64(v.ST), 0),
  110. }
  111. rst = append(rst, historyData)
  112. }
  113. // 查询成功
  114. logger.GetLogger().Debugln("QueryHistoryDatas successed: %v", rst)
  115. appG.Response(http.StatusOK, e.SUCCESS, rst)
  116. }
  117. // HistoryTikData Tik数据
  118. type HistoryTikData struct {
  119. TimeStamp time.Time `json:"TS"` // 行情时间文本
  120. PE float64 `json:"PE"` // 现价
  121. Vol int `json:"Vol"` // 现量
  122. TT float64 `json:"TT"` // 现金额
  123. Bid float64 `json:"Bid"` // 买价
  124. BV int `json:"BV"` // 买量
  125. Ask float64 `json:"Ask"` // 卖价
  126. AV int `json:"AV"` // 卖量
  127. HV int `json:"HV"` // 持仓量
  128. HI int `json:"HI"` // 单笔持仓
  129. TDR int `json:"TDR"` // 交易方向,0:买 1:卖
  130. TK int `json:"TK"` // 交易类型
  131. }
  132. // QueryHistoryTikDatasReq 查询行情Tik数据请求参数
  133. type QueryHistoryTikDatasReq struct {
  134. GoodsCode string `form:"goodsCode" binding:"required"`
  135. StartTime string `form:"startTime"`
  136. EndTime string `form:"endTime"`
  137. Count int `form:"count"`
  138. IsAsc bool `form:"isAsc"`
  139. }
  140. // QueryHistoryTikDatas 查询行情Tik数据
  141. // @Summary 查询行情Tik数据
  142. // @Produce json
  143. // @Security ApiKeyAuth
  144. // @Param goodsCode query string true "商品代码"
  145. // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
  146. // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
  147. // @Param count query int false "条数"
  148. // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
  149. // @Success 200 {object} HistoryTikData
  150. // @Failure 500 {object} app.Response
  151. // @Router /Quote/QueryHistoryTikDatas [get]
  152. // @Tags 行情服务
  153. func QueryHistoryTikDatas(c *gin.Context) {
  154. appG := app.Gin{C: c}
  155. // 获取请求参数
  156. var req QueryHistoryTikDatasReq
  157. if err := appG.C.ShouldBindQuery(&req); err != nil {
  158. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  159. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  160. return
  161. }
  162. // 转换时间
  163. timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
  164. var startTime *time.Time
  165. if len(req.StartTime) > 0 {
  166. st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
  167. if err != nil {
  168. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  169. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  170. return
  171. }
  172. startTime = &st
  173. }
  174. var endTime *time.Time
  175. if len(req.EndTime) > 0 {
  176. et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
  177. if err != nil {
  178. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  179. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  180. return
  181. }
  182. endTime = &et
  183. }
  184. // 查询数据
  185. tikDatas, err := models.GetHistoryTikDatas(req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
  186. if err != nil {
  187. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  188. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  189. return
  190. }
  191. // 获取目标商品信息
  192. goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
  193. if err != nil {
  194. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  195. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  196. return
  197. }
  198. // 计算最终价格
  199. rst := make([]HistoryTikData, 0)
  200. for _, v := range tikDatas {
  201. // 获取方向
  202. buyOrSell := 0
  203. if v.TDR == 83 { // (Ascii) B-66 S-83
  204. buyOrSell = 1
  205. }
  206. rst = append(rst, HistoryTikData{
  207. TimeStamp: time.Unix(int64(v.AT), 0),
  208. PE: utils.IntToFloat64(v.PE, int(goods.Decimalplace)),
  209. Vol: v.Vol,
  210. TT: float64(v.TT),
  211. Bid: utils.IntToFloat64(v.Bid, int(goods.Decimalplace)),
  212. BV: v.BV,
  213. Ask: utils.IntToFloat64(v.Ask, int(goods.Decimalplace)),
  214. AV: v.AV,
  215. HV: v.HV,
  216. HI: v.HI,
  217. TDR: buyOrSell,
  218. TK: v.TK,
  219. })
  220. }
  221. // 查询成功
  222. logger.GetLogger().Debugln("QueryHistoryTikDatas successed: %v", rst)
  223. appG.Response(http.StatusOK, e.SUCCESS, rst)
  224. }
  225. // QueryTSDataReq 分时图数据查询请求参数
  226. type QueryTSDataReq struct {
  227. GoodsCode string `form:"goodsCode" binding:"required"` // 商品代码
  228. }
  229. // QueryTSDataRsp 分时图数据查询返回模型
  230. type QueryTSDataRsp struct {
  231. GoodsCode string `json:"goodsCode"` // 商品代码
  232. OutGoodsCode string `json:"outGoodsCode"` // 外部商品代码
  233. DecimalPlace int `json:"decimalPlace"` // 小数位
  234. TradeDate string `json:"tradeDate"` // 交易日
  235. StartTime time.Time `json:"startTime"` // 开始时间
  236. EndTime time.Time `json:"endTime"` // 结束时间
  237. PreSettle float64 `json:"preSettle"` // 昨结价
  238. Count int `json:"Count"` // 交易日应有数据个数
  239. HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
  240. RunSteps []TSDataRunStep `json:"runSteps"` // 交易日开休市计划
  241. }
  242. // TSDataRunStep 分时图交易日开休市计划
  243. type TSDataRunStep struct {
  244. Groupid int32 `json:"groupid"` // 分组ID
  245. Tradeweekday int32 `json:"tradeweekday"` // 交易日归属 - 0:星期天、1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六
  246. Sectionid int32 `json:"sectionid"` // 从 1 开始 往下编 [0为系统清盘、结算时间] SectionId = 0时,开始时间=清盘时间 开始周几= 清盘周几, 结束时间=结算时间 结束周几=结算周几
  247. Runstep int32 `json:"runstep"` // 运行阶段 - 2:连续交易
  248. Startweekday int32 `json:"startweekday"` // 起始周几
  249. Starttime string `json:"starttime"` // 起始时间(HH:mm)
  250. Endweekday int32 `json:"endweekday"` // 结束周几
  251. Endtime string `json:"endtime"` // 结束时间(HH:mm)
  252. Startflag int32 `json:"startflag"` // 开始日标识 - (-1:上日 0:当日 1:次日 )
  253. Endflag int32 `json:"endflag"` // 结束日标识 - (-1:上日 0:当日 1:次日 )
  254. Start time.Time `json:"start"` // 真实开始时间
  255. End time.Time `json:"end"` // 真实结束时间
  256. }
  257. // QueryTSData 分时图数据查询
  258. // @Summary 分时图数据查询
  259. // @Produce json
  260. // @Security ApiKeyAuth
  261. // @Param goodsCode query string true "商品代码"
  262. // @Success 200 {object} QueryTSDataRsp
  263. // @Failure 500 {object} app.Response
  264. // @Router /Quote/QueryTSData [get]
  265. // @Tags 行情服务
  266. func QueryTSData(c *gin.Context) {
  267. appG := app.Gin{C: c}
  268. // 获取请求参数
  269. var req QueryTSDataReq
  270. if err := appG.C.ShouldBindQuery(&req); err != nil {
  271. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  272. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  273. return
  274. }
  275. // FIXME: - 一些不常变化的数据(如市场信息、商品信息等)应缓存到Redis中, 或缓存到服务内存
  276. // 获取商品信息
  277. goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
  278. if goods == nil {
  279. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  280. appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
  281. return
  282. }
  283. // 获取市场
  284. market, err := models.GetMarketByGoodsCode(req.GoodsCode)
  285. if err != nil {
  286. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  287. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  288. return
  289. }
  290. if market == nil {
  291. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  292. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
  293. return
  294. }
  295. // 获取目标品种交易日
  296. // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
  297. // 故通道交易的品种目前只能在交易系统的外部市场中获
  298. // 取统一的交易日,后期应要求服务端同步外部数据
  299. // 2021.06.30 业务更新:15、97和99市场的行情交易日要从商品盘面中获取
  300. quoteTradeDate := ""
  301. if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
  302. if quoteDays, err := models.GetQuoteDays("'" + req.GoodsCode + "'"); err == nil {
  303. if len(quoteDays) > 0 {
  304. quoteTradeDate = time.Unix(quoteDays[0].Exchangedate, 0).Format("20060102")
  305. }
  306. }
  307. } else {
  308. if marketRun, err := models.GetMarketRun(int(market.Marketid)); err == nil {
  309. quoteTradeDate = marketRun.Tradedate2
  310. }
  311. }
  312. if len(quoteTradeDate) == 0 {
  313. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  314. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKETRUN_FAILED, nil)
  315. return
  316. }
  317. // 获取目标品种的开休市计划
  318. var runSteps []map[string]interface{}
  319. // 通道交易外部市场开休市计划表 - QuoteSourceGroupRunStep; 其它市场的 - MarketRunStepDetail
  320. if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
  321. // 外部市场
  322. sourceRunSteps, err := models.FindQuoteSourceGroupRunSteps(*goods)
  323. if err != nil {
  324. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  325. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  326. return
  327. }
  328. for _, v := range sourceRunSteps {
  329. // struct -> json
  330. if jsonBytes, err := json.Marshal(v); err == nil {
  331. // json -> struct
  332. var runStepMap map[string]interface{}
  333. json.Unmarshal(jsonBytes, &runStepMap)
  334. runSteps = append(runSteps, runStepMap)
  335. }
  336. }
  337. }
  338. // 非外部市场或外部市场没有配置QuoteSourceGroupRunStep表数据的情况下,都从MarketRunStepDetail中获取数据
  339. if len(runSteps) == 0 {
  340. sourceRunSteps, err := models.FindMarketRunStepDetails(int(market.Marketid))
  341. if err != nil {
  342. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  343. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  344. return
  345. }
  346. for _, v := range sourceRunSteps {
  347. // struct -> json
  348. if jsonBytes, err := json.Marshal(v); err == nil {
  349. // json -> struct
  350. var runStepMap map[string]interface{}
  351. json.Unmarshal(jsonBytes, &runStepMap)
  352. runSteps = append(runSteps, runStepMap)
  353. }
  354. }
  355. }
  356. // 获取目标商品的盘面信息
  357. quoteDays, err := models.GetQuoteDays("'" + strings.ToUpper(goods.Goodscode) + "'")
  358. if err != nil {
  359. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  360. appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
  361. return
  362. }
  363. var preSettle float64
  364. var preSettleInt int
  365. if len(quoteDays) > 0 {
  366. if quoteDays[0].Presettle != 0 {
  367. preSettleInt = int(quoteDays[0].Presettle)
  368. preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
  369. }
  370. if preSettle == 0 && quoteDays[0].Preclose != 0 {
  371. preSettleInt = int(quoteDays[0].Preclose)
  372. preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
  373. }
  374. }
  375. // 构建返回数据
  376. // 注意:这里使用的是 marketRun.Tradedate2 行情交易日
  377. queryTSDataRsp := QueryTSDataRsp{
  378. GoodsCode: goods.Goodscode,
  379. OutGoodsCode: goods.Outgoodscode,
  380. TradeDate: quoteTradeDate,
  381. DecimalPlace: int(goods.Decimalplace),
  382. PreSettle: preSettle,
  383. }
  384. // 构建分时图可直接使用的开休市数据
  385. // 这里有一个知识点:TRADEWEEKDAY 与 STARTWEEKDAY,以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天(管理端限制),
  386. // 所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
  387. // 当前交易日(周几)对应的开休市计划
  388. tradeDate, err := time.ParseInLocation("20060102", quoteTradeDate, time.Local)
  389. if err != nil {
  390. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  391. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  392. return
  393. }
  394. // !!!开休市计划明细
  395. curWeekRunSteps := make([]map[string]interface{}, 0)
  396. for _, v := range runSteps {
  397. tradeWeekDay := int(v["tradeweekday"].(float64))
  398. if tradeWeekDay == int(tradeDate.Weekday()) {
  399. curWeekRunSteps = append(curWeekRunSteps, v)
  400. }
  401. }
  402. // 获取不到可用的开休市计划
  403. if len(curWeekRunSteps) == 0 {
  404. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  405. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  406. return
  407. }
  408. // 按 SECTIONID 顺序排序
  409. sort.Slice(curWeekRunSteps, func(i int, j int) bool {
  410. return curWeekRunSteps[i]["sectionid"].(float64) < curWeekRunSteps[j]["sectionid"].(float64)
  411. })
  412. // 把各开休市时间段转化为真实的时间
  413. // 关于开休市计划的时间顺序:管理端会按时间顺序添加开休市计划,所以交易日开始时间为第一条记录的开始时间,结束时间为最后一条记录的结束时间
  414. // 关于目标商品的交易日问题:目前只能从商品所属市场获取当前交易日,这样有两个问题,一是不能按常规显示最后一个有历史数据的交易日;二是目前所有外部商品的开休市计划都是一样的。
  415. timeFormat := "20060102 15:04"
  416. // 开始时间
  417. startInterval := getTradeDay(int(curWeekRunSteps[0]["tradeweekday"].(float64)), int(curWeekRunSteps[0]["startweekday"].(float64)))
  418. queryTSDataRsp.StartTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[0]["starttime"].(string)), time.Local)
  419. if startInterval != 0 {
  420. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
  421. queryTSDataRsp.StartTime = queryTSDataRsp.StartTime.Add(duration)
  422. }
  423. // 结束时间
  424. index := len(curWeekRunSteps) - 1
  425. endInterval := getTradeDay(int(curWeekRunSteps[index]["tradeweekday"].(float64)), int(curWeekRunSteps[index]["endweekday"].(float64)))
  426. queryTSDataRsp.EndTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[index]["endtime"].(string)), time.Local)
  427. if endInterval != 0 {
  428. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
  429. queryTSDataRsp.EndTime = queryTSDataRsp.EndTime.Add(duration)
  430. }
  431. // fmt.Printf("开始时间:%s 结束时间:%s\n", queryTSDataRsp.StartTime.Format(timeFormat), queryTSDataRsp.EndTime.Format(timeFormat))
  432. // 获取目标时间段的历史数据(1分钟周期)
  433. // 这里要注意:由于交易库和行情库由于GoodsCode大小写不一定对得上,所以在使用交易库的商品查询行情数据时间,都要使用OutGoodsCode字段
  434. // 20210702: 按行情服务要求,改用大写的GoodsCode
  435. cycleDatas, err := models.GetHistoryCycleDatas(models.CycleTypeMinutes1, strings.ToUpper(queryTSDataRsp.GoodsCode), &queryTSDataRsp.StartTime, &queryTSDataRsp.EndTime, 0, true)
  436. if err != nil {
  437. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  438. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  439. return
  440. }
  441. // ==================== 补数据(补休市数据和缺少的数据)====================
  442. if len(cycleDatas) > 0 {
  443. // 补数据第一步:如果第一数据不是开始时间的,需要补数据
  444. sources := time.Unix(int64(cycleDatas[0].ST), 0)
  445. diff := sources.Sub(queryTSDataRsp.StartTime)
  446. if diff.Minutes() > 0 {
  447. minute := int(diff.Minutes())
  448. for i := 1; i <= minute; i++ {
  449. st := cycleDatas[0].ST - i*60
  450. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  451. // 前面的数据使用昨结来补
  452. cycleDatas = append(cycleDatas, models.CycleData{
  453. GC: cycleDatas[0].GC,
  454. Open: preSettleInt,
  455. High: preSettleInt,
  456. Low: preSettleInt,
  457. Close: preSettleInt,
  458. TV: 0,
  459. TT: 0,
  460. HV: 0,
  461. SP: 0,
  462. ST: st,
  463. SST: stt,
  464. FI: true,
  465. })
  466. }
  467. }
  468. // 接时间戳排序
  469. sort.Slice(cycleDatas, func(i int, j int) bool {
  470. return cycleDatas[i].ST < cycleDatas[j].ST
  471. })
  472. // 补数据第二步:按尾部的时间(当前服务器时间或最后休市时间)进行全补
  473. // 获取服务器时间
  474. s, _ := models.GetServerTime()
  475. endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
  476. if err != nil {
  477. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  478. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  479. return
  480. }
  481. if endTime.After(queryTSDataRsp.EndTime) {
  482. endTime = queryTSDataRsp.EndTime
  483. }
  484. index := len(cycleDatas) - 1
  485. sources = time.Unix(int64(cycleDatas[index].ST), 0)
  486. diff = endTime.Sub(sources)
  487. if diff.Minutes() > 0 {
  488. minute := int(diff.Minutes())
  489. for i := 1; i <= minute; i++ {
  490. st := cycleDatas[index].ST + i*60
  491. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  492. cycleDatas = append(cycleDatas, models.CycleData{
  493. GC: cycleDatas[index].GC,
  494. Open: cycleDatas[index].Close,
  495. High: cycleDatas[index].Close,
  496. Low: cycleDatas[index].Close,
  497. Close: cycleDatas[index].Close,
  498. TV: 0,
  499. TT: 0,
  500. HV: 0,
  501. SP: 0,
  502. ST: st,
  503. SST: stt,
  504. FI: true,
  505. })
  506. }
  507. }
  508. // 接时间戳排序
  509. sort.Slice(cycleDatas, func(i int, j int) bool {
  510. return cycleDatas[i].ST < cycleDatas[j].ST
  511. })
  512. // 补数据第三步:补中间数据
  513. fillDatas := make([]models.CycleData, 0)
  514. for i := range cycleDatas {
  515. // 第一条记录跳过
  516. if i == 0 {
  517. continue
  518. }
  519. current := time.Unix(int64(cycleDatas[i].ST), 0)
  520. prev := time.Unix(int64(cycleDatas[i-1].ST), 0)
  521. diff := current.Sub(prev)
  522. if diff.Minutes() > 1 {
  523. minute := int(diff.Minutes())
  524. // 判断是否需要补数据,与上一条数据的间距不是一分钟
  525. if minute > 1 {
  526. // 注意补中间数据时,是不用补最后一条数据的,所以是 < minute
  527. for j := 1; j < minute; j++ {
  528. st := cycleDatas[i-1].ST + j*60
  529. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  530. fillDatas = append(fillDatas, models.CycleData{
  531. GC: cycleDatas[i-1].GC,
  532. Open: cycleDatas[i-1].Close,
  533. High: cycleDatas[i-1].Close,
  534. Low: cycleDatas[i-1].Close,
  535. Close: cycleDatas[i-1].Close,
  536. TV: 0,
  537. TT: 0,
  538. HV: 0,
  539. SP: 0,
  540. ST: st,
  541. SST: stt,
  542. FI: true,
  543. })
  544. }
  545. }
  546. }
  547. }
  548. // 加入到源数据
  549. cycleDatas = append(cycleDatas, fillDatas...)
  550. // 接时间戳排序
  551. sort.Slice(cycleDatas, func(i int, j int) bool {
  552. return cycleDatas[i].ST < cycleDatas[j].ST
  553. })
  554. } else {
  555. // TODO: - 下面这块操作需求确认
  556. // 如果查询结果是空数据,则使用昨结价补到服务器时间(或最后休市时间)
  557. // 获取服务器时间
  558. s, _ := models.GetServerTime()
  559. endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
  560. if err != nil {
  561. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  562. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  563. return
  564. }
  565. if endTime.After(queryTSDataRsp.EndTime) {
  566. endTime = queryTSDataRsp.EndTime
  567. }
  568. diff := endTime.Sub(queryTSDataRsp.StartTime)
  569. minute := int(diff.Minutes())
  570. for i := 1; i <= minute; i++ {
  571. st := int(queryTSDataRsp.StartTime.Unix()) + i*60
  572. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  573. cycleDatas = append(cycleDatas, models.CycleData{
  574. GC: queryTSDataRsp.GoodsCode,
  575. Open: preSettleInt,
  576. High: preSettleInt,
  577. Low: preSettleInt,
  578. Close: preSettleInt,
  579. TV: 0,
  580. TT: 0,
  581. HV: 0,
  582. SP: 0,
  583. ST: st,
  584. SST: stt,
  585. FI: true,
  586. })
  587. }
  588. // 接时间戳排序
  589. sort.Slice(cycleDatas, func(i int, j int) bool {
  590. return cycleDatas[i].ST < cycleDatas[j].ST
  591. })
  592. }
  593. // 补数据第四步:清除掉开市计划外的数据
  594. // 先计算出每条计划明细的真正开始与结束时间
  595. queryTSDataRsp.RunSteps = make([]TSDataRunStep, 0)
  596. for _, v := range curWeekRunSteps {
  597. // 开始时间
  598. startInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["startweekday"].(float64)))
  599. v["start"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["starttime"].(string)), time.Local)
  600. if startInterval != 0 {
  601. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
  602. v["start"] = v["start"].(time.Time).Add(duration)
  603. }
  604. // 结束时间
  605. endInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["endweekday"].(float64)))
  606. v["end"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["endtime"].(string)), time.Local)
  607. if endInterval != 0 {
  608. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
  609. v["end"] = v["end"].(time.Time).Add(duration)
  610. }
  611. // map -> struct
  612. var tsDataRunStep TSDataRunStep
  613. jsonbody, err := json.Marshal(v)
  614. if err != nil {
  615. continue
  616. }
  617. if err := json.Unmarshal(jsonbody, &tsDataRunStep); err != nil {
  618. continue
  619. }
  620. queryTSDataRsp.RunSteps = append(queryTSDataRsp.RunSteps, tsDataRunStep)
  621. }
  622. // 最终返回的历史数据
  623. historyDatas := make([]HistoryData, 0)
  624. for _, cycleData := range cycleDatas {
  625. needAdd := false
  626. for _, runStep := range curWeekRunSteps {
  627. // 判断是否在开市计划内
  628. if cycleData.ST >= int(runStep["start"].(time.Time).Unix()) && cycleData.ST <= int(runStep["end"].(time.Time).Unix()) {
  629. needAdd = true
  630. break
  631. }
  632. }
  633. if needAdd {
  634. historyDatas = append(historyDatas, HistoryData{
  635. Opened: utils.IntToFloat64(cycleData.Open, int(goods.Decimalplace)),
  636. Highest: utils.IntToFloat64(cycleData.High, int(goods.Decimalplace)),
  637. Lowest: utils.IntToFloat64(cycleData.Low, int(goods.Decimalplace)),
  638. Closed: utils.IntToFloat64(cycleData.Close, int(goods.Decimalplace)),
  639. TotleVolume: cycleData.TV,
  640. TotleTurnover: float64(cycleData.TT),
  641. HoldVolume: cycleData.HV,
  642. Settle: utils.IntToFloat64(cycleData.SP, int(goods.Decimalplace)),
  643. TimeStamp: time.Unix(int64(cycleData.ST), 0),
  644. IsFill: cycleData.FI,
  645. })
  646. }
  647. }
  648. queryTSDataRsp.HistoryDatas = historyDatas
  649. // 计算当前交易日应该有多少个历史数据,主要用于iOS
  650. for _, v := range queryTSDataRsp.RunSteps {
  651. diff := v.End.Sub(v.Start)
  652. queryTSDataRsp.Count += int(diff.Minutes())
  653. }
  654. // 查询成功
  655. logger.GetLogger().Debugln("QueryTSData successed: %v", queryTSDataRsp)
  656. appG.Response(http.StatusOK, e.SUCCESS, queryTSDataRsp)
  657. }
  658. // getTradeDay 获取结算计划中天数间隔的方法
  659. // - tradeDay: 交易日周几
  660. // - weekDay: 开始或结束周几
  661. // - Returns: 天数间隔
  662. func getTradeDay(tradeDay, weekDay int) int {
  663. if tradeDay == weekDay {
  664. return 0
  665. }
  666. if weekDay == 0 {
  667. weekDay = 7
  668. }
  669. betWeekDay := weekDay - tradeDay
  670. if betWeekDay < 0 {
  671. betWeekDay = betWeekDay + 7
  672. }
  673. if betWeekDay >= 4 {
  674. betWeekDay = betWeekDay - 7
  675. }
  676. return betWeekDay
  677. }