history.go 26 KB

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